diff --git a/application/Espo/Controllers/EmailAddress.php b/application/Espo/Controllers/EmailAddress.php index b0ad7811ec..c0e4ce42c5 100644 --- a/application/Espo/Controllers/EmailAddress.php +++ b/application/Espo/Controllers/EmailAddress.php @@ -42,11 +42,13 @@ class EmailAddress extends \Espo\Core\Controllers\Record throw new Forbidden(); } $q = $request->get('q'); - $limit = intval($request->get('limit')); - if (empty($limit) || $limit > 30) { - $limit = 5; + $maxSize = intval($request->get('maxSize')); + if (empty($maxSize) || $maxSize > 50) { + $maxSize = $this->getConfig()->get('recordsPerPage', 20); } - return $this->getRecordService()->searchInAddressBook($q, $limit); + + $onlyActual = $request->get('onlyActual') === 'true'; + + return $this->getRecordService()->searchInAddressBook($q, $maxSize, $onlyActual); } } - diff --git a/application/Espo/Core/SelectManagers/Base.php b/application/Espo/Core/SelectManagers/Base.php index eb2c38c966..172c272f59 100644 --- a/application/Espo/Core/SelectManagers/Base.php +++ b/application/Espo/Core/SelectManagers/Base.php @@ -962,6 +962,7 @@ class Base case 'after': $where['type'] = 'after'; $dt = new \DateTime($value, new \DateTimeZone($timeZone)); + $dt->modify('+1 day -1 second'); $dt->setTimezone(new \DateTimeZone('UTC')); $where['value'] = $dt->format($format); break; diff --git a/application/Espo/Core/Utils/Database/DBAL/Schema/Comparator.php b/application/Espo/Core/Utils/Database/DBAL/Schema/Comparator.php index 19351ff148..76ea2bc5fc 100644 --- a/application/Espo/Core/Utils/Database/DBAL/Schema/Comparator.php +++ b/application/Espo/Core/Utils/Database/DBAL/Schema/Comparator.php @@ -28,12 +28,11 @@ ************************************************************************/ namespace Espo\Core\Utils\Database\DBAL\Schema; -use Doctrine\DBAL\Schema\Column; +use Doctrine\DBAL\Schema\Column; class Comparator extends \Doctrine\DBAL\Schema\Comparator { - public function diffColumn(Column $column1, Column $column2) { $changedProperties = array(); @@ -80,6 +79,15 @@ class Comparator extends \Doctrine\DBAL\Schema\Comparator } } + if ($column1->getType() instanceof \Doctrine\DBAL\Types\TextType) { + $length1 = $column1->getLength() ?: 16777215/* mediumtext length*/; + $length2 = $column2->getLength() ?: 16777215; + + if ($length2 > $length1) { + $changedProperties[] = 'length'; + } + } + if ($column1->getType() instanceof \Doctrine\DBAL\Types\DecimalType) { if (($column1->getPrecision()?:10) != ($column2->getPrecision()?:10)) { $changedProperties[] = 'precision'; diff --git a/application/Espo/Core/Utils/Database/Schema/tables/preferences.php b/application/Espo/Core/Utils/Database/Schema/tables/preferences.php index 81a9f04352..3c07039620 100644 --- a/application/Espo/Core/Utils/Database/Schema/tables/preferences.php +++ b/application/Espo/Core/Utils/Database/Schema/tables/preferences.php @@ -27,29 +27,28 @@ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ -return array( +return [ - 'unset' => array( + 'unset' => [ '__APPEND__', - 'Preferences' - ), + 'Preferences', + ], 'unsetIgnore' => [ '__APPEND__', ['Preferences', 'fields', 'id'], - ['Preferences', 'fields', 'data'] + ['Preferences', 'fields', 'data'], ], - 'Preferences' => array( - 'fields' => array( - 'id' => array( + 'Preferences' => [ + 'fields' => [ + 'id' => [ 'dbType' => 'varchar', 'len' => 24, - 'type' => 'id' - ), - 'data' => array( - 'type' => 'text' - ) - ) - ) - -); - + 'type' => 'id', + ], + 'data' => [ + 'type' => 'text', + 'len' => 16777216, + ] + ] + ], +]; diff --git a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Call.json b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Call.json index 8f800d6cc0..b9dbb29fcc 100644 --- a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Call.json +++ b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Call.json @@ -61,6 +61,10 @@ "type": "enum", "notStorable": true, "options": ["None", "Accepted", "Tentative", "Declined"], + "style": { + "Accepted": "success", + "Declined": "danger" + }, "layoutDetailDisabled": true, "layoutMassUpdateDisabled": true, "where": { diff --git a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Meeting.json b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Meeting.json index b4856953c4..2782273695 100644 --- a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Meeting.json +++ b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Meeting.json @@ -65,6 +65,10 @@ "type": "enum", "notStorable": true, "options": ["None", "Accepted", "Tentative", "Declined"], + "style": { + "Accepted": "success", + "Declined": "danger" + }, "layoutDetailDisabled": true, "layoutMassUpdateDisabled": true, "where": { diff --git a/application/Espo/Resources/i18n/en_US/Global.json b/application/Espo/Resources/i18n/en_US/Global.json index 33481f8969..007ebb4722 100644 --- a/application/Espo/Resources/i18n/en_US/Global.json +++ b/application/Espo/Resources/i18n/en_US/Global.json @@ -265,6 +265,7 @@ "fieldShouldBeGreater": "{field} shouldn't be less then {value}", "fieldBadPasswordConfirm": "{field} not confirmed properly", "fieldMaxFileSizeError": "File should not exceed {max} Mb", + "fieldValueDuplicate": "Duplicate value", "fieldIsUploading": "Uploading in progress", "resetPreferencesDone": "Preferences has been reset to defaults", "confirmation": "Are you sure?", diff --git a/application/Espo/Services/EmailAddress.php b/application/Espo/Services/EmailAddress.php index 2640a2db49..b055d89faa 100644 --- a/application/Espo/Services/EmailAddress.php +++ b/application/Espo/Services/EmailAddress.php @@ -37,130 +37,96 @@ class EmailAddress extends Record { const ERASED_PREFIX = 'ERASED:'; - protected function findInAddressBookByEntityType($query, $limit, $entityType, &$result) + protected function findInAddressBookByEntityType($query, $limit, $entityType, &$result, $onlyActual = false) { - $whereClause = array( - 'OR' => array( - array( + $whereClause = [ + 'OR' => [ + [ 'name*' => $query . '%' - ), - array( + ], + [ 'emailAddress*' => $query . '%' - ) - ), - array( + ] + ], + [ 'emailAddress!=' => null - ) - ); + ] + ]; - $searchParams = array( + $selectParams = [ 'whereClause' => $whereClause, 'orderBy' => 'name', 'limit' => $limit - ); + ]; + + $handleSelectParamsMethodName = 'handleSelectParams' . $entityType; + if (method_exists($this, $handleSelectParamsMethodName)) { + $this->$handleSelectParamsMethodName($query, $selectParams); + } $selectManager = $this->getSelectManagerFactory()->create($entityType); + $selectManager->applyAccess($selectParams); - $selectManager->applyAccess($searchParams); - - $collection = $this->getEntityManager()->getRepository($entityType)->find($searchParams); + $collection = $this->getEntityManager()->getRepository($entityType)->find($selectParams); foreach ($collection as $entity) { $emailAddress = $entity->get('emailAddress'); + $emailAddressData = $this->getEntityManager()->getRepository('EmailAddress')->getEmailAddressData($entity); + + $skipPrimaryEmailAddress = false; if ($emailAddress) { - if (strpos($emailAddress, self::ERASED_PREFIX) === 0) { - continue; + if (strpos($emailAddress, self::ERASED_PREFIX) === 0) $skipPrimaryEmailAddress = true; + + if ($onlyActual) { + if ($entity->get('emailAddressIsOptedOut')) $skipPrimaryEmailAddress = true; + + foreach ($emailAddressData as $item) { + if ($emailAddress !== $item->emailAddress) continue; + if (!empty($item->invalid)) $skipPrimaryEmailAddress = true; + } } } - $result[] = [ - 'emailAddress' => $emailAddress, - 'entityName' => $entity->get('name'), - 'entityType' => $entityType, - 'entityId' => $entity->id - ]; + if (!$skipPrimaryEmailAddress) { + $result[] = [ + 'emailAddress' => $emailAddress, + 'entityName' => $entity->get('name'), + 'entityType' => $entityType, + 'entityId' => $entity->id + ]; + } - $emailAddressData = $this->getEntityManager()->getRepository('EmailAddress')->getEmailAddressData($entity); - foreach ($emailAddressData as $d) { - if ($emailAddress != $d->emailAddress) { - $emailAddress = $d->emailAddress; - if (strpos($emailAddress, $query) === 0 && strpos($emailAddress, self::ERASED_PREFIX) !== 0) { - $result[] = [ - 'emailAddress' => $emailAddress, - 'entityName' => $entity->get('name'), - 'entityType' => $entityType, - 'entityId' => $entity->id - ]; - } + foreach ($emailAddressData as $item) { + if ($emailAddress === $item->emailAddress) continue; + + if (strpos($item->emailAddress, self::ERASED_PREFIX) === 0) continue; + + if ($onlyActual) { + if (!empty($item->invalid)) continue; + if (!empty($item->optOut)) continue; } + + $result[] = [ + 'emailAddress' => $item->emailAddress, + 'entityName' => $entity->get('name'), + 'entityType' => $entityType, + 'entityId' => $entity->id + ]; } } } - protected function findInAddressBookUsers($query, $limit, &$result) + protected function handleSelectParamsUser($query, &$selectParams) { - $whereClause = array( - 'OR' => array( - array( - 'name*' => $query . '%' - ), - array( - 'emailAddress*' => $query . '%' - ) - ), - array( - 'emailAddress!=' => null - ) - ); - if ($this->getAcl()->get('portalPermission') === 'no') { - $whereClause['type!='] = 'portal'; - } - - $searchParams = array( - 'whereClause' => $whereClause, - 'orderBy' => 'name', - 'limit' => $limit - ); - - $selectManager = $this->getSelectManagerFactory()->create('User'); - - $selectManager->applyAccess($searchParams); - - $collection = $this->getEntityManager()->getRepository('User')->find($searchParams); - - foreach ($collection as $entity) { - $emailAddress = $entity->get('emailAddress'); - - if ($emailAddress) { - if (strpos($emailAddress, self::ERASED_PREFIX) === 0) { - continue; - } - } - - $result[] = [ - 'emailAddress' => $emailAddress, - 'entityName' => $entity->get('name'), - 'entityType' => 'User', - 'entityId' => $entity->id + $selectParams['whereClause'][] = [ + 'type!=' => 'portal' ]; - - $emailAddressData = $this->getEntityManager()->getRepository('EmailAddress')->getEmailAddressData($entity); - foreach ($emailAddressData as $d) { - if ($emailAddress != $d->emailAddress) { - $emailAddress = $d->emailAddress; - if (strpos($emailAddress, $query) === 0 && strpos($emailAddress, self::ERASED_PREFIX) !== 0) { - $result[] = [ - 'emailAddress' => $emailAddress, - 'entityName' => $entity->get('name'), - 'entityType' => 'User', - 'entityId' => $entity->id - ]; - } - } - } } + $selectParams['whereClause'][] = [ + 'type!=' => ['api', 'system', 'super-admin'] + ]; } protected function findInInboundEmail($query, $limit, &$result) @@ -188,39 +154,52 @@ class EmailAddress extends Record } } - public function searchInAddressBook($query, $limit) + public function searchInAddressBook($query, $limit, $onlyActual = false) { $result = []; - $this->findInAddressBookUsers($query, $limit, $result); + $this->findInAddressBookByEntityType($query, $limit, 'User', $result, $onlyActual); if ($this->getAcl()->checkScope('Contact')) { - $this->findInAddressBookByEntityType($query, $limit, 'Contact', $result); + $this->findInAddressBookByEntityType($query, $limit, 'Contact', $result, $onlyActual); } if ($this->getAcl()->checkScope('Lead')) { - $this->findInAddressBookByEntityType($query, $limit, 'Lead', $result); + $this->findInAddressBookByEntityType($query, $limit, 'Lead', $result, $onlyActual); } if ($this->getAcl()->checkScope('Account')) { - $this->findInAddressBookByEntityType($query, $limit, 'Account', $result); + $this->findInAddressBookByEntityType($query, $limit, 'Account', $result, $onlyActual); } $this->findInInboundEmail($query, $limit, $result); foreach ($this->getHavingEmailAddressEntityTypeList() as $entityType) { if ($this->getAcl()->checkScope($entityType)) { - $this->findInAddressBookByEntityType($query, $limit, $entityType, $result); + $this->findInAddressBookByEntityType($query, $limit, $entityType, $result, $onlyActual); } } - $final = array(); + $finalResult = []; - foreach ($result as $r) { - foreach ($final as $f) { - if ($f['emailAddress'] == $r['emailAddress']) { + foreach ($result as $item) { + foreach ($finalResult as $item1) { + if ($item['emailAddress'] == $item1['emailAddress']) { continue 2; } } - $final[] = $r; + $finalResult[] = $item; } - return $final; + usort($finalResult, function ($item1, $item2) use ($query) { + if (strpos($query, '@') === false) return 0; + + $p1 = strpos($item1['emailAddress'], $query); + $p2 = strpos($item2['emailAddress'], $query); + + if ($p1 === 0 && $p2 !== 0) return -1; + if ($p1 !== 0 && $p2 !== 0) return 0; + if ($p1 !== 0 && $p2 === 0) return 1; + + return 0; + }); + + return $finalResult; } protected function getHavingEmailAddressEntityTypeList() @@ -234,6 +213,4 @@ class EmailAddress extends Record } return $list; } - } - diff --git a/client/res/templates/site/navbar.tpl b/client/res/templates/site/navbar.tpl index 79e67dfb7e..1ae3a2a473 100644 --- a/client/res/templates/site/navbar.tpl +++ b/client/res/templates/site/navbar.tpl @@ -13,7 +13,6 @@ {{#unless isInMore}}
  • - {{label}} {{#if iconClass}} @@ -24,6 +23,7 @@ {{shortLabel}} {{/if}} + {{label}}
  • {{/unless}} diff --git a/client/src/views/email/fields/email-address-varchar.js b/client/src/views/email/fields/email-address-varchar.js index 7a7f8339b2..b959aa755e 100644 --- a/client/src/views/email/fields/email-address-varchar.js +++ b/client/src/views/email/fields/email-address-varchar.js @@ -87,6 +87,13 @@ Espo.define('views/email/fields/email-address-varchar', ['views/fields/varchar', } }, + getAutocompleteMaxCount: function () { + if (this.autocompleteMaxCount) { + return this.autocompleteMaxCount; + } + return this.getConfig().get('recordsPerPage'); + }, + parseNameFromStringAddress: function (s) { return From.prototype.parseNameFromStringAddress.call(this, s); }, @@ -143,11 +150,12 @@ Espo.define('views/email/fields/email-address-varchar', ['views/fields/varchar', this.$input.autocomplete({ serviceUrl: function (q) { - return 'EmailAddress/action/searchInAddressBook?limit=5'; + return 'EmailAddress/action/searchInAddressBook?onlyActual=true&maxSize=' + this.getAutocompleteMaxCount(); }.bind(this), paramName: 'q', minChars: 1, autoSelectFirst: true, + triggerSelectOnValidInput: false, formatResult: function (suggestion) { return suggestion.name + ' <' + suggestion.id + '>'; }, diff --git a/client/src/views/email/fields/email-address.js b/client/src/views/email/fields/email-address.js index ca913c3987..7b6ac0478b 100644 --- a/client/src/views/email/fields/email-address.js +++ b/client/src/views/email/fields/email-address.js @@ -29,6 +29,13 @@ Espo.define('views/email/fields/email-address', ['views/fields/base'], function return Dep.extend({ + getAutocompleteMaxCount: function () { + if (this.autocompleteMaxCount) { + return this.autocompleteMaxCount; + } + return this.getConfig().get('recordsPerPage'); + }, + afterRender: function () { Dep.prototype.afterRender.call(this); @@ -37,11 +44,12 @@ Espo.define('views/email/fields/email-address', ['views/fields/base'], function if (this.mode == 'search') { this.$input.autocomplete({ serviceUrl: function (q) { - return 'EmailAddress/action/searchInAddressBook?limit=5'; + return 'EmailAddress/action/searchInAddressBook?maxSize=' + this.getAutocompleteMaxCount(); }.bind(this), paramName: 'q', minChars: 1, autoSelectFirst: true, + triggerSelectOnValidInput: false, formatResult: function (suggestion) { return suggestion.name + ' <' + suggestion.id + '>'; }, diff --git a/client/src/views/fields/email.js b/client/src/views/fields/email.js index c496a807a2..645a937cf2 100644 --- a/client/src/views/fields/email.js +++ b/client/src/views/fields/email.js @@ -42,20 +42,33 @@ Espo.define('views/fields/email', 'views/fields/varchar', function (Dep) { validateEmailData: function () { var data = this.model.get(this.dataFieldName); - if (data && data.length) { - var re = /\S+@+\S+/; - var notValid = false; - data.forEach(function (row, i) { - var emailAddress = row.emailAddress; - if (!re.test(emailAddress) && emailAddress.indexOf(this.erasedPlaceholder) !== 0) { - var msg = this.translate('fieldShouldBeEmail', 'messages').replace('{field}', this.getLabelText()); - this.showValidationMessage(msg, 'div.email-address-block:nth-child(' + (i + 1).toString() + ') input'); - notValid = true; - } - }, this); - if (notValid) { - return true; + if (!data || !data.length) return; + + var addressList = []; + + var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + + var notValid = false; + + data.forEach(function (row, i) { + var address = row.emailAddress; + var addressLowerCase = String(address).toLowerCase(); + if (!re.test(addressLowerCase) && address.indexOf(this.erasedPlaceholder) !== 0) { + var msg = this.translate('fieldShouldBeEmail', 'messages').replace('{field}', this.getLabelText()); + this.showValidationMessage(msg, 'div.email-address-block:nth-child(' + (i + 1).toString() + ') input'); + notValid = true; + return; } + if (~addressList.indexOf(addressLowerCase)) { + var msg = this.translate('fieldValueDuplicate', 'messages').replace('{field}', this.getLabelText()); + this.showValidationMessage(msg, 'div.email-address-block:nth-child(' + (i + 1).toString() + ') input'); + notValid = true; + return; + } + addressList.push(addressLowerCase); + }, this); + if (notValid) { + return true; } }, @@ -126,6 +139,13 @@ Espo.define('views/fields/email', 'views/fields/varchar', function (Dep) { return data; }, + getAutocompleteMaxCount: function () { + if (this.autocompleteMaxCount) { + return this.autocompleteMaxCount; + } + return this.getConfig().get('recordsPerPage'); + }, + events: { 'click [data-action="mailTo"]': function (e) { this.mailTo($(e.currentTarget).data('email-address')); @@ -230,11 +250,12 @@ Espo.define('views/fields/email', 'views/fields/varchar', function (Dep) { if (this.mode == 'search') { this.$element.autocomplete({ serviceUrl: function (q) { - return 'EmailAddress/action/searchInAddressBook?limit=5'; + return 'EmailAddress/action/searchInAddressBook?maxSize=' + this.getAutocompleteMaxCount(); }.bind(this), paramName: 'q', minChars: 1, autoSelectFirst: true, + triggerSelectOnValidInput: false, formatResult: function (suggestion) { return suggestion.name + ' <' + suggestion.id + '>'; }, diff --git a/client/src/views/fields/link-multiple-with-role.js b/client/src/views/fields/link-multiple-with-role.js index 0945c1322e..829b915894 100644 --- a/client/src/views/fields/link-multiple-with-role.js +++ b/client/src/views/fields/link-multiple-with-role.js @@ -80,7 +80,9 @@ Espo.define('views/fields/link-multiple-with-role', 'views/fields/link-multiple' role = ''; } if (role != '') { - roleHtml = ' » ' + + var style = this.getMetadata().get(['entityDefs', this.model.entityType, 'fields', this.roleField, 'style', role]) || 'muted'; + roleHtml = ' » ' + + '' + this.getHelper().stripTags(this.getLanguage().translateOption(role, this.roleField, this.roleFieldScope)) + ''; } diff --git a/client/src/views/fields/phone.js b/client/src/views/fields/phone.js index 07a435f86f..98ded90f96 100644 --- a/client/src/views/fields/phone.js +++ b/client/src/views/fields/phone.js @@ -38,7 +38,7 @@ Espo.define('views/fields/phone', 'views/fields/varchar', function (Dep) { listTemplate: 'fields/phone/list', - validations: ['required'], + validations: ['required', 'phoneData'], validateRequired: function () { if (this.isRequired()) { @@ -50,6 +50,30 @@ Espo.define('views/fields/phone', 'views/fields/varchar', function (Dep) { } }, + validatePhoneData: function () { + var data = this.model.get(this.dataFieldName); + if (!data || !data.length) return; + + var numberList = []; + + var notValid = false; + data.forEach(function (row, i) { + var number = row.phoneNumber; + var numberClean = String(number).replace(/[\s\+]/g, ''); + + if (~numberList.indexOf(numberClean)) { + var msg = this.translate('fieldValueDuplicate', 'messages').replace('{field}', this.getLabelText()); + this.showValidationMessage(msg, 'div.phone-number-block:nth-child(' + (i + 1).toString() + ') input'); + notValid = true; + return; + } + numberList.push(numberClean); + }, this); + if (notValid) { + return true; + } + }, + data: function () { var phoneNumberData; if (this.mode == 'edit') { diff --git a/client/src/views/site/navbar.js b/client/src/views/site/navbar.js index ea13cf87af..9155fb51bc 100644 --- a/client/src/views/site/navbar.js +++ b/client/src/views/site/navbar.js @@ -251,17 +251,27 @@ Espo.define('views/site/navbar', 'view', function (Dep) { } }; - var tabCount = this.tabList.length; var $navbar = $('#navbar .navbar'); - var navbarNeededHeight = (this.getThemeManager().getParam('navbarHeight') || 43) + 1; + + if (window.innerWidth >= smallScreenWidth) { + $tabs.children('li').each(function (i, li) { + hideOneTab(); + }); + $navbar.css('max-height', 'unset'); + $navbar.css('overflow', 'visible'); + } + + var navbarHeight = this.getThemeManager().getParam('navbarHeight') || 43; + var navbarBaseWidth = this.getThemeManager().getParam('navbarBaseWidth') || 556; + + var tabCount = this.tabList.length; + + var navbarNeededHeight = navbarHeight + 1; $moreDd = $('#nav-more-tabs-dropdown'); $moreLi = $moreDd.closest('li'); - var navbarBaseWidth = this.getThemeManager().getParam('navbarBaseWidth') || 556; - var updateWidth = function () { - var windowWidth = $(window.document).width(); var windowWidth = window.innerWidth; var moreWidth = $moreLi.width(); @@ -273,6 +283,9 @@ Espo.define('views/site/navbar', 'view', function (Dep) { return; } + $navbar.css('max-height', navbarHeight + 'px'); + $navbar.css('overflow', 'hidden'); + $more.parent().addClass('hidden'); var headerWidth = this.$el.width(); @@ -293,6 +306,9 @@ Espo.define('views/site/navbar', 'view', function (Dep) { } } + $navbar.css('max-height', 'unset'); + $navbar.css('overflow', 'visible'); + if ($more.children().length > 0) { $moreDropdown.removeClass('hidden'); } @@ -306,6 +322,7 @@ Espo.define('views/site/navbar', 'view', function (Dep) { }, 200); } else { if (!isRecursive) { + updateWidth(); setTimeout(function () { processUpdateWidth(true); }, 10); diff --git a/frontend/less/espo-rtl/layout.less b/frontend/less/espo-rtl/layout.less new file mode 100644 index 0000000000..1935345a50 --- /dev/null +++ b/frontend/less/espo-rtl/layout.less @@ -0,0 +1,75 @@ +#navbar .navbar { + ul.tabs { + > li.more { + > ul > li > a { + > span.full-label { + padding-left: 30px; + padding-right: 30px; + position: static; + } + + } + } + } +} + +@media screen and (min-width: @screen-sm-min) { + + #navbar .navbar { + + ul.tabs { + > li > a > span.short-label { + float: right; + right: -4px; + left: 0; + margin-left: 4px; + margin-right: 0; + } + + li.more > li > a { + padding-right: 10px; + padding-left: 0; + } + } + } + + .search-container .view-mode-switcher-buttons-group { + float: left !important; + } +} + +@media screen and (max-width: (@screen-sm-min - 1px)) { + #navbar .navbar { + ul.tabs { + > li a > span.full-label { + padding-right: 30px; + padding-left: 0; + } + } + } +} + +div.list-kanban > div > table { + + th.group-header { + + > div { + padding-left: 0; + padding-right: @table-cell-padding; + } + + > div:before, + > div:after { + content: none; + } + } +} + +.kanban-row { + .item-menu-container.pull-right { + float: left !important; + left: -9px; + margin-left: -10px; + margin-right: 0; + } +} \ No newline at end of file diff --git a/frontend/less/espo-rtl/main.less b/frontend/less/espo-rtl/main.less index 2777fda304..2717e5b636 100644 --- a/frontend/less/espo-rtl/main.less +++ b/frontend/less/espo-rtl/main.less @@ -10,6 +10,9 @@ @import "../espo/layout.less"; @import "../espo/custom.less"; + +@import "layout.less"; + @import "custom.less"; @import "../espo/mixins/side-modal.less"; diff --git a/frontend/less/espo-vertical/layout.less b/frontend/less/espo-vertical/layout.less index c1b8d80fa9..a5fc4d3640 100644 --- a/frontend/less/espo-vertical/layout.less +++ b/frontend/less/espo-vertical/layout.less @@ -277,9 +277,8 @@ body { #navbar { ul.tabs { - > li > a > span.full-label { - position: absolute; - margin-left: 30px; + > li > a { + height: 37px } > li.more > ul > li > a > span.full-label { @@ -287,7 +286,17 @@ body { position: static; } - > li.more > ul > li > a > span.short-label, { + > li a > span.full-label { + position: static; + padding-left: 30px; + position: static; + } + + > li a > span.short-label { + position: absolute; + } + + > li.more > ul > li > a > span.short-label { position: absolute; } @@ -303,9 +312,9 @@ body { } > li > a > span.full-label { - display: inline; + display: inline-block; text-overflow: ellipsis; - max-width: ~"calc(100% - 50px)"; + max-width: ~"calc(100%)"; overflow: hidden; white-space: nowrap; } diff --git a/frontend/less/espo/layout.less b/frontend/less/espo/layout.less index 3ba54c5597..94de867ef5 100644 --- a/frontend/less/espo/layout.less +++ b/frontend/less/espo/layout.less @@ -18,6 +18,10 @@ padding: 0; width: @logo-width; height: @logo-height; + + img.logo { + max-width: @logo-width; + } } .side-menu-button { @@ -35,15 +39,45 @@ display: none; } - .more-icon { - display: inline; - } - ul.tabs span.short-label { - display: none; - } - ul.tabs span.full-label { - display: inline; + ul.tabs { + + span.full-label { + display: inline; + } + + span.short-label > span { + display: inline-block; + width: 16px; + text-align: center; + } + + .more-icon { + display: inline; + } + + > li.more { + > ul > li > a { + padding-top: 9px; + padding-bottom: 9px; + height: 37px; + + > span.full-label { + padding-left: 30px; + position: static; + } + + > span.short-label { + position: absolute; + } + } + } + + > li span.short-label { + > span.short-label-text { + display: none; + } + } } .navbar-right { @@ -76,6 +110,18 @@ border-color: transparent; } + ul.tabs { + > li a > span.full-label { + position: static; + padding-left: 30px; + position: static; + } + + > li a > span.short-label { + position: absolute; + } + } + input.global-search-input { width: 100%; } @@ -87,7 +133,26 @@ } @media screen and (min-width: @screen-sm-min) { + #navbar .navbar { + + max-height: @navbar-height; + overflow: hidden; + + ul.tabs { + > li > a > span.short-label { + display: block; + float: left; + position: relative; + left: -4px; + margin-right: 4px; + } + + li.more > li > a { + padding-left: 10px; + } + } + .more-icon { top: 1px; position: relative; @@ -108,7 +173,7 @@ border-left-width: @navbar-color-border-width; border-left-style: solid; border-color: transparent; - padding-left: 20px - @navbar-color-border-width; + padding-left: 15px - @navbar-color-border-width; } } } diff --git a/frontend/less/espo/variables.less b/frontend/less/espo/variables.less index cfadfe8196..dc13fca2c1 100644 --- a/frontend/less/espo/variables.less +++ b/frontend/less/espo/variables.less @@ -168,7 +168,7 @@ @modal-title-line-height: floor(@font-size-h4 * @line-height-base); -@navbar-color-border-width: 4px; +@navbar-color-border-width: 0; @container-max-width: 1800px;