diff --git a/application/Espo/Core/Acl/Map/DataBuilder.php b/application/Espo/Core/Acl/Map/DataBuilder.php index 08f6388697..14187fc878 100644 --- a/application/Espo/Core/Acl/Map/DataBuilder.php +++ b/application/Espo/Core/Acl/Map/DataBuilder.php @@ -58,6 +58,9 @@ class DataBuilder public function __construct(private MetadataProvider $metadataProvider, private FieldUtil $fieldUtil) {} + /** + * @return stdClass&object{table: stdClass, fieldTable: stdClass} + */ public function build(Table $table): stdClass { $data = (object) [ diff --git a/application/Espo/Core/Select/Where/ItemGeneralConverter.php b/application/Espo/Core/Select/Where/ItemGeneralConverter.php index eac45bdfff..4ac7af8c46 100644 --- a/application/Espo/Core/Select/Where/ItemGeneralConverter.php +++ b/application/Espo/Core/Select/Where/ItemGeneralConverter.php @@ -128,6 +128,7 @@ class ItemGeneralConverter implements ItemConverter case 'columnLike': case 'columnIn': case 'columnNotIn': + case 'columnIsNull': case 'columnIsNotNull': case 'columnEquals': case 'columnNotEquals': diff --git a/application/Espo/Core/Utils/Client/DevModeExtensionInitJsFileListProvider.php b/application/Espo/Core/Utils/Client/DevModeExtensionInitJsFileListProvider.php new file mode 100644 index 0000000000..a3a0ab625e --- /dev/null +++ b/application/Espo/Core/Utils/Client/DevModeExtensionInitJsFileListProvider.php @@ -0,0 +1,91 @@ +. + * + * The interactive user interfaces in modified source and object code versions + * of this program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU Affero General Public License version 3. + * + * In accordance with Section 7(b) of the GNU Affero General Public License version 3, + * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. + ************************************************************************/ + +namespace Espo\Core\Utils\Client; + +use Espo\Core\Utils\Config; +use Espo\Core\Utils\File\Manager as FileManager; +use Espo\Core\Utils\Module; +use Espo\Core\Utils\Util; + +/** + * Allows bundled extensions to work when the system is in the developer mode. + */ +class DevModeExtensionInitJsFileListProvider +{ + public function __construct( + private Module $module, + private FileManager $fileManager, + private Config $config, + ) {} + + /** + * @return string[] + */ + public function get(): array + { + $developedModule = $this->config->get('developedModule'); + + if (!$developedModule) { + return []; + } + + $output = []; + + foreach ($this->getBundledModuleList() as $module) { + if ($module === $developedModule) { + continue; + } + + $file = "client/custom/modules/$module/lib/init.js"; + + if ($this->fileManager->exists($file)) { + $output[] = $file; + } + } + + return $output; + } + + /** + * @return string[] + */ + private function getBundledModuleList(): array + { + $modules = array_values(array_filter( + $this->module->getList(), + fn ($item) => $this->module->get([$item, 'bundled']) + )); + + return array_map( + fn ($item) => Util::fromCamelCase($item, '-'), + $modules + ); + } +} diff --git a/application/Espo/Core/Utils/ClientManager.php b/application/Espo/Core/Utils/ClientManager.php index 7e92b2cd24..ae7bedca64 100644 --- a/application/Espo/Core/Utils/ClientManager.php +++ b/application/Espo/Core/Utils/ClientManager.php @@ -31,6 +31,7 @@ namespace Espo\Core\Utils; use Espo\Core\Api\Response; use Espo\Core\Api\ResponseWrapper; +use Espo\Core\Utils\Client\DevModeExtensionInitJsFileListProvider; use Espo\Core\Utils\Client\DevModeJsFileListProvider; use Espo\Core\Utils\Client\LoaderParamsProvider; use Espo\Core\Utils\Client\RenderParams; @@ -67,6 +68,7 @@ class ClientManager private Metadata $metadata, private FileManager $fileManager, private DevModeJsFileListProvider $devModeJsFileListProvider, + private DevModeExtensionInitJsFileListProvider $devModeExtensionInitJsFileListProvider, private Module $module, private LoaderParamsProvider $loaderParamsProvider, private SystemConfig $systemConfig, @@ -304,6 +306,7 @@ class ClientManager return array_merge( $this->metadata->get(['app', 'client', 'developerModeScriptList']) ?? [], $this->getDeveloperModeBundleLibFileList(), + $this->devModeExtensionInitJsFileListProvider->get(), ); } diff --git a/client/src/ui/autocomplete.js b/client/src/ui/autocomplete.js index ea3fb38906..9b0a34d5b5 100644 --- a/client/src/ui/autocomplete.js +++ b/client/src/ui/autocomplete.js @@ -93,6 +93,8 @@ class Autocomplete { const $modalBody = this.$element.closest('.modal-body'); + const isModal = !!$modalBody.length; + this.$element.autocomplete({ beforeRender: $container => { if (options.beforeRender) { @@ -110,6 +112,13 @@ class Autocomplete { setTimeout(() => this.$element.autocomplete('hide'), 30); } } + + if (isModal) { + // Fixes dropdown dissapearing when clicking scrollbar. + $container.on('mousedown', e => { + e.preventDefault(); + }); + } }, lookup: lookup, minChars: options.minChars || 0, diff --git a/client/src/views/admin/field-manager/fields/dynamic-logic-conditions.js b/client/src/views/admin/field-manager/fields/dynamic-logic-conditions.js index 0d45852065..301b5e8c32 100644 --- a/client/src/views/admin/field-manager/fields/dynamic-logic-conditions.js +++ b/client/src/views/admin/field-manager/fields/dynamic-logic-conditions.js @@ -29,7 +29,7 @@ import BaseFieldView from 'views/fields/base'; /** - * Important. Used in extensions. + * Important. Extended in extensions. */ export default class extends BaseFieldView { @@ -46,25 +46,23 @@ export default class extends BaseFieldView { setup() { this.addActionHandler('editConditions', () => this.edit()); - this.conditionGroup = Espo.Utils.cloneDeep((this.model.get(this.name) || {}).conditionGroup || []); - this.scope = this.params.scope || this.options.scope; - - this.createStringView(); } - createStringView() { - this.createView('conditionGroup', 'views/admin/dynamic-logic/conditions-string/group-base', { + async prepare() { + this.conditionGroup = Espo.Utils.cloneDeep((this.model.attributes[this.name] || {}).conditionGroup || []); + + return this.createStringView(); + } + + async createStringView() { + return this.createView('conditionGroup', 'views/admin/dynamic-logic/conditions-string/group-base', { selector: '.top-group-string-container', itemData: { value: this.conditionGroup }, operator: 'and', scope: this.scope, - }, view => { - if (this.isRendered()) { - view.render(); - } }); } @@ -75,12 +73,13 @@ export default class extends BaseFieldView { }, view => { view.render(); - this.listenTo(view, 'apply', conditionGroup => { + this.listenTo(view, 'apply', async conditionGroup => { this.conditionGroup = conditionGroup; this.trigger('change'); - this.createStringView(); + await this.createStringView(); + await this.reRender(); }); }); } diff --git a/client/src/views/template/record/edit.js b/client/src/views/template/record/edit.js index b98e9e0a3f..fc3f744943 100644 --- a/client/src/views/template/record/edit.js +++ b/client/src/views/template/record/edit.js @@ -52,9 +52,9 @@ export default class extends EditRecordView { const entityType = this.model.get('entityType'); if (!entityType) { - this.model.set('header', ''); - this.model.set('body', ''); - this.model.set('footer', ''); + this.model.set('header', null); + this.model.set('body', null); + this.model.set('footer', null); this.hideField('variables'); @@ -67,6 +67,7 @@ export default class extends EditRecordView { this.model.set('header', storedData[entityType].header); this.model.set('body', storedData[entityType].body); this.model.set('footer', storedData[entityType].footer); + this.model.set('style', storedData[entityType].style); return; } @@ -74,6 +75,7 @@ export default class extends EditRecordView { let header, body, footer; let sourceType = null; + let style = null; if ( this.getMetadata().get(['entityDefs', 'Template', 'defaultTemplates', entityType]) @@ -102,15 +104,18 @@ export default class extends EditRecordView { footer = this.getMetadata().get( ['entityDefs', 'Template', 'defaultTemplates', sourceType, 'footer'] ); + + style = this.getMetadata().get(['entityDefs', 'Template', 'defaultTemplates', sourceType, 'style']); } - body = body || ''; + body = body || null; header = header || null; footer = footer || null; this.model.set('body', body); this.model.set('header', header); this.model.set('footer', footer); + this.model.set('style', style); }); this.listenTo(this.model, 'change', (e, o) => { @@ -121,7 +126,8 @@ export default class extends EditRecordView { if ( !this.model.hasChanged('header') && !this.model.hasChanged('body') && - !this.model.hasChanged('footer') + !this.model.hasChanged('footer') && + !this.model.hasChanged('style') ) { return; } @@ -136,6 +142,7 @@ export default class extends EditRecordView { header: this.model.get('header'), body: this.model.get('body'), footer: this.model.get('footer'), + style: this.model.get('style'), }; }); }