Merge branch 'hotfix/5.5.2'
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
]
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
@@ -61,6 +61,10 @@
|
||||
"type": "enum",
|
||||
"notStorable": true,
|
||||
"options": ["None", "Accepted", "Tentative", "Declined"],
|
||||
"style": {
|
||||
"Accepted": "success",
|
||||
"Declined": "danger"
|
||||
},
|
||||
"layoutDetailDisabled": true,
|
||||
"layoutMassUpdateDisabled": true,
|
||||
"where": {
|
||||
|
||||
@@ -65,6 +65,10 @@
|
||||
"type": "enum",
|
||||
"notStorable": true,
|
||||
"options": ["None", "Accepted", "Tentative", "Declined"],
|
||||
"style": {
|
||||
"Accepted": "success",
|
||||
"Declined": "danger"
|
||||
},
|
||||
"layoutDetailDisabled": true,
|
||||
"layoutMassUpdateDisabled": true,
|
||||
"where": {
|
||||
|
||||
@@ -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?",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
{{#unless isInMore}}
|
||||
<li data-name="{{name}}" class="not-in-more tab">
|
||||
<a href="{{link}}" class="nav-link"{{#if color}} style="border-color: {{color}}"{{/if}}>
|
||||
<span class="full-label">{{label}}</span>
|
||||
<span class="short-label" title="{{label}}"{{#if color}} style="color: {{color}}"{{/if}}>
|
||||
{{#if iconClass}}
|
||||
<span class="{{iconClass}}"></span>
|
||||
@@ -24,6 +23,7 @@
|
||||
<span class="short-label-text">{{shortLabel}}</span>
|
||||
{{/if}}
|
||||
</span>
|
||||
<span class="full-label">{{label}}</span>
|
||||
</a>
|
||||
</li>
|
||||
{{/unless}}
|
||||
|
||||
@@ -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 + '>';
|
||||
},
|
||||
|
||||
@@ -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 + '>';
|
||||
},
|
||||
|
||||
@@ -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 + '>';
|
||||
},
|
||||
|
||||
@@ -80,7 +80,9 @@ Espo.define('views/fields/link-multiple-with-role', 'views/fields/link-multiple'
|
||||
role = '';
|
||||
}
|
||||
if (role != '') {
|
||||
roleHtml = '<span class="text-muted small"> » ' +
|
||||
var style = this.getMetadata().get(['entityDefs', this.model.entityType, 'fields', this.roleField, 'style', role]) || 'muted';
|
||||
roleHtml = '<span class="text-muted small"> » </span>' +
|
||||
'<span class="text-'+style+' small">' +
|
||||
this.getHelper().stripTags(this.getLanguage().translateOption(role, this.roleField, this.roleFieldScope)) +
|
||||
'</span>';
|
||||
}
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,9 @@
|
||||
@import "../espo/layout.less";
|
||||
@import "../espo/custom.less";
|
||||
|
||||
|
||||
@import "layout.less";
|
||||
|
||||
@import "custom.less";
|
||||
|
||||
@import "../espo/mixins/side-modal.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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user