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;