').attr('id', id).css('minHeight', 40 + 'px');
(0, _jquery.default)(context.layoutInfo.codable).replaceWith($codable);
context.$aceCodable = $codable;
isReplaced = true;
}
$codable.removeClass('hidden');
if ($editor.hasClass('fullscreen')) {
$codable.css('height', $editable.css('height'));
}
requireAce().then(() => {
const html = prepareHtml($editable.html());
aceEditor = ace.edit(id);
aceEditor.setValue(html);
aceEditor.setOptions({
maxLines: !$editor.hasClass('fullscreen') ? 100000 : null,
enableLiveAutocompletion: true,
tabSize: 2,
useSoftTabs: true
});
aceEditor.setOptions({
fontFamily: 'var(--font-family-monospace)'
});
aceEditor.setFontSize('var(--font-size-small)');
aceEditor.container.style.lineHeight = 'var(--line-height-small)';
aceEditor.renderer.updateFontSize();
if (options.isDark) {
aceEditor.setOptions({
theme: 'ace/theme/tomorrow_night'
});
}
aceEditor.getSession().setUseWrapMode(true);
aceEditor.setShowPrintMargin(false);
aceEditor.getSession().setUseWorker(false);
aceEditor.commands.removeCommand('find');
aceEditor.setHighlightActiveLine(false);
aceEditor.focus();
try {
aceEditor.gotoLine(0, 0, false);
} catch (e) {}
aceEditor.on('blur', e => {
context.triggerEvent('blur.codeview', aceEditor.getValue(), e);
});
aceEditor.on('change', () => {
context.triggerEvent('change.codeview', aceEditor.getValue());
});
// noinspection JSValidateTypes
context.aceEditor = aceEditor;
const modeToRequired = options.handlebars ? 'ace/mode/handlebars' : 'ace/mode/html';
const Mode = ace.require(modeToRequired).Mode;
aceEditor.session.setMode(new Mode());
isActivated = true;
isBeingActivated = false;
});
};
context.memo('button.aceCodeview', () => {
return ui.button({
className: 'btn-codeview note-codeview-keep',
contents: '
',
tooltip: lang.options.codeview,
click: () => toggle()
}).render();
});
/**
* @return Promise
*/
const requireAce = function () {
return Espo.loader.requirePromise('lib!ace').then(lib => {
ace = /** window.ace */lib;
const list = [Espo.loader.requirePromise('lib!ace-ext-language_tools')];
list.push(options.handlebars ? Espo.loader.requirePromise('lib!ace-mode-handlebars') : Espo.loader.requirePromise('lib!ace-mode-html'));
if (options.isDark) {
list.push(Espo.loader.requirePromise('lib!ace-theme-tomorrow_night'));
}
return Promise.all(list);
});
};
},
'espoTable': function (context) {
const ui = _jquery.default.summernote.ui;
const options = context.options;
const self = options.espoView;
const lang = options.langInfo;
if (!self) {
return;
}
context.memo('button.espoTable', () => {
return ui.buttonGroup([ui.button({
className: 'dropdown-toggle',
contents: ui.dropdownButtonContents(ui.icon(options.icons.table), options),
tooltip: options.espoTable.tooltip,
data: {
toggle: 'dropdown'
}
}), ui.dropdown({
title: lang.table.table,
className: 'note-table',
items: ['
', '
1 x 1
'].join('')
})], {
callback: $node => {
const $catcher = $node.find('.note-dimension-picker-mousecatcher');
const createTable = (colCount, rowCount, options) => {
const tds = [];
let tdHTML;
for (let idxCol = 0; idxCol < colCount; idxCol++) {
tds.push('
| ');
}
tdHTML = tds.join('\n');
const trs = [];
let trHTML;
for (let idxRow = 0; idxRow < rowCount; idxRow++) {
trs.push('
' + tdHTML + '
');
}
trHTML = trs.join('\n');
const $table = (0, _jquery.default)('
');
/*if (options.tableBorderWidth !== undefined) {
$table.attr('border', options.tableBorderWidth);
}
if (options.tableCellPadding !== undefined) {
$table.attr('cellpadding', options.tableCellPadding);
}*/
$table.css({
width: '100%',
borderCollapse: 'collapse'
//borderSpacing: 0,
});
if (options && options.tableClassName) {
$table.addClass(options.tableClassName);
}
return $table[0];
};
$catcher.css({
width: options.insertTableMaxSize.col + 'em',
height: options.insertTableMaxSize.row + 'em'
}).mousedown(() => {
const $note = context.$note;
const dims = $catcher.data('value').split('x');
const range = $note.summernote('editor.getLastRange').deleteContents();
createTable(dims[0], dims[1], options);
range.insertNode(createTable(dims[0], dims[1], options));
}).on('mousemove', event => {
const PX_PER_EM = 18;
const $picker = (0, _jquery.default)(event.target.parentNode);
const $dimensionDisplay = $picker.next();
const $catcher = $picker.find('.note-dimension-picker-mousecatcher');
const $highlighted = $picker.find('.note-dimension-picker-highlighted');
const $unhighlighted = $picker.find('.note-dimension-picker-unhighlighted');
let posOffset;
if (event.offsetX === undefined) {
const posCatcher = (0, _jquery.default)(event.target).offset();
posOffset = {
x: event.pageX - posCatcher.left,
y: event.pageY - posCatcher.top
};
} else {
posOffset = {
x: event.offsetX,
y: event.offsetY
};
}
const dim = {
c: Math.ceil(posOffset.x / PX_PER_EM) || 1,
r: Math.ceil(posOffset.y / PX_PER_EM) || 1
};
$highlighted.css({
width: dim.c + 'em',
height: dim.r + 'em'
});
$catcher.data('value', dim.c + 'x' + dim.r);
if (dim.c > 3 && dim.c < options.insertTableMaxSize.col) {
$unhighlighted.css({
width: dim.c + 1 + 'em'
});
}
if (dim.r > 3 && dim.r < options.insertTableMaxSize.row) {
$unhighlighted.css({
height: dim.r + 1 + 'em'
});
}
$dimensionDisplay.html(dim.c + ' x ' + dim.r);
});
}
}).render();
});
},
'espoImage': function (context) {
const ui = _jquery.default.summernote.ui;
const options = context.options;
const self = options.espoView;
const lang = options.langInfo;
if (!self) {
return;
}
context.memo('button.espoImage', () => {
const button = ui.button({
contents: options.espoImage.icon,
tooltip: options.espoImage.tooltip,
click() {
context.invoke('espoImage.show');
}
});
return button.render();
});
this.initialize = function () {};
this.destroy = function () {
if (!self) {
return;
}
self.clearView('insertImageDialog');
};
this.show = function () {
self.createView('insertImageDialog', 'views/wysiwyg/modals/insert-image', {
labels: {
insert: lang.image.insert,
url: lang.image.url,
selectFromFiles: lang.image.selectFromFiles
}
}, view => {
view.render();
self.listenToOnce(view, 'upload', target => {
self.$summernote.summernote('insertImagesOrCallback', target);
});
self.listenToOnce(view, 'insert', target => {
self.$summernote.summernote('insertImage', target);
});
self.listenToOnce(view, 'close', () => {
self.clearView('insertImageDialog');
self.fixPopovers();
});
});
};
},
// Not used?
'linkDialog': function (context) {
const options = context.options;
const self = options.espoView;
const lang = options.langInfo;
if (!self) {
return;
}
this.show = function () {
const linkInfo = context.invoke('editor.getLinkInfo');
self.createView('dialogInsertLink', 'views/wysiwyg/modals/insert-link', {
labels: {
insert: lang.link.insert,
openInNewWindow: lang.link.openInNewWindow,
url: lang.link.url,
textToDisplay: lang.link.textToDisplay
},
linkInfo: linkInfo
}, view => {
view.render();
self.listenToOnce(view, 'insert', data => {
data.text = _handlebars.default.Utils.escapeExpression(data.text);
self.$summernote.summernote('createLink', data);
});
self.listenToOnce(view, 'close', () => {
self.clearView('dialogInsertLink');
self.fixPopovers();
});
});
};
},
'espoLink': function (context) {
const ui = _jquery.default.summernote.ui;
const options = context.options;
const self = options.espoView;
const lang = options.langInfo;
if (!self) {
return;
}
const isMacLike = /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform);
context.memo('button.espoLink', function () {
const button = ui.button({
contents: options.espoLink.icon,
tooltip: options.espoLink.tooltip + ' (' + (isMacLike ? 'CMD+K' : 'CTRL+K') + ')',
click() {
context.invoke('espoLink.show');
}
});
return button.render();
});
this.initialize = function () {
this.$modalBody = self.$el.closest('.modal-body');
this.isInModal = this.$modalBody.length > 0;
};
this.destroy = function () {
if (!self) {
return;
}
self.clearView('dialogInsertLink');
};
this.show = function () {
const linkInfo = context.invoke('editor.getLinkInfo');
const container = this.isInModal ? this.$modalBody.get(0) : window;
self.createView('dialogInsertLink', 'views/wysiwyg/modals/insert-link', {
labels: {
insert: lang.link.insert,
openInNewWindow: lang.link.openInNewWindow,
url: lang.link.url,
textToDisplay: lang.link.textToDisplay
},
linkInfo: linkInfo
}, view => {
view.render();
self.listenToOnce(view, 'insert', data => {
const scrollY = 'scrollY' in container ? container.scrollY : container.scrollTop;
data.text = _handlebars.default.Utils.escapeExpression(data.text);
self.$summernote.summernote('createLink', data);
setTimeout(() => container.scroll(0, scrollY), 20);
});
self.listenToOnce(view, 'close', () => {
self.clearView('dialogInsertLink');
self.fixPopovers();
});
});
};
},
'fullscreen': function (context) {
const options = context.options;
const self = options.espoView;
if (!self) {
return;
}
this.$window = (0, _jquery.default)(window);
this.$scrollbar = (0, _jquery.default)('html, body');
this.initialize = function () {
this.$editor = context.layoutInfo.editor;
this.$toolbar = context.layoutInfo.toolbar;
this.$editable = context.layoutInfo.editable;
this.$modal = self.$el.closest('.modal');
this.isInModal = this.$modal.length > 0;
};
this.resizeTo = function (size) {
this.$editable.css('height', size.h);
if (context.$aceCodable) {
context.$aceCodable.css('height', size.h);
}
};
this.onResize = function () {
this.resizeTo({
h: this.$window.height() - this.$toolbar.outerHeight()
});
};
this.isFullscreen = function () {
return this.$editor.hasClass('fullscreen');
};
this.destroy = function () {
this.$window.off('resize.summernote' + self.cid);
if (this.isInModal) {
this.$modal.css('overflow-y', '');
} else {
this.$scrollbar.css('overflow', '');
}
};
let maxLines;
this.toggle = function () {
this.$editor.toggleClass('fullscreen');
const aceEditor = /** @type {import('ace-builds').Ace.Editor} */context.aceEditor;
if (this.isFullscreen()) {
this.$editable.data('orgHeight', this.$editable.css('height'));
this.$editable.data('orgMaxHeight', this.$editable.css('maxHeight'));
this.$editable.css('maxHeight', '');
this.$window.on('resize.summernote' + self.cid, this.onResize.bind(this)).trigger('resize');
if (this.isInModal) {
this.$modal.css('overflow-y', 'hidden');
} else {
this.$scrollbar.css('overflow', 'hidden');
}
// noinspection JSUnusedGlobalSymbols
this._isFullscreen = true;
if (aceEditor) {
maxLines = aceEditor.getOption('maxLines');
aceEditor.setOptions({
maxLines: null
});
aceEditor.resize();
}
} else {
this.$window.off('resize.summernote' + self.cid);
this.resizeTo({
h: this.$editable.data('orgHeight')
});
if (context.$aceCodable) {
context.$aceCodable.css('height', '');
}
this.$editable.css('maxHeight', this.$editable.css('orgMaxHeight'));
this.$editable.css('height', '');
if (this.isInModal) {
this.$modal.css('overflow-y', '');
} else {
this.$scrollbar.css('overflow', '');
}
// noinspection JSUnusedGlobalSymbols
this._isFullscreen = false;
if (aceEditor) {
aceEditor.setOptions({
maxLines: 100000
});
aceEditor.resize();
}
}
context.invoke('toolbar.updateFullscreen', this.isFullscreen());
};
}
});
}
});
define("controller", ["exports", "exceptions", "bullbone", "di", "helpers/site/modal-bar-provider"], function (_exports, _exceptions, _bullbone, _di, _modalBarProvider) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
_exceptions = _interopRequireDefault(_exceptions);
_modalBarProvider = _interopRequireDefault(_modalBarProvider);
var _staticBlock;
let _init_modalBarProvider, _init_extra_modalBarProvider;
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see
.
*
* 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.
************************************************************************/
/** @module controller */
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _applyDecs(e, t, n, r, o, i) { var a, c, u, s, f, l, p, d = Symbol.metadata || Symbol.for("Symbol.metadata"), m = Object.defineProperty, h = Object.create, y = [h(null), h(null)], v = t.length; function g(t, n, r) { return function (o, i) { n && (i = o, o = e); for (var a = 0; a < t.length; a++) i = t[a].apply(o, r ? [i] : []); return r ? i : o; }; } function b(e, t, n, r) { if ("function" != typeof e && (r || void 0 !== e)) throw new TypeError(t + " must " + (n || "be") + " a function" + (r ? "" : " or undefined")); return e; } function applyDec(e, t, n, r, o, i, u, s, f, l, p) { function d(e) { if (!p(e)) throw new TypeError("Attempted to access private element on non-instance"); } var h = [].concat(t[0]), v = t[3], w = !u, D = 1 === o, S = 3 === o, j = 4 === o, E = 2 === o; function I(t, n, r) { return function (o, i) { return n && (i = o, o = e), r && r(o), P[t].call(o, i); }; } if (!w) { var P = {}, k = [], F = S ? "get" : j || D ? "set" : "value"; if (f ? (l || D ? P = { get: _setFunctionName(function () { return v(this); }, r, "get"), set: function (e) { t[4](this, e); } } : P[F] = v, l || _setFunctionName(P[F], r, E ? "" : F)) : l || (P = Object.getOwnPropertyDescriptor(e, r)), !l && !f) { if ((c = y[+s][r]) && 7 !== (c ^ o)) throw Error("Decorating two elements with the same name (" + P[F].name + ") is not supported yet"); y[+s][r] = o < 3 ? 1 : o; } } for (var N = e, O = h.length - 1; O >= 0; O -= n ? 2 : 1) { var T = b(h[O], "A decorator", "be", !0), z = n ? h[O - 1] : void 0, A = {}, H = { kind: ["field", "accessor", "method", "getter", "setter", "class"][o], name: r, metadata: a, addInitializer: function (e, t) { if (e.v) throw new TypeError("attempted to call addInitializer after decoration was finished"); b(t, "An initializer", "be", !0), i.push(t); }.bind(null, A) }; if (w) c = T.call(z, N, H), A.v = 1, b(c, "class decorators", "return") && (N = c);else if (H.static = s, H.private = f, c = H.access = { has: f ? p.bind() : function (e) { return r in e; } }, j || (c.get = f ? E ? function (e) { return d(e), P.value; } : I("get", 0, d) : function (e) { return e[r]; }), E || S || (c.set = f ? I("set", 0, d) : function (e, t) { e[r] = t; }), N = T.call(z, D ? { get: P.get, set: P.set } : P[F], H), A.v = 1, D) { if ("object" == typeof N && N) (c = b(N.get, "accessor.get")) && (P.get = c), (c = b(N.set, "accessor.set")) && (P.set = c), (c = b(N.init, "accessor.init")) && k.unshift(c);else if (void 0 !== N) throw new TypeError("accessor decorators must return an object with get, set, or init properties or undefined"); } else b(N, (l ? "field" : "method") + " decorators", "return") && (l ? k.unshift(N) : P[F] = N); } return o < 2 && u.push(g(k, s, 1), g(i, s, 0)), l || w || (f ? D ? u.splice(-1, 0, I("get", s), I("set", s)) : u.push(E ? P[F] : b.call.bind(P[F])) : m(e, r, P)), N; } function w(e) { return m(e, d, { configurable: !0, enumerable: !0, value: a }); } return void 0 !== i && (a = i[d]), a = h(null == a ? null : a), f = [], l = function (e) { e && f.push(g(e)); }, p = function (t, r) { for (var i = 0; i < n.length; i++) { var a = n[i], c = a[1], l = 7 & c; if ((8 & c) == t && !l == r) { var p = a[2], d = !!a[3], m = 16 & c; applyDec(t ? e : e.prototype, a, m, d ? "#" + p : _toPropertyKey(p), l, l < 2 ? [] : t ? s = s || [] : u = u || [], f, !!t, d, r, t && d ? function (t) { return _checkInRHS(t) === e; } : o); } } }, p(8, 0), p(0, 0), p(8, 1), p(0, 1), l(u), l(s), c = f, v || w(e), { e: c, get c() { var n = []; return v && [w(e = applyDec(e, [t], r, e.name, 5, n)), g(n, 1)]; } }; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
function _setFunctionName(e, t, n) { "symbol" == typeof t && (t = (t = t.description) ? "[" + t + "]" : ""); try { Object.defineProperty(e, "name", { configurable: !0, value: n ? n + " " + t : t }); } catch (e) {} return e; }
function _checkInRHS(e) { if (Object(e) !== e) throw TypeError("right-hand side of 'in' should be an object, got " + (null !== e ? typeof e : "null")); return e; }
/**
* @callback module:controller~viewCallback
* @param {module:view} view A view.
*/
/**
* @callback module:controller~masterViewCallback
* @param {module:views/site/master} view A master view.
*/
/**
* A controller. To be extended.
*
* @mixes Bull.Events
*/
class Controller {
/**
* @internal
* @param {Object.
} params
* @param {Object} injections
*/
constructor(params, injections) {
_init_extra_modalBarProvider(this);
this.params = params || {};
/** @type {module:controllers/base} */
this.baseController = injections.baseController;
/** @type {Bull.Factory} */
this.viewFactory = injections.viewFactory;
/** @type {module:model} */
this.modelFactory = injections.modelFactory;
/** @type {module:collection-factory} */
this.collectionFactory = injections.collectionFactory;
this._settings = injections.settings || null;
this._user = injections.user || null;
this._preferences = injections.preferences || null;
this._acl = injections.acl || null;
this._cache = injections.cache || null;
this._router = injections.router || null;
this._storage = injections.storage || null;
this._metadata = injections.metadata || null;
this._dateTime = injections.dateTime || null;
this._broadcastChannel = injections.broadcastChannel || null;
this.setMasterRendered(false);
}
/**
* A default action.
*
* @type {string}
*/
defaultAction = 'index';
/**
* A name.
*
* @type {string|null}
*/
name = null;
/**
* Params.
*
* @type {Object}
* @private
*/
params = null;
/**
* A view factory.
*
* @type {Bull.Factory}
* @protected
*/
viewFactory = null;
/**
* A model factory.
*
* @type {module:model-factory}
* @protected
*/
modelFactory = null;
/**
* A body view.
*
* @public
* @type {string|null}
*/
masterView = null;
/**
* @private
* @type {ModalBarProvider}
*/
modalBarProvider = _init_modalBarProvider(this);
/**
* Set the router.
*
* @internal
* @param {module:router} router
*/
setRouter(router) {
this._router = router;
this.trigger('router-set', router);
}
/**
* @protected
* @returns {module:models/settings}
*/
getConfig() {
return this._settings;
}
/**
* @protected
* @returns {module:models/user}
*/
getUser() {
return this._user;
}
/**
* @protected
* @returns {module:models/preferences}
*/
getPreferences() {
return this._preferences;
}
/**
* @protected
* @returns {module:acl-manager}
*/
getAcl() {
return this._acl;
}
/**
* @protected
* @returns {module:cache}
*/
getCache() {
return this._cache;
}
/**
* @protected
* @returns {module:router}
*/
getRouter() {
return this._router;
}
/**
* @protected
* @returns {module:storage}
*/
getStorage() {
return this._storage;
}
/**
* @protected
* @returns {module:metadata}
*/
getMetadata() {
return this._metadata;
}
/**
* @protected
* @returns {module:date-time}
*/
getDateTime() {
return this._dateTime;
}
/**
* Get a parameter of all controllers.
*
* @param {string} key A key.
* @return {*} Null if a key doesn't exist.
*/
get(key) {
if (key in this.params) {
return this.params[key];
}
return null;
}
/**
* Set a parameter for all controllers.
*
* @param {string} key A name of a view.
* @param {*} value
*/
set(key, value) {
this.params[key] = value;
}
/**
* Unset a parameter.
*
* @param {string} key A key.
*/
unset(key) {
delete this.params[key];
}
/**
* Has a parameter.
*
* @param {string} key A key.
* @returns {boolean}
*/
has(key) {
return key in this.params;
}
/**
* @param {string} key
* @param {string} [name]
* @return {string}
* @private
*/
_composeScrollKey(key, name) {
name = name || this.name;
return `scrollTop-${name}-${key}`;
}
/**
* @param {string} key
* @return {string}
* @private
*/
_composeMainViewKey(key) {
return `mainView-${this.name}-${key}`;
}
/**
* Get a stored main view.
*
* @param {string} key A key.
* @returns {module:view|null}
*/
getStoredMainView(key) {
return this.get(this._composeMainViewKey(key));
}
/**
* Has a stored main view.
* @param {string} key
* @returns {boolean}
*/
hasStoredMainView(key) {
return this.has(this._composeMainViewKey(key));
}
/**
* Clear a stored main view.
* @param {string} key
*/
clearStoredMainView(key) {
const view = this.getStoredMainView(key);
if (view) {
view.remove(true);
}
this.unset(this._composeScrollKey(key));
this.unset(this._composeMainViewKey(key));
}
/**
* Store a main view.
*
* @param {string} key A key.
* @param {module:view} view A view.
*/
storeMainView(key, view) {
this.set(this._composeMainViewKey(key), view);
this.listenTo(view, 'remove', o => {
o = o || {};
if (o.ignoreCleaning) {
return;
}
this.stopListening(view, 'remove');
this.clearStoredMainView(key);
});
}
/**
* Check access to an action.
*
* @param {string} action An action.
* @returns {boolean}
*/
checkAccess(action) {
return true;
}
/**
* Process access check to the controller.
*/
handleAccessGlobal() {
if (!this.checkAccessGlobal()) {
throw new _exceptions.default.AccessDenied("Denied access to '" + this.name + "'");
}
}
/**
* Check access to the controller.
*
* @returns {boolean}
*/
checkAccessGlobal() {
return true;
}
/**
* Check access to an action. Throwing an exception.
*
* @param {string} action An action.
*/
handleCheckAccess(action) {
if (this.checkAccess(action)) {
return;
}
const msg = action ? "Denied access to action '" + this.name + "#" + action + "'" : "Denied access to scope '" + this.name + "'";
throw new _exceptions.default.AccessDenied(msg);
}
/**
* Process an action.
*
* @param {string} action
* @param {Object} options
*/
doAction(action, options) {
this.handleAccessGlobal();
action = action || this.defaultAction;
const method = 'action' + Espo.Utils.upperCaseFirst(action);
if (!(method in this)) {
throw new _exceptions.default.NotFound("Action '" + this.name + "#" + action + "' is not found");
}
const preMethod = 'before' + Espo.Utils.upperCaseFirst(action);
const postMethod = 'after' + Espo.Utils.upperCaseFirst(action);
if (preMethod in this) {
this[preMethod].call(this, options || {});
}
this[method].call(this, options || {});
if (postMethod in this) {
this[postMethod].call(this, options || {});
}
}
/**
* Serve a master view. Render if not already rendered.
*
* @param {module:controller~masterViewCallback} callback A callback with a created master view.
* @private
*/
master(callback) {
const entireView = this.getEntireView();
if (entireView) {
entireView.remove();
this.setEntireView(null);
}
const masterView = this.getMasterView();
if (masterView) {
callback.call(this, masterView);
return;
}
const viewName = this.masterView || 'views/site/master';
this.viewFactory.create(viewName, {
fullSelector: 'body'
}, async (/** import('views/site/master').default */masterView) => {
this.setMasterView(masterView);
if (this.isMasterRendered()) {
callback.call(this, masterView);
return;
}
this.modalBarProvider.set(masterView.collapsedModalBarView || null);
await masterView.render();
this.setMasterRendered(true);
callback.call(this, masterView);
});
}
/**
* @private
* @return {import('view').default|null}
*/
getEntireView() {
return this.get('entire');
}
/**
* @private
* @param {import('view').default|null} view
*/
setEntireView(view) {
this.set('entire', view);
}
/**
* @private
* @return {import('view').default|null}
*/
getMasterView() {
return this.get('master');
}
/**
* @private
* @param {import('view').default|null} view
*/
setMasterView(view) {
if (!view) {
this.modalBarProvider.set(null);
}
this.set('master', view);
}
/**
* @private
* @param {boolean} value
*/
setMasterRendered(value) {
this.set('masterRendered', value);
}
/**
* @private
* @return {boolean}
*/
isMasterRendered() {
return !!this.get('masterRendered');
}
/**
* @param {import('views/site/master').default} masterView
* @private
*/
_unchainMainView(masterView) {
if (!masterView.currentViewKey /*||
!this.hasStoredMainView(masterView.currentViewKey)*/) {
return;
}
const currentMainView = masterView.getMainView();
if (!currentMainView) {
return;
}
currentMainView.propagateEvent('remove', {
ignoreCleaning: true
});
masterView.unchainView('main');
}
/**
* @typedef {Object} module:controller~mainParams
* @property {boolean} [useStored] Use a stored view if available.
* @property {string} [key] A stored view key.
*/
/**
* Create a main view in the master container and render it.
*
* @param {string|module:view} [view] A view name or view instance.
* @param {Object.} [options] Options for a view.
* @param {module:controller~viewCallback} [callback] A callback with a created view.
* @param {module:controller~mainParams} [params] Parameters.
*/
main(view, options, callback) {
let params = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
const dto = {
isCanceled: false,
key: params.key,
useStored: params.useStored,
callback: callback
};
const selector = '#main';
const useStored = params.useStored || false;
const key = params.key;
this.listenToOnce(this.baseController, 'action', () => dto.isCanceled = true);
const mainView = view && typeof view === 'object' ? view : undefined;
const viewName = !mainView ? view || 'views/base' : undefined;
this.master(async masterView => {
if (dto.isCanceled) {
return;
}
options = options || {};
options.fullSelector = selector;
if (useStored && this.hasStoredMainView(key)) {
const mainView = this.getStoredMainView(key);
let isActual = true;
if (mainView && 'isActualForReuse' in mainView && typeof mainView.isActualForReuse === 'function') {
isActual = mainView.isActualForReuse();
}
const lastUrl = mainView && 'lastUrl' in mainView ? mainView.lastUrl : null;
if (isActual && (!lastUrl || lastUrl === this.getRouter().getCurrentUrl())) {
this._processMain(mainView, masterView, dto);
if ('setupReuse' in mainView && typeof mainView.setupReuse === 'function') {
mainView.setupReuse(options.params || {});
}
return;
}
this.clearStoredMainView(key);
}
if (mainView) {
this._unchainMainView(masterView);
await masterView.assignView('main', mainView, selector);
dto.isSet = true;
this._processMain(view, masterView, dto);
return;
}
this.viewFactory.create(viewName, options, view => {
this._processMain(view, masterView, dto);
});
});
}
/**
* @param {import('view').default} mainView
* @param {import('views/site/master').default} masterView
* @param {{
* isCanceled: boolean,
* key?: string,
* useStored?: boolean,
* callback?: module:controller~viewCallback,
* isSet?: boolean,
* }} dto Data.
* @private
*/
_processMain(mainView, masterView, dto) {
if (dto.isCanceled) {
return;
}
const key = dto.key;
if (key) {
this.storeMainView(key, mainView);
}
const onAction = () => {
mainView.cancelRender();
dto.isCanceled = true;
};
mainView.listenToOnce(this.baseController, 'action', onAction);
if (masterView.currentViewKey) {
const scrollKey = this._composeScrollKey(masterView.currentViewKey, masterView.currentName);
this.set(scrollKey, window.scrollY);
if (!dto.isSet) {
this._unchainMainView(masterView);
}
}
masterView.currentViewKey = key;
masterView.currentName = this.name;
if (!dto.isSet) {
masterView.setView('main', mainView);
}
const afterRender = () => {
setTimeout(() => mainView.stopListening(this.baseController, 'action', onAction), 500);
mainView.updatePageTitle();
const scrollKey = this._composeScrollKey(key);
if (dto.useStored && this.has(scrollKey)) {
window.scrollTo({
top: this.get(scrollKey)
});
return;
}
window.scrollTo({
top: 0
});
};
if (dto.callback) {
this.listenToOnce(mainView, 'after:render', afterRender);
dto.callback.call(this, mainView);
return;
}
mainView.render().then(afterRender);
}
/**
* Show a loading notify-message.
*/
showLoadingNotification() {
const master = this.getMasterView();
if (!master) {
return;
}
master.showLoadingNotification();
}
/**
* Hide a loading notify-message.
*/
hideLoadingNotification() {
const master = this.getMasterView();
if (!master) {
return;
}
master.hideLoadingNotification();
}
/**
* Create a view in the BODY element. Use for rendering separate pages without the default navbar and footer.
* If a callback is not passed, the view will be automatically rendered.
*
* @param {string|module:view} view A view name or view instance.
* @param {Object.} [options] Options for a view.
* @param {module:controller~viewCallback} [callback] A callback with a created view.
*/
entire(view, options, callback) {
const masterView = this.getMasterView();
if (masterView) {
masterView.remove();
}
this.setMasterView(null);
this.setMasterRendered(false);
if (typeof view === 'object') {
view.setElement('body');
this.viewFactory.prepare(view, () => {
if (!callback) {
view.render();
return;
}
callback(view);
});
return;
}
options = options || {};
options.fullSelector = 'body';
this.viewFactory.create(view, options, view => {
this.setEntireView(view);
if (!callback) {
view.render();
return;
}
callback(view);
});
}
static #_ = _staticBlock = () => [_init_modalBarProvider, _init_extra_modalBarProvider] = _applyDecs(this, [], [[(0, _di.inject)(_modalBarProvider.default), 0, "modalBarProvider"]]).e;
}
_staticBlock();
Object.assign(Controller.prototype, _bullbone.Events);
/** For backward compatibility. */
Controller.extend = _bullbone.View.extend;
var _default = _exports.default = Controller;
});
define("views/main", ["exports", "view", "di", "helpers/site/shortcut-manager"], function (_exports, _view, _di, _shortcutManager) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
_view = _interopRequireDefault(_view);
_shortcutManager = _interopRequireDefault(_shortcutManager);
var _staticBlock;
let _init_shortcutManager, _init_extra_shortcutManager;
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* 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.
************************************************************************/
/** @module views/main */
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _applyDecs(e, t, n, r, o, i) { var a, c, u, s, f, l, p, d = Symbol.metadata || Symbol.for("Symbol.metadata"), m = Object.defineProperty, h = Object.create, y = [h(null), h(null)], v = t.length; function g(t, n, r) { return function (o, i) { n && (i = o, o = e); for (var a = 0; a < t.length; a++) i = t[a].apply(o, r ? [i] : []); return r ? i : o; }; } function b(e, t, n, r) { if ("function" != typeof e && (r || void 0 !== e)) throw new TypeError(t + " must " + (n || "be") + " a function" + (r ? "" : " or undefined")); return e; } function applyDec(e, t, n, r, o, i, u, s, f, l, p) { function d(e) { if (!p(e)) throw new TypeError("Attempted to access private element on non-instance"); } var h = [].concat(t[0]), v = t[3], w = !u, D = 1 === o, S = 3 === o, j = 4 === o, E = 2 === o; function I(t, n, r) { return function (o, i) { return n && (i = o, o = e), r && r(o), P[t].call(o, i); }; } if (!w) { var P = {}, k = [], F = S ? "get" : j || D ? "set" : "value"; if (f ? (l || D ? P = { get: _setFunctionName(function () { return v(this); }, r, "get"), set: function (e) { t[4](this, e); } } : P[F] = v, l || _setFunctionName(P[F], r, E ? "" : F)) : l || (P = Object.getOwnPropertyDescriptor(e, r)), !l && !f) { if ((c = y[+s][r]) && 7 !== (c ^ o)) throw Error("Decorating two elements with the same name (" + P[F].name + ") is not supported yet"); y[+s][r] = o < 3 ? 1 : o; } } for (var N = e, O = h.length - 1; O >= 0; O -= n ? 2 : 1) { var T = b(h[O], "A decorator", "be", !0), z = n ? h[O - 1] : void 0, A = {}, H = { kind: ["field", "accessor", "method", "getter", "setter", "class"][o], name: r, metadata: a, addInitializer: function (e, t) { if (e.v) throw new TypeError("attempted to call addInitializer after decoration was finished"); b(t, "An initializer", "be", !0), i.push(t); }.bind(null, A) }; if (w) c = T.call(z, N, H), A.v = 1, b(c, "class decorators", "return") && (N = c);else if (H.static = s, H.private = f, c = H.access = { has: f ? p.bind() : function (e) { return r in e; } }, j || (c.get = f ? E ? function (e) { return d(e), P.value; } : I("get", 0, d) : function (e) { return e[r]; }), E || S || (c.set = f ? I("set", 0, d) : function (e, t) { e[r] = t; }), N = T.call(z, D ? { get: P.get, set: P.set } : P[F], H), A.v = 1, D) { if ("object" == typeof N && N) (c = b(N.get, "accessor.get")) && (P.get = c), (c = b(N.set, "accessor.set")) && (P.set = c), (c = b(N.init, "accessor.init")) && k.unshift(c);else if (void 0 !== N) throw new TypeError("accessor decorators must return an object with get, set, or init properties or undefined"); } else b(N, (l ? "field" : "method") + " decorators", "return") && (l ? k.unshift(N) : P[F] = N); } return o < 2 && u.push(g(k, s, 1), g(i, s, 0)), l || w || (f ? D ? u.splice(-1, 0, I("get", s), I("set", s)) : u.push(E ? P[F] : b.call.bind(P[F])) : m(e, r, P)), N; } function w(e) { return m(e, d, { configurable: !0, enumerable: !0, value: a }); } return void 0 !== i && (a = i[d]), a = h(null == a ? null : a), f = [], l = function (e) { e && f.push(g(e)); }, p = function (t, r) { for (var i = 0; i < n.length; i++) { var a = n[i], c = a[1], l = 7 & c; if ((8 & c) == t && !l == r) { var p = a[2], d = !!a[3], m = 16 & c; applyDec(t ? e : e.prototype, a, m, d ? "#" + p : _toPropertyKey(p), l, l < 2 ? [] : t ? s = s || [] : u = u || [], f, !!t, d, r, t && d ? function (t) { return _checkInRHS(t) === e; } : o); } } }, p(8, 0), p(0, 0), p(8, 1), p(0, 1), l(u), l(s), c = f, v || w(e), { e: c, get c() { var n = []; return v && [w(e = applyDec(e, [t], r, e.name, 5, n)), g(n, 1)]; } }; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
function _setFunctionName(e, t, n) { "symbol" == typeof t && (t = (t = t.description) ? "[" + t + "]" : ""); try { Object.defineProperty(e, "name", { configurable: !0, value: n ? n + " " + t : t }); } catch (e) {} return e; }
function _checkInRHS(e) { if (Object(e) !== e) throw TypeError("right-hand side of 'in' should be an object, got " + (null !== e ? typeof e : "null")); return e; }
/**
* A base main view. The detail, edit, list views to be extended from.
*/
class MainView extends _view.default {
/**
* A scope name.
*
* @type {string} scope
*/
scope = '';
/**
* A name.
*
* @type {string} name
*/
name = '';
/**
* A top-right menu item (button or dropdown action).
* Handled by a class method `action{Action}`, a click handler or a handler class.
*
* @typedef {Object} module:views/main~MenuItem
*
* @property {string} [name] A name.
* @property {string} [action] An action.
* @property {string} [link] A link.
* @property {string} [label] A translatable label.
* @property {string} [labelTranslation] A label translation path.
* @property {'default'|'danger'|'success'|'warning'} [style] A style. Only for buttons.
* @property {boolean} [hidden] Hidden.
* @property {boolean} [disabled] Disabled.
* @property {Object.} [data] Data attribute values.
* @property {string} [title] A title.
* @property {string} [iconHtml] An icon HTML.
* @property {string} [iconClass] An icon class.
* @property {string} [html] An HTML.
* @property {string} [text] A text.
* @property {string} [className] An additional class name. Only for buttons.
* @property {'create'|'read'|'edit'|'stream'|'delete'} [acl] Access to a record (or a scope if `aclScope` specified)
* required for a menu item.
* @property {string} [aclScope] A scope to check access to with the `acl` parameter.
* @property {string} [configCheck] A config parameter defining a menu item availability.
* If starts with `!`, then the result is negated.
* @property {module:utils~AccessDefs[]} [accessDataList] Access definitions.
* @property {string} [handler] A handler.
* @property {string} [initFunction] An init method in the handler.
* @property {string} [actionFunction] An action method in the handler.
* @property {string} [checkVisibilityFunction] A method in the handler that determine whether an item is available.
* @property {function()} [onClick] A click handler.
*/
/**
* Top-right menu definitions.
*
* @type {{
* buttons: module:views/main~MenuItem[],
* dropdown: module:views/main~MenuItem[],
* actions: module:views/main~MenuItem[],
* }} menu
* @private
* @internal
*/
menu = {};
/**
* @private
* @type {JQuery|null}
*/
$headerActionsContainer = null;
/**
* A shortcut-key => action map.
*
* @protected
* @type {?Object.}
*/
shortcutKeys = null;
/**
* @private
* @type {ShortcutManager}
*/
shortcutManager = _init_shortcutManager(this);
/** @inheritDoc */
events = (_init_extra_shortcutManager(this), {
/** @this MainView */
'click .action': function (e) {
Espo.Utils.handleAction(this, e.originalEvent, e.currentTarget, {
actionItems: [...this.menu.buttons, ...this.menu.dropdown],
className: 'main-header-manu-action'
});
}
});
lastUrl;
/** @inheritDoc */
init() {
this.scope = this.options.scope || this.scope;
this.menu = {};
this.options.params = this.options.params || {};
if (this.name && this.scope) {
const key = this.name.charAt(0).toLowerCase() + this.name.slice(1);
this.menu = this.getMetadata().get(['clientDefs', this.scope, 'menu', key]) || {};
}
/**
* @private
* @type {string[]}
*/
this.headerActionItemTypeList = ['buttons', 'dropdown', 'actions'];
this.menu = Espo.Utils.cloneDeep(this.menu);
let globalMenu = {};
if (this.name) {
globalMenu = Espo.Utils.cloneDeep(this.getMetadata().get(['clientDefs', 'Global', 'menu', this.name.charAt(0).toLowerCase() + this.name.slice(1)]) || {});
}
this._reRenderHeaderOnSync = false;
this._menuHandlers = {};
this.headerActionItemTypeList.forEach(type => {
this.menu[type] = this.menu[type] || [];
this.menu[type] = this.menu[type].concat(globalMenu[type] || []);
const itemList = this.menu[type];
itemList.forEach(item => {
const viewObject = this;
// @todo Set _reRenderHeaderOnSync to true if `acl` is set `ascScope` is not set?
// Set _reRenderHeaderOnSync in `addMenuItem` method.
if ((item.initFunction || item.checkVisibilityFunction) && (item.handler || item.data && item.data.handler)) {
this.wait(new Promise(resolve => {
const handler = item.handler || item.data.handler;
Espo.loader.require(handler, Handler => {
const handler = new Handler(viewObject);
const name = item.name || item.action;
if (name) {
this._menuHandlers[name] = handler;
}
if (item.initFunction) {
handler[item.initFunction].call(handler);
}
if (item.checkVisibilityFunction && this.model) {
this._reRenderHeaderOnSync = true;
}
resolve();
});
}));
}
});
});
if (this.model) {
this.whenReady().then(() => {
if (!this._reRenderHeaderOnSync) {
return;
}
this.listenTo(this.model, 'sync', () => {
if (!this.getHeaderView()) {
return;
}
this.getHeaderView().reRender();
});
});
}
this.updateLastUrl();
this.on('after:render-internal', () => {
this.$headerActionsContainer = this.$el.find('.page-header .header-buttons');
});
this.on('header-rendered', () => {
this.$headerActionsContainer = this.$el.find('.page-header .header-buttons');
this.adjustButtons();
});
this.on('after:render', () => this.adjustButtons());
if (this.shortcutKeys) {
this.shortcutKeys = Espo.Utils.cloneDeep(this.shortcutKeys);
}
}
/**
* @private
*/
initShortcuts() {
if (!this.shortcutKeys) {
return;
}
this.shortcutManager.add(this, this.shortcutKeys);
this.once('remove', () => {
this.shortcutManager.remove(this);
});
}
setupFinal() {
this.initShortcuts();
}
/**
* Update a last history URL.
*/
updateLastUrl() {
this.lastUrl = this.getRouter().getCurrentUrl();
}
/**
* @internal
* @returns {{
* buttons?: module:views/main~MenuItem[],
* dropdown?: module:views/main~MenuItem[],
* actions?: module:views/main~MenuItem[],
* }}
*/
getMenu() {
if (this.menuDisabled || !this.menu) {
return {};
}
const menu = {};
this.headerActionItemTypeList.forEach(type => {
(this.menu[type] || []).forEach(item => {
if (item === false) {
menu[type].push(false);
return;
}
item = Espo.Utils.clone(item);
menu[type] = menu[type] || [];
if (!Espo.Utils.checkActionAvailability(this.getHelper(), item)) {
return;
}
if (!Espo.Utils.checkActionAccess(this.getAcl(), this.model || this.scope, item)) {
return;
}
if (item.accessDataList) {
if (!Espo.Utils.checkAccessDataList(item.accessDataList, this.getAcl(), this.getUser())) {
return;
}
}
item.name = item.name || item.action;
item.action = item.action || null;
if (this._menuHandlers[item.name] && item.checkVisibilityFunction) {
const handler = this._menuHandlers[item.name];
if (!handler[item.checkVisibilityFunction](item.name)) {
return;
}
}
if (item.labelTranslation) {
item.html = this.getHelper().escapeString(this.getLanguage().translatePath(item.labelTranslation));
}
menu[type].push(item);
});
});
return menu;
}
/**
* Get a header HTML. To be overridden.
*
* @returns {string} HTML.
*/
getHeader() {
return '';
}
/**
* Build a header HTML. To be called from the #getHeader method.
* Beware of XSS.
*
* @param {(string|Element|JQuery)[]} itemList A breadcrumb path. Like: Account > Name > edit.
* @returns {string} HTML
*/
buildHeaderHtml(itemList) {
const $itemList = itemList.map(item => {
return $('').addClass('breadcrumb-item').append(item);
});
const $div = $('
').addClass('header-breadcrumbs');
$itemList.forEach(($item, i) => {
$div.append($item);
if (i === $itemList.length - 1) {
return;
}
$div.append($('
').addClass('breadcrumb-separator').append($('
')));
});
return $div.get(0).outerHTML;
}
/**
* Get an icon HTML.
*
* @returns {string} HTML
*/
getHeaderIconHtml() {
return this.getHelper().getScopeColorIconHtml(this.scope);
}
// noinspection JSUnusedGlobalSymbols
/**
* Action 'showModal'.
*
* @todo Revise. To be removed?
*
* @param {Object} data
*/
actionShowModal(data) {
const view = data.view;
if (!view) {
return;
}
this.createView('modal', view, {
model: this.model,
collection: this.collection
}, view => {
view.render();
this.listenTo(view, 'after:save', () => {
if (this.model) {
this.model.fetch();
}
if (this.collection) {
this.collection.fetch();
}
});
});
}
/**
* Update a menu item.
*
* @param {string} name An item name.
* @param {module:views/main~MenuItem} item New item definitions to write.
* @param {boolean} [doNotReRender=false] Skip re-render.
*
* @since 8.2.0
*/
updateMenuItem(name, item, doNotReRender) {
const actionItem = this._getHeaderActionItem(name);
if (!actionItem) {
return;
}
for (const key in item) {
actionItem[key] = item[key];
}
if (doNotReRender) {
return;
}
if (this.isRendered()) {
this.getHeaderView().reRender();
return;
}
if (this.isBeingRendered()) {
this.whenRendered().then(() => {
this.getHeaderView().reRender();
});
}
}
/**
* Add a menu item.
*
* @param {'buttons'|'dropdown'} type A type.
* @param {module:views/main~MenuItem|false} item Item definitions.
* @param {boolean} [toBeginning=false] To beginning.
* @param {boolean} [doNotReRender=false] Skip re-render.
*/
addMenuItem(type, item, toBeginning, doNotReRender) {
if (item) {
item.name = item.name || item.action || Espo.Utils.generateId();
const name = item.name;
let index = -1;
this.menu[type].forEach((data, i) => {
data = data || {};
if (data.name === name) {
index = i;
}
});
if (~index) {
this.menu[type].splice(index, 1);
}
}
let method = 'push';
if (toBeginning) {
method = 'unshift';
}
this.menu[type][method](item);
if (!doNotReRender && this.isRendered()) {
this.getHeaderView().reRender();
return;
}
if (!doNotReRender && this.isBeingRendered()) {
this.once('after:render', () => {
this.getHeaderView().reRender();
});
}
}
/**
* Remove a menu item.
*
* @param {string} name An item name.
* @param {boolean} [doNotReRender] Skip re-render.
*/
removeMenuItem(name, doNotReRender) {
let index = -1;
let type = false;
this.headerActionItemTypeList.forEach(t => {
(this.menu[t] || []).forEach((item, i) => {
item = item || {};
if (item.name === name) {
index = i;
type = t;
}
});
});
if (~index && type) {
this.menu[type].splice(index, 1);
}
if (!doNotReRender && this.isRendered()) {
this.getHeaderView().reRender();
return;
}
if (!doNotReRender && this.isBeingRendered()) {
this.once('after:render', () => {
this.getHeaderView().reRender();
});
return;
}
if (doNotReRender && this.isRendered()) {
this.$headerActionsContainer.find('[data-name="' + name + '"]').remove();
}
}
/**
* Disable a menu item.
*
* @param {string} name A name.
*/
disableMenuItem(name) {
const item = this._getHeaderActionItem(name);
if (item) {
item.disabled = true;
}
const process = () => {
this.$headerActionsContainer.find(`[data-name="${name}"]`).addClass('disabled').attr('disabled');
};
if (this.isBeingRendered()) {
this.whenRendered().then(() => process());
return;
}
if (!this.isRendered()) {
return;
}
process();
}
/**
* Enable a menu item.
*
* @param {string} name A name.
*/
enableMenuItem(name) {
const item = this._getHeaderActionItem(name);
if (item) {
item.disabled = false;
}
const process = () => {
this.$headerActionsContainer.find(`[data-name="${name}"]`).removeClass('disabled').removeAttr('disabled');
};
if (this.isBeingRendered()) {
this.whenRendered().then(() => process());
return;
}
if (!this.isRendered()) {
return;
}
process();
}
// noinspection JSUnusedGlobalSymbols
/**
* Action 'navigateToRoot'.
*
* @param {Object} data
* @param {MouseEvent} event
*/
actionNavigateToRoot(data, event) {
event.stopPropagation();
this.getRouter().checkConfirmLeaveOut(() => {
const rootUrl = this.options.rootUrl || this.options.params.rootUrl || '#' + this.scope;
this.getRouter().navigate(rootUrl, {
trigger: true,
isReturn: true
});
});
}
/**
* @private
* @param {string} name
* @return {module:views/main~MenuItem|undefined}
*/
_getHeaderActionItem(name) {
for (const type of this.headerActionItemTypeList) {
if (!this.menu[type]) {
continue;
}
for (const item of this.menu[type]) {
if (item && item.name === name) {
return item;
}
}
}
return undefined;
}
/**
* Hide a menu item.
*
* @param {string} name A name.
*/
hideHeaderActionItem(name) {
const item = this._getHeaderActionItem(name);
if (item) {
item.hidden = true;
}
if (!this.isRendered()) {
return;
}
this.$headerActionsContainer.find(`li > .action[data-name="${name}"]`).parent().addClass('hidden');
this.$headerActionsContainer.find(`a.action[data-name="${name}"]`).addClass('hidden');
this.controlMenuDropdownVisibility();
this.adjustButtons();
if (this.getHeaderView()) {
this.getHeaderView().trigger('action-item-update');
}
}
/**
* Show a hidden menu item.
*
* @param {string} name A name.
*/
showHeaderActionItem(name) {
const item = this._getHeaderActionItem(name);
if (item) {
item.hidden = false;
}
const processUi = () => {
const $dropdownItem = this.$headerActionsContainer.find(`li > .action[data-name="${name}"]`).parent();
const $button = this.$headerActionsContainer.find(`a.action[data-name="${name}"]`);
// Item can be available but not rendered as it was skipped by access check in getMenu.
if (item && !$dropdownItem.length && !$button.length) {
if (this.getHeaderView()) {
this.getHeaderView().reRender();
}
return;
}
$dropdownItem.removeClass('hidden');
$button.removeClass('hidden');
this.controlMenuDropdownVisibility();
this.adjustButtons();
if (this.getHeaderView()) {
this.getHeaderView().trigger('action-item-update');
}
};
if (!this.isRendered()) {
if (this.isBeingRendered()) {
this.whenRendered().then(() => processUi());
}
return;
}
processUi();
}
/**
* Whether a menu has any non-hidden dropdown items.
*
* @private
* @returns {boolean}
*/
hasMenuVisibleDropdownItems() {
let hasItems = false;
(this.menu.dropdown || []).forEach(item => {
if (!item.hidden) {
hasItems = true;
}
});
return hasItems;
}
/**
* @private
*/
controlMenuDropdownVisibility() {
const $group = this.$headerActionsContainer.find('.dropdown-group');
if (this.hasMenuVisibleDropdownItems()) {
$group.removeClass('hidden');
$group.find('> button').removeClass('hidden');
return;
}
$group.addClass('hidden');
$group.find('> button').addClass('hidden');
}
/**
* @protected
* @return {module:views/header}
*/
getHeaderView() {
return this.getView('header');
}
/**
* @private
*/
adjustButtons() {
const $buttons = this.$headerActionsContainer.find('.btn');
$buttons.removeClass('radius-left').removeClass('radius-right');
const $buttonsVisible = $buttons.filter(':not(.hidden)');
$buttonsVisible.first().addClass('radius-left');
$buttonsVisible.last().addClass('radius-right');
}
/**
* Called when a stored view is reused (by the controller).
*
* @public
* @param {Object.} params Routing params.
*/
setupReuse(params) {
this.initShortcuts();
}
static #_ = _staticBlock = () => [_init_shortcutManager, _init_extra_shortcutManager] = _applyDecs(this, [], [[(0, _di.inject)(_shortcutManager.default), 0, "shortcutManager"]], 0, void 0, _view.default).e;
}
_staticBlock();
var _default = _exports.default = MainView;
});
define("views/collapsed-modal", ["exports", "view"], function (_exports, _view) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
_view = _interopRequireDefault(_view);
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* 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.
************************************************************************/
class CollapsedModalView extends _view.default {
templateContent = `
`;
events = {
/** @this CollapsedModalView */
'click [data-action="expand"]': function () {
this.expand();
},
/** @this CollapsedModalView */
'click [data-action="close"]': function () {
this.close();
}
};
/**
* @private
*/
title;
/**
* @param {{
* modalView: import('views/modal').default,
* onClose: function(),
* onExpand: function(),
* duplicateNumber?: number|null,
* title?: string,
* }} options
*/
constructor(options) {
super(options);
this.options = options;
this.modalView = options.modalView;
}
data() {
let title = this.title;
if (this.options.duplicateNumber) {
title = `${this.title} ${this.options.duplicateNumber}`;
}
return {
title: title
};
}
setup() {
this.title = this.options.title || 'no-title';
}
expand() {
this.options.onExpand();
}
close() {
this.options.onClose();
}
}
// noinspection JSUnusedGlobalSymbols
var _default = _exports.default = CollapsedModalView;
});
define("views/base", ["exports", "view"], function (_exports, _view) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
_view = _interopRequireDefault(_view);
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* 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.
************************************************************************/
/** @module views/base */
class BaseView extends _view.default {
/**
* @typedef {Object} module:views/base~options
* @property {string} [template] A template.
*/
/**
* @param {module:views/base~options & Object.} [options] Options.
*/
constructor(options) {
super(options);
}
}
var _default = _exports.default = BaseView;
});
define("views/search/filter", ["exports", "view"], function (_exports, _view) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
_view = _interopRequireDefault(_view);
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* 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.
************************************************************************/
/** @module views/search/filter */
class FilterView extends _view.default {
template = 'search/filter';
data() {
return {
name: this.name,
scope: this.model.entityType,
notRemovable: this.options.notRemovable
};
}
/**
* @param {{
* name: string,
* viewName?: string|null,
* params?: Record|null,
* notRemovable?: boolean,
* model: import('model').default,
* }} options Field view is supported as of v9.3.
*/
constructor(options) {
super(options);
this.options = options;
}
setup() {
const name = this.name = this.options.name;
let viewName = this.options.viewName;
if (!viewName) {
let type = this.model.getFieldType(name);
if (!type && name === 'id') {
type = 'id';
}
if (type) {
viewName = this.model.getFieldParam(name, 'view') || this.getFieldManager().getViewName(type);
}
}
if (!viewName) {
return;
}
this.createView('field', viewName, {
mode: 'search',
model: this.model,
selector: '.field',
name: name,
searchParams: this.options.params
}, view => {
this.listenTo(view, 'change', () => this.trigger('change'));
this.listenTo(view, 'search', () => this.trigger('search'));
});
}
/**
* @return {import('views/fields/base').default|null}
*/
getFieldView() {
return this.getView('field');
}
populateDefaults() {
const view = this.getFieldView();
if (!view) {
return;
}
if (!('populateSearchDefaults' in view)) {
return;
}
view.populateSearchDefaults();
}
}
var _default = _exports.default = FilterView;
});
define("views/record/panels-container", ["exports", "view"], function (_exports, _view) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
_view = _interopRequireDefault(_view);
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* 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.
************************************************************************/
/** @module views/record/panels-container */
/**
* A panel container view. For bottom and side views.
*/
class PanelsContainerRecordView extends _view.default {
/** @private */
panelSoftLockedTypeList = ['default', 'acl', 'delimiter', 'dynamicLogic'];
/**
* A panel.
*
* @typedef {Object} module:views/record/panels-container~panel
*
* @property {string} name A name.
* @property {boolean} [hidden] Hidden.
* @property {string} [label] A label.
* @property {'default'|'success'|'danger'|'warning'} [style] A style.
* @property {string} [titleHtml] A title HTML.
* @property {boolean} [notRefreshable] Not refreshable.
* @property {boolean} [isForm] If for a form.
* @property {module:views/record/panels-container~button[]} [buttonList] Buttons.
* @property {module:views/record/panels-container~action[]} [actionList] Dropdown actions.
* @property {string} [view] A view name.
* @property {Object.} [Options] A view options.
* @property {boolean} [sticked] To stick to an upper panel.
* @property {number} [tabNumber] A tab number.
* @property {string} [aclScope] A scope to check access to.
* @property {Espo.Utils~AccessDefs[]} [accessDataList] Access control defs.
* @property {Record} [dynamicLogicVisible] Visibility rules.
* @property {Record} [dynamicLogicStyled] Style rules.
* @property {Record} [options] Options.
* @property {boolean} [disabled] Disabled.
*/
/**
* A button. Handled by an `action{Action}` method or a click handler.
*
* @typedef {Object} module:views/record/panels-container~button
*
* @property {string} [action] An action.
* @property {string} [name] A name. Required if a handler is used.
* @property {boolean} [hidden] Hidden.
* @property {string} [label] A label. Translatable.
* @property {string} [html] A HTML.
* @property {string} [text] A text.
* @property {string} [title] A title (on hover). Translatable.
* @property {Object.} [data] Data attributes.
* @property {string} [handler] A handler.
* @property {string} [actionFunction] An action function.
* @property {function()} [onClick] A click event.
*/
/**
* An action. Handled by an `action{Action}` method or a click handler.
*
* @typedef {Object} module:views/record/panels-container~action
*
* @property {string} [action] An action.
* @property {string} [name] A name. Required if a handler is used.
* @property {string} [link] A link URL.
* @property {boolean} [hidden] Hidden.
* @property {string} [label] A label. Translatable.
* @property {string} [html] A HTML.
* @property {string} [text] A text.
* @property {Object.} [data] Data attributes.
* @property {string} [handler] A handler.
* @property {string} [actionFunction] An action function.
* @property {function()} [onClick] A click event.
*/
/**
* A panel list.
*
* @protected
* @type {module:views/record/panels-container~panel[]}
*/
panelList = null;
/** @private */
hasTabs = false;
/**
* @private
* @type {Object.[]|null}
*/
tabDataList = null;
/**
* @protected
*/
currentTab = 0;
/**
* @protected
* @type {string}
*/
scope = '';
/**
* @protected
* @type {string}
*/
entityType = '';
/**
* @protected
* @type {string}
*/
name = '';
/**
* A mode.
*
* @type 'detail'|'edit'
*/
mode = 'detail';
data() {
const tabDataList = this.hasTabs ? this.getTabDataList() : [];
return {
panelList: this.panelList,
scope: this.scope,
entityType: this.entityType,
tabDataList: tabDataList
};
}
events = {
'click .action': function (e) {
const $target = $(e.currentTarget);
const panel = $target.data('panel');
if (!panel) {
return;
}
const panelView = this.getView(panel);
if (!panelView) {
return;
}
let actionItems;
if (typeof panelView.getButtonList === 'function' && typeof panelView.getActionList === 'function') {
actionItems = [...panelView.getButtonList(), ...panelView.getActionList()];
}
Espo.Utils.handleAction(panelView, e.originalEvent, e.currentTarget, {
actionItems: actionItems,
className: 'panel-action'
});
// @todo Check data. Maybe pass cloned data with unset params.
/*
let action = $target.data('action');
let data = $target.data();
if (action && panel) {
let method = 'action' + Espo.Utils.upperCaseFirst(action);
let d = _.clone(data);
delete d['action'];
delete d['panel'];
let view = this.getView(panel);
if (view && typeof view[method] == 'function') {
view[method].call(view, d, e);
}
}*/
},
'click .panels-show-more-delimiter [data-action="showMorePanels"]': 'actionShowMorePanels',
/** @this module:views/record/panels-container */
'click .tabs > button': function (e) {
const tab = parseInt($(e.currentTarget).attr('data-tab'));
this.selectTab(tab);
}
};
afterRender() {
this.adjustPanels();
}
adjustPanels() {
if (!this.isRendered()) {
return;
}
const $panels = this.$el.find('> .panel');
$panels.removeClass('first').removeClass('last').removeClass('in-middle');
const $visiblePanels = $panels.filter(`:not(.tab-hidden):not(.hidden)`);
const groups = [];
let currentGroup = [];
let inTab = false;
$visiblePanels.each((i, el) => {
const $el = $(el);
let breakGroup = false;
if (!breakGroup && this.hasTabs && !inTab && $el.attr('data-tab') !== '-1') {
inTab = true;
breakGroup = true;
}
if (!breakGroup && !$el.hasClass('sticked')) {
breakGroup = true;
}
if (breakGroup) {
if (i !== 0) {
groups.push(currentGroup);
}
currentGroup = [];
}
currentGroup.push($el);
if (i === $visiblePanels.length - 1) {
groups.push(currentGroup);
}
});
groups.forEach(group => {
group.forEach(($el, i) => {
if (i === group.length - 1) {
if (i === 0) {
return;
}
$el.addClass('last');
return;
}
if (i === 0 && group.length) {
$el.addClass('first');
return;
}
$el.addClass('in-middle');
});
});
}
/**
* Set read-only.
*/
setReadOnly() {
this.readOnly = true;
}
/**
* Set not read-only.
*/
setNotReadOnly(onlyNotSetAsReadOnly) {
this.readOnly = false;
if (!onlyNotSetAsReadOnly) {
return;
}
this.panelList.forEach(item => {
this.applyAccessToActions(item.buttonList);
this.applyAccessToActions(item.actionList);
this.whenRendered().then(() => {
if (this.getPanelActionsView(item.name)) {
this.getPanelActionsView(item.name).reRender();
}
});
});
}
/**
* @private
* @param {Object[]} actionList
*/
applyAccessToActions(actionList) {
if (!actionList) {
return;
}
actionList.forEach(item => {
if (!Espo.Utils.checkActionAvailability(this.getHelper(), item)) {
item.hidden = true;
return;
}
const access = Espo.Utils.checkActionAccess(this.getAcl(), this.model, item, true);
if (access) {
if (item.isHiddenByAcl) {
item.isHiddenByAcl = false;
item.hidden = false;
delete item.hiddenByAclSoft;
}
} else if (!item.hidden) {
item.isHiddenByAcl = true;
item.hidden = true;
delete item.hiddenByAclSoft;
if (access === null) {
item.hiddenByAclSoft = true;
}
}
});
}
/**
* Set up panel views.
*
* @protected
*/
setupPanelViews() {
this.panelList.forEach(p => {
const name = p.name;
let options = {
model: this.model,
panelName: name,
selector: `.panel[data-name="${name}"] > .panel-body`,
defs: p,
mode: this.mode,
recordHelper: this.recordHelper,
inlineEditDisabled: this.inlineEditDisabled,
readOnly: this.readOnly,
disabled: p.hidden || false,
recordViewObject: this.recordViewObject,
dataObject: this.options.dataObject
};
options = _.extend(options, p.options);
this.createView(name, p.view, options, view => {
let hasSoftHidden = false;
if ('getActionList' in view) {
p.actionList = view.getActionList();
this.applyAccessToActions(p.actionList);
// noinspection JSUnresolvedReference
if (p.actionList.find(it => it.hiddenByAclSoft)) {
hasSoftHidden = true;
}
}
if ('getButtonList' in view) {
p.buttonList = view.getButtonList();
this.applyAccessToActions(p.buttonList);
// noinspection JSUnresolvedReference
if (p.buttonList.find(it => it.hiddenByAclSoft)) {
hasSoftHidden = true;
}
}
if (hasSoftHidden) {
this.listenToOnce(this.model, 'sync', () => {
this.applyAccessToActions(p.actionList);
this.applyAccessToActions(p.buttonList);
view.whenRendered().then(() => {
if (this.getPanelActionsView(name)) {
this.getPanelActionsView(name).reRender();
}
});
});
}
if (view.titleHtml) {
p.titleHtml = view.titleHtml;
} else {
if (p.label) {
p.title = this.translate(p.label, 'labels', this.scope);
} else {
p.title = view.title;
}
}
// @todo Use name_Actions.
this.createView(name + 'Actions', 'views/record/panel-actions', {
selector: `.panel[data-name="${p.name}"] > .panel-heading > .panel-actions-container`,
model: this.model,
defs: p,
scope: this.scope,
entityType: this.entityType
});
});
});
}
/**
* @param {string} name
* @return {import('views/record/panel-actions')}
*/
getPanelActionsView(name) {
return this.getView(name + 'Actions');
}
/**
* Set up panels.
*
* @protected
*/
setupPanels() {}
/**
* Get field views.
*
* @param {boolean} [withHidden] With hidden.
* @return {Object.}
*/
getFieldViews(withHidden) {
let fields = {};
this.panelList.forEach(p => {
const panelView = this.getPanelView(p.name);
if ((!panelView.disabled || withHidden) && 'getFieldViews' in panelView) {
fields = _.extend(fields, panelView.getFieldViews());
}
});
return fields;
}
/**
* Fetch.
*
* @return {Object.}
*/
fetch() {
let data = {};
this.panelList.forEach(p => {
const panelView = this.getPanelView(p.name);
if (!panelView.disabled && 'fetch' in panelView) {
data = _.extend(data, panelView.fetch());
}
});
return data;
}
/**
* @private
* @param name
* @return {import('views/record/panels/side').default|import('views/record/panels/side').default|null}
*/
getPanelView(name) {
return this.getView(name);
}
/**
* @param {string} name
* @return {boolean}
*/
hasPanel(name) {
return !!this.panelList.find(item => item.name === name);
}
/**
* @internal
*
* @param {string} name
* @param [callback] Not to be used.
* @param {boolean} [wasShown]
*/
processShowPanel(name, callback) {
let wasShown = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
if (this.recordHelper.getPanelStateParam(name, 'hidden')) {
return;
}
if (!this.hasPanel(name)) {
return;
}
this.panelList.filter(item => item.name === name).forEach(item => {
item.hidden = false;
if (typeof item.tabNumber !== 'undefined') {
this.controlTabVisibilityShow(item.tabNumber);
}
});
this.showPanelFinalize(name, callback, wasShown);
}
/**
* @internal
*
* @param {string} name
* @param [callback] Not to be used.
*/
processHidePanel(name, callback) {
if (!this.recordHelper.getPanelStateParam(name, 'hidden')) {
return;
}
if (!this.hasPanel(name)) {
return;
}
this.panelList.filter(item => item.name === name).forEach(item => {
item.hidden = true;
if (typeof item.tabNumber !== 'undefined') {
this.controlTabVisibilityHide(item.tabNumber);
}
});
this.hidePanelFinalize(name, callback);
}
/**
* @private
*
* @param {string} name
* @param [callback] Not to be used.
* @param {boolean} [wasShown]
*/
async showPanelFinalize(name, callback, wasShown) {
const process = wasRendered => {
const view = this.getPanelView(name);
if (view) {
if (view.element) {
const panelElement = view.element.closest('.panel');
if (panelElement) {
panelElement.classList.remove('hidden');
}
}
view.disabled = false;
view.trigger('show');
view.trigger('panel-show-propagated');
if (wasRendered && !wasShown && view.getFieldViews) {
const fields = view.getFieldViews();
if (fields) {
for (const i in fields) {
fields[i].reRender();
}
}
}
}
if (typeof callback === 'function') {
callback.call(this);
}
};
if (this.isRendered()) {
process(true);
this.adjustPanels();
return;
}
await this.whenRendered();
process();
}
/**
* @private
*
* @param {string} name
* @param [callback] Not to be used.
*/
async hidePanelFinalize(name, callback) {
if (this.isRendered()) {
const view = this.getPanelView(name);
if (view) {
if (view.element) {
const panelElement = view.element.closest('.panel');
if (panelElement) {
panelElement.classList.add('hidden');
}
}
view.disabled = true;
view.trigger('hide');
}
if (typeof callback === 'function') {
callback();
}
this.adjustPanels();
return;
}
if (typeof callback === 'function') {
await this.whenRendered();
callback();
}
}
/**
* @param {string} name
* @param {string} [softLockedType]
* @param [callback] Not to be used.
*/
showPanel(name, softLockedType, callback) {
if (this.recordHelper.getPanelStateParam(name, 'hiddenLocked')) {
return;
}
if (softLockedType) {
const param = 'hidden' + Espo.Utils.upperCaseFirst(softLockedType) + 'Locked';
this.recordHelper.setPanelStateParam(name, param, false);
for (let i = 0; i < this.panelSoftLockedTypeList.length; i++) {
const iType = this.panelSoftLockedTypeList[i];
if (iType === softLockedType) {
continue;
}
const iParam = 'hidden' + Espo.Utils.upperCaseFirst(iType) + 'Locked';
if (this.recordHelper.getPanelStateParam(name, iParam)) {
return;
}
}
}
const wasShown = this.recordHelper.getPanelStateParam(name, 'hidden') === false;
this.recordHelper.setPanelStateParam(name, 'hidden', false);
this.processShowPanel(name, callback, wasShown);
}
/**
* @param {string} name
* @param {boolean} [locked=false]
* @param {string} [softLockedType]
* @param [callback] Not to be used.
*/
hidePanel(name, locked, softLockedType, callback) {
this.recordHelper.setPanelStateParam(name, 'hidden', true);
if (locked) {
this.recordHelper.setPanelStateParam(name, 'hiddenLocked', true);
}
if (softLockedType) {
const param = 'hidden' + Espo.Utils.upperCaseFirst(softLockedType) + 'Locked';
this.recordHelper.setPanelStateParam(name, param, true);
}
this.processHidePanel(name, callback);
}
/**
* @internal
* @protected
* @param {Object} layoutData
*/
alterPanels(layoutData) {
layoutData = layoutData || this.layoutData || {};
const tabBreakIndexList = [];
const tabDataList = [];
for (const name in layoutData) {
const item = layoutData[name];
if (name === '_delimiter_') {
this.panelList.push({
name: name
});
}
if (item.tabBreak) {
tabBreakIndexList.push(item.index);
tabDataList.push({
index: item.index,
label: item.tabLabel
});
}
}
/**
* @private
* @type {Object.[]}
*/
this.tabDataList = tabDataList.sort((v1, v2) => v1.index - v2.index);
this.panelList = this.panelList.filter(item => {
return !this.recordHelper.getPanelStateParam(item.name, 'hiddenLocked');
});
const newList = [];
this.panelList.forEach((item, i) => {
item.index = 'index' in item ? item.index : i;
let allowedInLayout = false;
if (item.name) {
const itemData = layoutData[item.name] || {};
if (itemData.disabled) {
return;
}
if (layoutData[item.name]) {
allowedInLayout = true;
}
for (const i in itemData) {
item[i] = itemData[i];
}
}
if (item.disabled && !allowedInLayout) {
return;
}
item.tabNumber = tabBreakIndexList.length - tabBreakIndexList.slice().reverse().findIndex(index => item.index > index) - 1;
if (item.tabNumber === tabBreakIndexList.length) {
item.tabNumber = -1;
}
newList.push(item);
});
newList.sort((v1, v2) => v1.index - v2.index);
const firstTabIndex = newList.findIndex(item => item.tabNumber !== -1);
/*let firstNonHiddenTabIndex = newList.findIndex(item => item.tabNumber !== -1 && !item.hidden);
if (firstNonHiddenTabIndex < 0) {
firstNonHiddenTabIndex = firstTabIndex;
}*/
if (firstTabIndex !== -1) {
newList[firstTabIndex].isTabsBeginning = true;
this.hasTabs = true;
this.currentTab = newList[firstTabIndex].tabNumber;
this.panelList.filter(item => item.tabNumber !== -1 && item.tabNumber !== this.currentTab).forEach(item => {
item.tabHidden = true;
});
this.panelList.forEach((item, i) => {
if (item.tabNumber !== -1 && (i === 0 || this.panelList[i - 1].tabNumber !== item.tabNumber)) {
item.sticked = false;
}
});
}
this.panelList = newList;
if (this.recordViewObject && this.recordViewObject.dynamicLogic) {
const dynamicLogic = this.recordViewObject.dynamicLogic;
this.panelList.forEach(item => {
if (item.dynamicLogicVisible) {
dynamicLogic.addPanelVisibleCondition(item.name, item.dynamicLogicVisible);
if (this.recordHelper.getPanelStateParam(item.name, 'hidden')) {
item.hidden = true;
}
}
if (item.style && item.style !== 'default' && item.dynamicLogicStyled) {
dynamicLogic.addPanelStyledCondition(item.name, item.dynamicLogicStyled);
}
});
}
if (this.hasTabs && this.options.isReturn && this.isStoredTabForThisRecord()) {
this.selectStoredTab();
}
}
/**
* @protected
*/
setupPanelsFinal() {
let afterDelimiter = false;
let rightAfterDelimiter = false;
let index = -1;
this.panelList.forEach((p, i) => {
if (p.name === '_delimiter_') {
afterDelimiter = true;
rightAfterDelimiter = true;
index = i;
return;
}
if (afterDelimiter) {
p.hidden = true;
p.hiddenAfterDelimiter = true;
this.recordHelper.setPanelStateParam(p.name, 'hidden', true);
this.recordHelper.setPanelStateParam(p.name, 'hiddenDelimiterLocked', true);
}
if (rightAfterDelimiter) {
p.isRightAfterDelimiter = true;
rightAfterDelimiter = false;
}
});
if (~index) {
this.panelList.splice(index, 1);
}
this.panelsAreSet = true;
this.trigger('panels-set');
}
actionShowMorePanels() {
this.panelList.forEach(/** module:views/record/panels-container~panel & Record*/p => {
if (!p.hiddenAfterDelimiter) {
return;
}
delete p.isRightAfterDelimiter;
this.showPanel(p.name, 'delimiter');
});
this.$el.find('.panels-show-more-delimiter').remove();
}
/**
* @internal
* @protected
* @param {function} callback
*/
onPanelsReady(callback) {
Promise.race([new Promise(resolve => {
if (this.panelsAreSet) {
resolve();
}
}), new Promise(resolve => {
this.once('panels-set', resolve);
})]).then(() => {
callback.call(this);
});
}
/**
* @private
* @return {{
* label: string,
* isActive: boolean,
* hidden: boolean,
* }[]}
*/
getTabDataList() {
return this.tabDataList.map((item, i) => {
let label = item.label;
if (!label) {
label = (i + 1).toString();
} else if (label[0] === '$') {
label = this.translate(label.substring(1), 'tabs', this.scope);
}
const hidden = this.panelList.filter(panel => panel.tabNumber === i).findIndex(panel => !this.recordHelper.getPanelStateParam(panel.name, 'hidden')) === -1;
return {
label: label,
isActive: i === this.currentTab,
hidden: hidden
};
});
}
/**
* @private
* @param {number} tab
* @param {boolean} [doNotStore]
*/
selectTab(tab) {
let doNotStore = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
this.currentTab = tab;
if (this.isRendered()) {
$('body > .popover').remove();
this.$el.find('.tabs > button').removeClass('active');
this.$el.find(`.tabs > button[data-tab="${tab}"]`).addClass('active');
this.$el.find('.panel[data-tab]:not([data-tab="-1"])').addClass('tab-hidden');
this.$el.find(`.panel[data-tab="${tab}"]`).removeClass('tab-hidden');
}
this.adjustPanels();
this.panelList.filter(item => item.tabNumber === tab && item.name).forEach(item => {
const view = this.getPanelView(item.name);
if (view) {
view.trigger('tab-show');
view.propagateEvent('panel-show-propagated');
}
item.tabHidden = false;
});
this.panelList.filter(item => item.tabNumber !== tab && item.name).forEach(item => {
const view = this.getPanelView(item.name);
if (view) {
view.trigger('tab-hide');
}
if (item.tabNumber > -1) {
item.tabHidden = true;
}
});
if (doNotStore) {
return;
}
this.storeTab();
}
/** @private */
storeTab() {
const key = `tab_${this.name}`;
const keyRecord = `tab_${this.name}_record`;
this.getSessionStorage().set(key, this.currentTab);
this.getSessionStorage().set(keyRecord, `${this.entityType}_${this.model.id}`);
}
/** @private */
isStoredTabForThisRecord() {
const keyRecord = `tab_${this.name}_record`;
return this.getSessionStorage().get(keyRecord) === `${this.entityType}_${this.model.id}`;
}
/** @private */
selectStoredTab() {
const key = `tab_${this.name}`;
const tab = this.getSessionStorage().get(key);
if (tab > 0) {
this.selectTab(tab);
}
}
/**
* @private
* @param {number} tab
*/
async controlTabVisibilityShow(tab) {
if (!this.hasTabs) {
return;
}
await this.whenRendered();
if (this.element) {
const tabElement = this.element.querySelector(`.tabs > [data-tab="${tab.toString()}"]`);
if (tabElement) {
tabElement.classList.remove('hidden');
}
}
}
/**
* @private
* @param {number} tab
*/
async controlTabVisibilityHide(tab) {
if (!this.hasTabs) {
return;
}
await this.whenRendered();
const panelList = this.panelList.filter(panel => panel.tabNumber === tab);
const allHidden = panelList.findIndex(panel => !this.recordHelper.getPanelStateParam(panel.name, 'hidden')) === -1;
if (!allHidden) {
return;
}
if (this.element) {
const tabElement = this.element.querySelector(`.tabs > [data-tab="${tab.toString()}"]`);
if (tabElement) {
tabElement.classList.add('hidden');
}
}
if (this.currentTab === tab) {
const firstVisiblePanel = this.panelList.find(panel => panel.tabNumber > -1 && !panel.hidden);
const firstVisibleTab = firstVisiblePanel ? firstVisiblePanel.tabNumber : 0;
this.selectTab(firstVisibleTab, true);
}
}
/**
* @protected
*/
setupInitial() {
// Handles the situation when the first tab is hidden.
this.listenToOnce(this.model, 'sync', async (m, r, /** Record */o) => {
if (o.action !== 'fetch') {
return;
}
setTimeout(async () => {
await this.whenRendered();
if (!this.hasTabs) {
return;
}
const firstVisiblePanel = this.panelList.find(p => !p.hidden && p.tabNumber > -1);
if (!firstVisiblePanel) {
return;
}
if (firstVisiblePanel.tabNumber > this.currentTab) {
this.selectTab(firstVisiblePanel.tabNumber, true);
}
}, 1);
});
}
}
var _default = _exports.default = PanelsContainerRecordView;
});
define("views/record/list-expanded", ["exports", "views/record/list"], function (_exports, _list) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
_list = _interopRequireDefault(_list);
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* 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.
************************************************************************/
/** @module module:views/record/list-expanded */
class ListExpandedRecordView extends _list.default {
template = 'record/list-expanded';
checkboxes = false;
selectable = false;
rowActionsView = false;
_internalLayoutType = 'list-row-expanded';
presentationType = 'expanded';
paginationDisabled = true;
header = false;
_internalLayout = null;
checkedList = null;
listContainerEl = '> .list > ul';
columnResize = false;
init() {
if (this.options.forcePagination) {
this.paginationDisabled = false;
}
super.init();
}
setup() {
super.setup();
this.on('after:save', model => {
const view = this.getView(model.id);
if (!view) {
return;
}
view.reRender();
});
// Prevents displaying an empty buttons container.
this.displayTotalCount = false;
}
_loadListLayout(callback) {
const type = this.type + 'Expanded';
this.layoutLoadCallbackList.push(callback);
if (this.layoutIsBeingLoaded) {
return;
}
this.layoutIsBeingLoaded = true;
this._helper.layoutManager.get(this.collection.entityType, type, listLayout => {
this.layoutLoadCallbackList.forEach(c => {
c(listLayout);
this.layoutLoadCallbackList = [];
this.layoutIsBeingLoaded = false;
});
});
}
_convertLayout(listLayout, model) {
model = model || this.collection.prepareModel();
const layout = {
rows: [],
right: false
};
for (const i in listLayout.rows) {
const row = listLayout.rows[i];
const layoutRow = [];
for (const j in row) {
const rowItem = row[j];
const type = rowItem.type || model.getFieldType(rowItem.name) || 'base';
const item = {
name: rowItem.name + 'Field',
field: rowItem.name,
view: rowItem.view || model.getFieldParam(rowItem.name, 'view') || this.getFieldManager().getViewName(type),
options: {
defs: {
name: rowItem.name,
params: rowItem.params || {}
},
mode: 'list'
},
align: rowItem.align,
small: rowItem.small,
soft: rowItem.soft
};
if (rowItem.options) {
for (const optionName in rowItem.options) {
if (typeof item.options[optionName] !== 'undefined') {
continue;
}
item.options[optionName] = rowItem.options[optionName];
}
}
if (rowItem.link) {
item.options.mode = 'listLink';
}
layoutRow.push(item);
}
layout.rows.push(layoutRow);
}
if ('right' in listLayout) {
if (listLayout.right) {
const name = listLayout.right.name || 'right';
layout.right = {
field: name,
name: name,
view: listLayout.right.view,
options: {
defs: {
params: {
width: listLayout.right.width || '7%'
}
}
}
};
}
} else if (this.rowActionsView) {
layout.right = this.getRowActionsDefs();
}
return layout;
}
getRowSelector(id) {
return 'li[data-id="' + id + '"]';
}
getCellSelector(model, item) {
const name = item.field || item.columnName;
return `${this.getSelector()} ${this.getRowSelector(model.id)} .cell[data-name="${name}"]`;
}
getRowContainerHtml(id) {
return $('').attr('data-id', id).addClass('list-group-item list-row').get(0).outerHTML;
}
prepareInternalLayout(internalLayout, model) {
const rows = internalLayout.rows || [];
rows.forEach(row => {
row.forEach(cell => {
//cell.fullSelector = this.getCellSelector(model, cell);
cell.options ??= {};
cell.options.fullSelector = this.getCellSelector(model, cell);
});
});
if (internalLayout.right) {
//internalLayout.right.fullSelector = this.getCellSelector(model, internalLayout.right);
internalLayout.right.options ??= {};
internalLayout.right.options.fullSelector = this.getCellSelector(model, internalLayout.right);
}
}
fetchAttributeListFromLayout() {
const list = [];
if (this.listLayout.rows) {
this.listLayout.rows.forEach(row => {
row.forEach(item => {
if (!item.name) {
return;
}
const field = item.name;
const fieldType = this.getMetadata().get(['entityDefs', this.scope, 'fields', field, 'type']);
if (!fieldType) {
return;
}
this.getFieldManager().getEntityTypeFieldAttributeList(this.scope, field).forEach(attribute => {
list.push(attribute);
});
});
});
}
return list;
}
}
var _default = _exports.default = ListExpandedRecordView;
});
define("views/record/panels/bottom", ["exports", "view"], function (_exports, _view) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
_view = _interopRequireDefault(_view);
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* 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.
************************************************************************/
/** @module views/record/panels/bottom */
/**
* @typedef {Object} module:views/record/panels/bottom~defs
* @property [buttonList]
* @property [actionList]
* @property [tabNumber] For internal purposes.
*/
/**
* A bottom panel.
*/
class BottomPanelView extends _view.default {
template = 'record/panels/side';
/**
* A field list.
*
* @protected
* @type {module:views/record/panels/side~field[]}
*/
fieldList;
/**
* @protected
* @type {Array}
*/
actionList;
/**
* @protected
* @type {module:views/record/panels-container~button[]}
*/
buttonList;
/**
* @protected
* @type {module:views/record/panels/bottom~defs|Object.}
*/
defs;
/**
* A mode.
*
* @protected
* @type {'list'|'detail'|'edit'}
*/
mode = 'detail';
/**
* Disable.
*
* @protected
*/
disabled = false;
events = {
/** @this BottomPanelView */
'click .action': function (e) {
Espo.Utils.handleAction(this, e.originalEvent, e.currentTarget, {
actionItems: [...this.buttonList, ...this.actionList],
className: 'panel-action'
});
}
};
data() {
return {
scope: this.scope,
entityType: this.entityType,
name: this.panelName,
hiddenFields: this.recordHelper.getHiddenFields(),
fieldList: this.getFieldList()
};
}
init() {
this.panelName = this.options.panelName;
this.defs = this.options.defs || {};
this.recordHelper = this.options.recordHelper;
if ('disabled' in this.options) {
this.disabled = this.options.disabled;
}
this.mode = this.options.mode || this.mode;
this.readOnlyLocked = this.options.readOnlyLocked || this.readOnly;
this.readOnly = this.readOnly || this.options.readOnly;
this.inlineEditDisabled = this.inlineEditDisabled || this.options.inlineEditDisabled;
this.buttonList = Espo.Utils.clone(this.defs.buttonList || this.buttonList || []);
this.actionList = Espo.Utils.clone(this.defs.actionList || this.actionList || []);
this.actionList.forEach(it => {
if (it.name) {
it.action = it.name;
}
});
this.fieldList = this.options.fieldList || this.fieldList || [];
this.recordViewObject = this.options.recordViewObject;
}
setup() {
this.setupFields();
this.fieldList = this.fieldList.map(d => {
let item = d;
if (typeof item !== 'object') {
item = {
name: item,
viewKey: item + 'Field'
};
}
item = Espo.Utils.clone(item);
item.viewKey = item.name + 'Field';
item.label = item.label || item.name;
if (this.recordHelper.getFieldStateParam(item.name, 'hidden') !== null) {
item.hidden = this.recordHelper.getFieldStateParam(item.name, 'hidden');
} else {
this.recordHelper.setFieldStateParam(item.name, 'hidden', item.hidden || false);
}
return item;
});
this.fieldList = this.fieldList.filter(item => {
if (!item.name) {
return;
}
if (!(item.name in ((this.model.defs || {}).fields || {}))) {
return;
}
return true;
});
this.createFields();
}
/**
* Set up fields.
*
* @protected
*/
setupFields() {}
/**
* @return {module:views/record/panels-container~button[]}
*/
getButtonList() {
return this.buttonList || [];
}
/**
* @return {module:views/record/panels-container~action[]}
*/
getActionList() {
return this.actionList || [];
}
/**
* Get field views.
*
* @return {Object.}
*/
getFieldViews() {
const fields = {};
this.getFieldList().forEach(item => {
if (this.hasView(item.viewKey)) {
fields[item.name] = this.getView(item.viewKey);
}
});
return fields;
}
/**
* @deprecated Use `getFieldViews`.
* @todo Remove in v10.0.
*/
getFields() {
return this.getFieldViews();
}
/**
* Get a field list.
*
* @return {module:views/record/panels/side~field[]}
*/
getFieldList() {
return this.fieldList.map(item => {
if (typeof item !== 'object') {
return {
name: item
};
}
return item;
});
}
/**
* @private
*/
createFields() {
this.getFieldList().forEach(item => {
let view = null;
let field;
let readOnly = null;
if (typeof item === 'object') {
field = item.name;
view = item.view;
if ('readOnly' in item) {
readOnly = item.readOnly;
}
} else {
field = item;
}
if (!(field in this.model.defs.fields)) {
return;
}
this.createField(field, view, null, null, readOnly);
});
}
/**
* Create a field view.
*
* @protected
* @param {string} field A field name.
* @param {string|null} [viewName] A view name/path.
* @param {Object} [params] Field params.
* @param {'detail'|'edit'|'list'|null} [mode='edit'] A mode.
* @param {boolean} [readOnly] Read-only.
* @param {Object} [options] View options.
*/
createField(field, viewName, params, mode, readOnly, options) {
const type = this.model.getFieldType(field) || 'base';
viewName = viewName || this.model.getFieldParam(field, 'view') || this.getFieldManager().getViewName(type);
const o = {
model: this.model,
selector: '.field[data-name="' + field + '"]',
defs: {
name: field,
params: params || {}
},
mode: mode || this.mode,
dataObject: this.options.dataObject
};
if (options) {
for (const param in options) {
o[param] = options[param];
}
}
let readOnlyLocked = this.readOnlyLocked;
if (this.readOnly) {
o.readOnly = true;
} else {
if (readOnly !== null) {
o.readOnly = readOnly;
}
}
if (readOnly) {
readOnlyLocked = true;
}
if (this.inlineEditDisabled) {
o.inlineEditDisabled = true;
}
if (this.recordHelper.getFieldStateParam(field, 'hidden')) {
o.disabled = true;
}
if (this.recordHelper.getFieldStateParam(field, 'hiddenLocked')) {
o.disabledLocked = true;
}
if (this.recordHelper.getFieldStateParam(field, 'readOnly')) {
o.readOnly = true;
}
if (this.recordHelper.getFieldStateParam(field, 'required') !== null) {
o.defs.params.required = this.recordHelper.getFieldStateParam(field, 'required');
}
if (!readOnlyLocked && this.recordHelper.getFieldStateParam(field, 'readOnlyLocked')) {
readOnlyLocked = true;
}
if (readOnlyLocked) {
o.readOnlyLocked = readOnlyLocked;
}
if (this.recordHelper.hasFieldOptionList(field)) {
o.customOptionList = this.recordHelper.getFieldOptionList(field);
}
if (this.recordViewObject) {
o.validateCallback = () => this.recordViewObject.validateField(field);
}
const viewKey = field + 'Field';
this.createView(viewKey, viewName, o);
}
// noinspection JSUnusedGlobalSymbols
/**
* Is tab-hidden.
*
* @return {boolean}
*/
isTabHidden() {
if (this.defs.tabNumber === -1 || typeof this.defs.tabNumber === 'undefined') {
return false;
}
const parentView = this.getParentView();
if (!parentView) {
return this.defs.tabNumber > 0;
}
// noinspection JSUnresolvedReference
if (parentView && parentView.hasTabs) {
return parentView.currentTab !== this.defs.tabNumber;
}
return false;
}
}
var _default = _exports.default = BottomPanelView;
});
define("views/fields/wysiwyg", ["exports", "views/fields/text", "helpers/misc/summernote-custom"], function (_exports, _text, _summernoteCustom) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
_text = _interopRequireDefault(_text);
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* 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.
************************************************************************/
/** @module views/fields/wysiwyg */
/**
* A wysiwyg field.
*
* @extends TextFieldView
*/
class WysiwygFieldView extends _text.default {
/**
* @typedef {Object} module:views/fields/wysiwyg~options
* @property {
* module:views/fields/wysiwyg~params &
* module:views/fields/base~params &
* Record
* } [params] Parameters.
*/
/**
* @typedef {Object} module:views/fields/wysiwyg~params
* @property {boolean} [required] Required.
* @property {number} [maxLength] A max length.
* @property {number} [height] A height in pixels.
* @property {number} [minHeight] A min height in pixels.
* @property {boolean} [useIframe] Use iframe.
* @property {Array} [toolbar] A custom toolbar.
* @property {string} [attachmentField] An attachment field name.
*/
/**
* @param {
* module:views/fields/wysiwyg~options &
* module:views/fields/base~options
* } options Options.
*/
constructor(options) {
super(options);
}
type = 'wysiwyg';
listTemplate = 'fields/wysiwyg/detail';
detailTemplate = 'fields/wysiwyg/detail';
editTemplate = 'fields/wysiwyg/edit';
height = 250;
rowsDefault = 10000;
fallbackBodySideMargin = 5;
fallbackBodyTopMargin = 4;
seeMoreDisabled = true;
fetchEmptyValueAsNull = true;
validationElementSelector = '.note-editor';
htmlPurificationDisabled = false;
htmlPurificationForEditDisabled = false;
tableClassName = 'table table-bordered';
noStylesheet = false;
useIframe = false;
handlebars = false;
/** @protected */
toolbar;
/** @protected */
hasBodyPlainField = false;
events = {
/** @this WysiwygFieldView */
'click .note-editable': function () {
this.fixPopovers();
},
/** @this WysiwygFieldView */
'focus .note-editable': function () {
this.$noteEditor.addClass('in-focus');
},
/** @this WysiwygFieldView */
'blur .note-editable': function () {
this.$noteEditor.removeClass('in-focus');
}
};
setup() {
super.setup();
this.loadSummernote();
if ('height' in this.params) {
this.height = this.params.height;
}
if ('minHeight' in this.params) {
this.minHeight = this.params.minHeight;
}
this.useIframe = this.params.useIframe || this.useIframe;
this.setupToolbar();
this.setupIsHtml();
this.once('remove', () => this.destroySummernote());
this.on('inline-edit-off', () => this.destroySummernote());
this.on('render', () => this.destroySummernote());
this.once('remove', () => {
$(window).off(`resize.${this.cid}`);
if (this.$scrollable) {
this.$scrollable.off(`scroll.${this.cid}-edit`);
}
});
}
/** @private */
loadSummernote() {
this.wait(Espo.loader.requirePromise('lib!summernote').then(() => {
if (!$.summernote.options || 'espoImage' in $.summernote.options) {
return;
}
this.initEspoPlugin();
}));
}
/** @protected */
setupIsHtml() {
if (!this.hasBodyPlainField) {
return;
}
this.listenTo(this.model, 'change:isHtml', (model, value, o) => {
if (o.ui && this.isEditMode()) {
if (!this.isRendered()) {
return;
}
if (this.isHtml()) {
let value = this.plainToHtml(this.model.get(this.name));
if (this.lastHtmlValue && this.model.get(this.name) === this.htmlToPlain(this.lastHtmlValue)) {
value = this.lastHtmlValue;
}
this.model.set(this.name, value, {
skipReRender: true
});
this.enableWysiwygMode();
return;
}
this.lastHtmlValue = this.model.get(this.name);
const value = this.htmlToPlain(this.model.get(this.name));
this.disableWysiwygMode();
this.model.set(this.name, value);
return;
}
if (this.isDetailMode() && this.isRendered()) {
this.reRender();
}
});
}
data() {
const data = super.data();
data.useIframe = this.useIframe;
data.isPlain = !this.isHtml();
data.isNone = !data.isNotEmpty && data.valueIsSet && this.isDetailMode();
// noinspection JSValidateTypes
return data;
}
setupToolbar() {
this.buttons = {};
const codeviewName = this.getConfig().get('wysiwygCodeEditorDisabled') ? 'codeview' : 'aceCodeview';
this.toolbar = this.params.toolbar || this.toolbar || [['style', ['style']], ['style', ['bold', 'italic', 'underline', 'clear']], ['fontsize', ['fontsize']], ['color', ['color']], ['para', ['ul', 'ol', 'paragraph']], ['height', ['height']], ['table', ['espoTable', 'espoLink', 'espoImage', 'hr']], ['misc', [codeviewName, 'fullscreen']]];
if (this.params.toolbar) {
return;
}
if (!this.params.attachmentField) {
return;
}
this.toolbar.push(['attachment', ['attachment']]);
this.buttons['attachment'] = () => {
const ui = $.summernote.ui;
const button = ui.button({
contents: '',
tooltip: this.translate('Attach File'),
click: () => {
this.attachFile();
}
});
return button.render();
};
}
/**
* @protected
* @return {boolean}
*/
isHtml() {
if (!this.hasBodyPlainField) {
return true;
}
return !this.model.has('isHtml') || this.model.get('isHtml');
}
fixPopovers() {
$('body > .note-popover').removeClass('hidden');
}
getValueForDisplay() {
if (!this.isReadMode() && this.isHtml()) {
return undefined;
}
const value = super.getValueForDisplay();
if (!this.isHtml()) {
return value;
}
return this.sanitizeHtml(value);
}
/**
* @protected
* @param {string} value
* @return {string}
*/
sanitizeHtml(value) {
if (!value) {
return '';
}
if (this.htmlPurificationDisabled) {
return this.sanitizeHtmlLight(value);
}
value = this.getHelper().sanitizeHtml(value);
if (this.isEditMode()) {
// Trick to handle the issue that attributes are re-ordered.
value = this.getHelper().sanitizeHtml(value);
}
return value;
}
sanitizeHtmlLight(value) {
return this.getHelper().moderateSanitizeHtml(value);
}
getValueForEdit() {
const value = this.model.get(this.name) || '';
if (this.htmlPurificationForEditDisabled) {
return this.sanitizeHtmlLight(value);
}
return this.sanitizeHtml(value);
}
afterRender() {
super.afterRender();
if (this.isEditMode()) {
this.$summernote = this.$el.find('.summernote');
}
const language = this.getConfig().get('language');
if (!(language in $.summernote.lang)) {
$.summernote.lang[language] = this.getLanguage().translate('summernote', 'sets');
}
if (this.isEditMode()) {
if (this.isHtml()) {
this.enableWysiwygMode();
} else {
this.$element.removeClass('hidden');
}
if (this.params.attachmentField && this.isInlineEditMode()) {
this.$el.find('.note-attachment').addClass('hidden');
}
}
if (this.isReadMode()) {
this.renderDetail();
}
}
renderDetail() {
if (!this.isHtml()) {
this.$el.find('.plain').removeClass('hidden');
return;
}
if (!this.useIframe) {
this.$element = this.$el.find('.html-container');
return;
}
this.$el.find('iframe').removeClass('hidden');
const $iframe = this.$el.find('iframe');
/** @type {HTMLIFrameElement} */
const iframeElement = this.iframe = $iframe.get(0);
iframeElement.setAttribute('sandbox', '');
if (!iframeElement || !iframeElement.contentWindow) {
return;
}
$iframe.on('load', () => {
$iframe.contents().find('a').attr('target', '_blank');
});
const documentElement = iframeElement.contentWindow.document;
let bodyHtml = this.getValueForIframe();
const useFallbackStylesheet = this.getThemeManager().getParam('isDark') && this.htmlHasColors(bodyHtml);
const addFallbackClass = this.getThemeManager().getParam('isDark') && (this.htmlHasColors(bodyHtml) || this.noStylesheet);
const $iframeContainer = $iframe.parent();
addFallbackClass ? $iframeContainer.addClass('fallback') : $iframeContainer.removeClass('fallback');
if (!this.noStylesheet) {
const linkElement = iframeElement.contentWindow.document.createElement('link');
linkElement.type = 'text/css';
linkElement.rel = 'stylesheet';
linkElement.href = this.getBasePath() + (useFallbackStylesheet ? this.getThemeManager().getIframeFallbackStylesheet() : this.getThemeManager().getIframeStylesheet());
bodyHtml = linkElement.outerHTML + bodyHtml;
}
let headHtml = '';
if (this.noStylesheet) {
const styleElement = documentElement.createElement('style');
styleElement.textContent = `\ntable.bordered, table.bordered td, table.bordered th {border: 1px solid;}\n`;
headHtml = styleElement.outerHTML;
}
// noinspection HtmlRequiredTitleElement
const documentHtml = `${headHtml}${bodyHtml}`;
documentElement.write(documentHtml);
documentElement.close();
const $body = $iframe.contents().find('html body');
$body.find('img').each((i, img) => {
const $img = $(img);
if ($img.css('max-width') !== 'none') {
return;
}
$img.css('max-width', '100%');
});
const $document = $(documentElement);
// Make dropdowns closed.
$document.on('click', () => {
const event = new MouseEvent('click', {
bubbles: true
});
$iframe[0].dispatchEvent(event);
});
// Make notifications & global-search popup closed.
$document.on('mouseup', () => {
const event = new MouseEvent('mouseup', {
bubbles: true
});
$iframe[0].dispatchEvent(event);
});
// Make shortcuts working.
$document.on('keydown', e => {
const originalEvent = /** @type {KeyboardEvent} */e.originalEvent;
const event = new KeyboardEvent('keydown', {
bubbles: true,
code: originalEvent.code,
ctrlKey: originalEvent.ctrlKey,
metaKey: originalEvent.metaKey,
altKey: originalEvent.altKey
});
$iframe[0].dispatchEvent(event);
});
const processWidth = function () {
const bodyElement = $body.get(0);
if (bodyElement) {
if (bodyElement.clientWidth !== iframeElement.scrollWidth) {
iframeElement.style.height = iframeElement.scrollHeight + 20 + 'px';
}
}
};
if (useFallbackStylesheet) {
$iframeContainer.css({
paddingLeft: this.fallbackBodySideMargin + 'px',
paddingRight: this.fallbackBodySideMargin + 'px',
paddingTop: this.fallbackBodyTopMargin + 'px'
});
}
const increaseHeightStep = 10;
const processIncreaseHeight = function (iteration, previousDiff) {
$body.css('height', '');
iteration = iteration || 0;
if (iteration > 200) {
return;
}
iteration++;
const diff = $document.height() - iframeElement.scrollHeight;
if (typeof previousDiff !== 'undefined') {
if (diff === previousDiff) {
$body.css('height', iframeElement.clientHeight - increaseHeightStep + 'px');
processWidth();
return;
}
}
if (diff) {
const height = iframeElement.scrollHeight + increaseHeightStep;
iframeElement.style.height = height + 'px';
processIncreaseHeight(iteration, diff);
} else {
processWidth();
}
};
const processBg = () => {
const color = iframeElement.contentWindow.getComputedStyle($body.get(0)).backgroundColor;
$iframeContainer.css({
backgroundColor: color
});
};
const processHeight = function (isOnLoad) {
if (!isOnLoad) {
$iframe.css({
overflowY: 'hidden',
overflowX: 'hidden'
});
iframeElement.style.height = '0px';
} else {
if (iframeElement.scrollHeight >= $document.height()) {
return;
}
}
const $body = $iframe.contents().find('html body');
let height = $body.height();
if (height === 0) {
height = $body.children().height() + 100;
}
iframeElement.style.height = height + 'px';
processIncreaseHeight();
if (!isOnLoad) {
$iframe.css({
overflowY: 'hidden',
overflowX: 'scroll'
});
}
};
$iframe.css({
visibility: 'hidden'
});
setTimeout(() => {
processHeight();
$iframe.css({
visibility: 'visible'
});
$iframe.on('load', () => {
processHeight(true);
if (useFallbackStylesheet && !this.noStylesheet) {
processBg();
}
});
}, 40);
if (!this.model.get(this.name)) {
$iframe.addClass('hidden');
}
let windowWidth = $(window).width();
$(window).off('resize.' + this.cid);
$(window).on('resize.' + this.cid, () => {
if ($(window).width() !== windowWidth) {
processHeight();
windowWidth = $(window).width();
}
});
}
/**
* @protected
* @return {string}
*/
getValueForIframe() {
return this.sanitizeHtml(this.model.get(this.name) || '');
}
enableWysiwygMode() {
if (!this.$element) {
return;
}
this.$element.addClass('hidden');
this.$summernote.removeClass('hidden');
const contents = this.getValueForEdit();
this.$summernote.html(contents);
// The same sanitizing in the email body field.
this.$summernote.find('style').remove();
this.$summernote.find('link[ref="stylesheet"]').remove();
const keyMap = Espo.Utils.cloneDeep($.summernote.options.keyMap);
keyMap.pc['CTRL+K'] = 'espoLink.show';
keyMap.mac['CMD+K'] = 'espoLink.show';
keyMap.pc['CTRL+DELETE'] = 'removeFormat';
keyMap.mac['CMD+DELETE'] = 'removeFormat';
delete keyMap.pc['CTRL+ENTER'];
delete keyMap.mac['CMD+ENTER'];
delete keyMap.pc['CTRL+BACKSLASH'];
delete keyMap.mac['CMD+BACKSLASH'];
const toolbar = this.toolbar;
let lastChangeKeydown = new Date();
const changeKeydownInterval = this.changeInterval * 1000;
// noinspection JSUnusedGlobalSymbols
const options = {
handlebars: this.handlebars,
prettifyHtml: false,
// should not be true
disableResizeEditor: true,
isDark: this.getThemeManager().getParam('isDark'),
espoView: this,
lang: this.getConfig().get('language'),
keyMap: keyMap,
callbacks: {
onImageUpload: files => {
const file = files[0];
Espo.Ui.notify(this.translate('Uploading...'));
this.uploadInlineAttachment(file).then(attachment => {
const url = '?entryPoint=attachment&id=' + attachment.id;
this.$summernote.summernote('insertImage', url);
Espo.Ui.notify(false);
});
},
onBlur: () => {
this.trigger('change');
},
onKeydown: () => {
if (Date.now() - lastChangeKeydown > changeKeydownInterval) {
this.trigger('change');
lastChangeKeydown = Date.now();
}
}
},
onCreateLink(link) {
return link;
},
toolbar: toolbar,
buttons: this.buttons,
dialogsInBody: this.$el,
codeviewFilter: true,
tableClassName: this.tableClassName,
// Dnd has issues.
disableDragAndDrop: true,
colorButton: {
foreColor: '#000000',
backColor: '#FFFFFF'
}
};
if (this.height) {
options.height = this.height;
} else {
let $scrollable = this.$el.closest('.modal-body');
if (!$scrollable.length) {
$scrollable = $(window);
}
this.$scrollable = $scrollable;
$scrollable.off(`scroll.${this.cid}-edit`);
$scrollable.on(`scroll.${this.cid}-edit`, e => this.onScrollEdit(e));
}
if (this.minHeight) {
options.minHeight = this.minHeight;
}
this.destroySummernote();
this.$summernote.summernote(options);
this.summernoteIsInitialized = true;
this.$toolbar = this.$el.find('.note-toolbar');
this.$area = this.$el.find('.note-editing-area');
this.$noteEditor = this.$el.find('> .note-editor');
}
focusOnInlineEdit() {
if (this.$noteEditor) {
this.$summernote.summernote('focus');
return;
}
super.focusOnInlineEdit();
}
uploadInlineAttachment(file) {
return new Promise((resolve, reject) => {
this.getModelFactory().create('Attachment', attachment => {
const fileReader = new FileReader();
fileReader.onload = e => {
attachment.set('name', file.name);
attachment.set('type', file.type);
attachment.set('role', 'Inline Attachment');
attachment.set('global', true);
attachment.set('size', file.size);
if (this.model.id) {
attachment.set('relatedId', this.model.id);
}
attachment.set('relatedType', this.model.entityType);
attachment.set('file', e.target.result);
attachment.set('field', this.name);
attachment.save().then(() => resolve(attachment)).catch(() => reject());
};
fileReader.readAsDataURL(file);
});
});
}
destroySummernote() {
if (this.summernoteIsInitialized && this.$summernote) {
this.$summernote.summernote('destroyAceCodeview');
this.$summernote.summernote('destroy');
this.summernoteIsInitialized = false;
}
}
plainToHtml(html) {
html = html || '';
return html.replace(/\n/g, '
');
}
/**
* @protected
* @param {string} html
* @return {string}
*/
htmlToPlain(html) {
const div = document.createElement('div');
div.innerHTML = html;
/**
* @param {Node|HTMLElement} node
* @return {string}
*/
function processNode(node) {
if (node.nodeType === Node.TEXT_NODE) {
return node.nodeValue;
}
if (node.nodeType === Node.ELEMENT_NODE) {
if (node instanceof HTMLAnchorElement) {
if (node.textContent === node.href) {
return node.href;
}
return `${node.textContent} (${node.href})`;
}
if (node instanceof HTMLQuoteElement) {
return `> ${node.textContent.trim()}`;
}
switch (node.tagName.toLowerCase()) {
case 'br':
case 'p':
case 'div':
return `\n${Array.from(node.childNodes).map(processNode).join('')}\n`;
}
return Array.from(node.childNodes).map(processNode).join('');
}
return '';
}
return processNode(div).replace(/\n{2,}/g, '\n\n').trim();
}
disableWysiwygMode() {
this.destroySummernote();
this.$noteEditor = null;
if (this.$summernote) {
this.$summernote.addClass('hidden');
}
this.$element.removeClass('hidden');
if (this.$scrollable) {
this.$scrollable.off('scroll.' + this.cid + '-edit');
}
}
fetch() {
const data = {};
if (this.isHtml()) {
let code = this.$summernote.summernote('code');
if (code === '
') {
code = '';
}
const imageTagString = `
{
if (this.isEditMode()) {
const msg = this.translate('Attached') + '\n' + attachments.map(m => m.attributes.name).join('\n');
Espo.Ui.notify(msg, 'success', 3000);
}
});
}
initEspoPlugin() {
const langSets = this.getLanguage().get('Global', 'sets', 'summernote') || {
image: {},
link: {},
video: {}
};
(0, _summernoteCustom.init)(langSets);
}
htmlHasColors(string) {
if (~string.indexOf('background-color:')) {
return true;
}
if (~string.indexOf('color:')) {
return true;
}
if (~string.indexOf('.
*
* 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.
************************************************************************/
/** @module views/fields/link */
/**
* A link field (belongs-to relation).
*
* @extends BaseFieldView
*/
class LinkFieldView extends _base.default {
/**
* @typedef {Object} module:views/fields/link~options
* @property {
* module:views/fields/link~params &
* module:views/fields/base~params &
* Record
* } [params] Parameters.
* @property {boolean} [createDisabled] Disable create button in the select modal.
*/
/**
* @typedef {Object} module:views/fields/link~params
* @property {boolean} [required] Required.
* @property {boolean} [autocompleteOnEmpty] Autocomplete on empty input.
* @property {boolean} [createButton] Show 'Create' button.
* @property {string} [entity] An entity type. As of 9.1.0.
*/
/**
* @param {
* module:views/fields/link~options &
* module:views/fields/base~options
* } options Options.
*/
constructor(options) {
super(options);
}
/** @inheritDoc */
type = 'link';
/** @inheritDoc */
listTemplate = 'fields/link/list';
/** @inheritDoc */
detailTemplate = 'fields/link/detail';
/** @inheritDoc */
editTemplate = 'fields/link/edit';
/** @inheritDoc */
searchTemplate = 'fields/link/search';
/**
* A name attribute name.
*
* @type {string}
*/
nameName;
/**
* An ID attribute name.
*
* @type {string}
*/
idName;
/**
* A foreign entity type.
*
* @type {string|null}
*/
foreignScope = null;
/**
* A select-record view.
*
* @protected
* @type {string}
*/
selectRecordsView = 'views/modals/select-records';
/**
* Autocomplete disabled.
*
* @protected
* @type {boolean}
*/
autocompleteDisabled = false;
/**
* Create disabled.
*
* @protected
* @type {boolean}
*/
createDisabled = false;
/**
* To display the create button.
*
* @protected
* @type {boolean}
*/
createButton = false;
/**
* Force create button even is disabled in clientDefs > relationshipPanels.
*
* @protected
* @type {boolean}
*/
forceCreateButton = false;
/**
* A search type list.
*
* @protected
* @type {string[]}
*/
searchTypeList = ['is', 'isEmpty', 'isNotEmpty', 'isNot', 'isOneOf', 'isNotOneOf'];
/**
* A primary filter list that will be available when selecting a record.
*
* @protected
* @type {string[]|null}
*/
selectFilterList = null;
/**
* A select primary filter.
*
* @protected
* @type {string|null}
*/
selectPrimaryFilterName = null;
/**
* A select bool filter list.
*
* @protected
* @type {string[]|null}
*/
selectBoolFilterList = null;
/**
* An autocomplete max record number.
*
* @protected
* @type {number|null}
*/
autocompleteMaxCount = null;
/**
* Select all attributes.
*
* @protected
* @type {boolean}
*/
forceSelectAllAttributes = false;
/**
* @protected
* @type {string[]|null}
*/
mandatorySelectAttributeList = null;
/**
* Trigger autocomplete on empty input.
*
* @protected
* @type {boolean}
*/
autocompleteOnEmpty = false;
/**
* A link element class name. Applicable in the detail mode.
*
* @protected
* @since 9.1.6
*/
linkClass;
/**
* @protected
* @type {string}
* @since 9.2.5
*/
foreignNameAttribute;
/** @inheritDoc */
events = {
/** @this LinkFieldView */
'auxclick a[href]:not([role="button"])': function (e) {
if (!this.isReadMode()) {
return;
}
const isCombination = e.button === 1 && (e.ctrlKey || e.metaKey);
if (!isCombination) {
return;
}
e.preventDefault();
e.stopPropagation();
this.quickView();
}
};
// noinspection JSCheckFunctionSignatures
/** @inheritDoc */
data() {
let nameValue = this.model.has(this.nameName) ? this.model.get(this.nameName) : this.model.get(this.idName);
if (nameValue === null) {
nameValue = this.model.get(this.idName);
}
if (this.isReadMode() && !nameValue && this.model.get(this.idName)) {
nameValue = this.translate(this.foreignScope, 'scopeNames');
}
let iconHtml = null;
if (this.isDetailMode() || this.isListMode()) {
iconHtml = this.getHelper().getScopeColorIconHtml(this.foreignScope);
}
const createButton = this.createButton && (!this.createDisabled || this.forceCreateButton);
// noinspection JSValidateTypes
return {
...super.data(),
idName: this.idName,
nameName: this.nameName,
idValue: this.model.get(this.idName),
nameValue: nameValue,
foreignScope: this.foreignScope,
valueIsSet: this.model.has(this.idName),
iconHtml: iconHtml,
url: this.getUrl(),
createButton: createButton,
linkClass: this.linkClass
};
}
/**
* @protected
* @return {?string}
*/
getUrl() {
const id = this.model.get(this.idName);
if (!id) {
return null;
}
return '#' + this.foreignScope + '/view/' + id;
}
/**
* Get advanced filters (field filters) to be applied when select a record.
* Can be extended.
*
* @protected
* @return {Object.|null}
*/
getSelectFilters() {
return null;
}
/**
* Get a select bool filter list. Applied when select a record.
* Can be extended.
*
* @protected
* @return {string[]|null}
*/
getSelectBoolFilterList() {
return this.selectBoolFilterList;
}
/**
* Get a select primary filter. Applied when select a record.
* Can be extended.
*
* @protected
* @return {string|null}
*/
getSelectPrimaryFilterName() {
return this.selectPrimaryFilterName;
}
/**
* Get a primary filter list that will be available when selecting a record.
* Can be extended.
*
* @return {string[]|null}
*/
getSelectFilterList() {
return this.selectFilterList;
}
/**
* Attributes to pass to a model when creating a new record.
* Can be extended.
*
* @return {Object.|null}
*/
getCreateAttributes() {
const attributeMap = this.getMetadata().get(['clientDefs', this.entityType, 'relationshipPanels', this.name, 'createAttributeMap']) || {};
const attributes = {};
Object.keys(attributeMap).forEach(attr => attributes[attributeMap[attr]] = this.model.get(attr));
return attributes;
}
/** @inheritDoc */
setup() {
this.nameName = this.name + 'Name';
this.idName = this.name + 'Id';
this.foreignScope = this.options.foreignScope || this.foreignScope;
this.foreignScope = this.foreignScope || this.params.entity || this.model.getFieldParam(this.name, 'entity') || this.model.getLinkParam(this.name, 'entity');
this.foreignNameAttribute = this.model.getLinkParam(this.name, 'foreignName') ?? this.getMetadata().get(`clientDefs.${this.foreignScope}.nameAttribute`) ?? 'name';
if ('createDisabled' in this.options) {
this.createDisabled = this.options.createDisabled;
}
if (!this.isListMode()) {
this.addActionHandler('selectLink', () => this.actionSelect());
this.addActionHandler('clearLink', () => this.clearLink());
}
if (this.isSearchMode()) {
this.addActionHandler('selectLinkOneOf', () => this.actionSelectOneOf());
this.addActionHandler('clearLinkOneOf', (e, target) => this.deleteLinkOneOf(target.dataset.id));
}
this.autocompleteOnEmpty = this.params.autocompleteOnEmpty || this.autocompleteOnEmpty;
this.createButton = this.params.createButton || this.createButton;
if (this.createButton && !this.getAcl().checkScope(this.foreignScope, 'create')) {
this.createButton = false;
}
if (this.createButton) {
this.addActionHandler('createLink', () => this.actionCreateLink());
}
/** @type {Object.} */
this.panelDefs = this.getMetadata().get(['clientDefs', this.entityType, 'relationshipPanels', this.name]) || {};
if (this.panelDefs.createDisabled) {
this.createDisabled = true;
}
}
/**
* Select.
*
* @param {import('model').default} model A model.
* @protected
* @return {Promise|void}
*/
select(model) {
this.$elementName.val(model.get(this.foreignNameAttribute) || model.id);
this.$elementId.val(model.id);
if (this.mode === this.MODE_SEARCH) {
this.searchData.idValue = model.id;
this.searchData.nameValue = model.get(this.foreignNameAttribute) || model.id;
}
this.trigger('change');
this.controlCreateButtonVisibility();
const attributes = {};
for (const [foreign, field] of Object.entries(this.getDependantForeignMap())) {
attributes[field] = model.get(foreign);
}
this.getSelectFieldHandler().then(async handler => {
this.model.set({
...attributes,
...(await handler.getAttributes(model))
}, {
fromField: this.name
});
});
}
/**
* Clear.
*/
clearLink() {
this.$elementName.val('');
this.$elementId.val('');
this.trigger('change');
this.controlCreateButtonVisibility();
for (const [, field] of Object.entries(this.getDependantForeignMap())) {
this.model.unset(field, {
fromField: this.name
});
}
this.getSelectFieldHandler().then(handler => {
handler.getClearAttributes().then(attributes => {
this.model.set(attributes, {
fromField: this.name
});
});
});
}
/** @private */
controlCreateButtonVisibility() {
if (!this.createButton) {
return;
}
const $btn = this.$el.find('[data-action="createLink"]');
this.model.get(this.idName) ? $btn.addClass('hidden') : $btn.removeClass('hidden');
}
/**
* @private
* @return {Promise<{
* getAttributes: function (module:model): Promise>,
* getClearAttributes: function(): Promise>,
* }>}
*/
getSelectFieldHandler() {
if (!this.panelDefs.selectFieldHandler) {
return Promise.resolve({
getClearAttributes: () => Promise.resolve({}),
getAttributes: () => Promise.resolve({})
});
}
return new Promise(resolve => {
Espo.loader.requirePromise(this.panelDefs.selectFieldHandler).then(Handler => {
// Model is passed as of v8.2.
const handler = new Handler(this.getHelper(), this.model);
resolve(handler);
});
});
}
/** @inheritDoc */
setupSearch() {
this.searchData.oneOfIdList = this.getSearchParamsData().oneOfIdList || this.searchParams.oneOfIdList || [];
this.searchData.oneOfNameHash = this.getSearchParamsData().oneOfNameHash || this.searchParams.oneOfNameHash || {};
if (~['is', 'isNot', 'equals'].indexOf(this.getSearchType())) {
this.searchData.idValue = this.getSearchParamsData().idValue || this.searchParams.idValue || this.searchParams.value;
this.searchData.nameValue = this.getSearchParamsData().nameValue || this.searchParams.nameValue || this.searchParams.valueName;
}
this.events['change select.search-type'] = e => {
const type = $(e.currentTarget).val();
this.handleSearchType(type);
};
}
/**
* Handle a search type.
*
* @protected
* @param {string} type A type.
*/
handleSearchType(type) {
if (~['is', 'isNot', 'isNotAndIsNotEmpty'].indexOf(type)) {
this.$el.find('div.primary').removeClass('hidden');
} else {
this.$el.find('div.primary').addClass('hidden');
}
if (~['isOneOf', 'isNotOneOf', 'isNotOneOfAndIsNotEmpty'].indexOf(type)) {
this.$el.find('div.one-of-container').removeClass('hidden');
} else {
this.$el.find('div.one-of-container').addClass('hidden');
}
}
/**
* Get an autocomplete max record number. Can be extended.
*
* @protected
* @return {number}
*/
getAutocompleteMaxCount() {
if (this.autocompleteMaxCount) {
return this.autocompleteMaxCount;
}
return this.getConfig().get('recordsPerPage');
}
/**
* @private
* @return {string[]}
*/
getMandatorySelectAttributeList() {
const list = this.mandatorySelectAttributeList || this.panelDefs.selectMandatoryAttributeList || [];
const map = this.getDependantForeignMap();
return [...list, ...Object.keys(map)];
}
/**
* @private
* @return {Record}
*/
getDependantForeignMap() {
if (this._dependantForeignMap) {
return this._dependantForeignMap;
}
const map = {};
this.model.getFieldList().filter(it => {
return this.model.getFieldType(it) === 'foreign' && this.model.getFieldParam(it, 'link') === this.name;
}).forEach(field => {
const foreign = this.model.getFieldParam(field, 'field');
if (!foreign) {
return;
}
map[foreign] = field;
});
this._dependantForeignMap = map;
return map;
}
// noinspection JSUnusedLocalSymbols
/**
* Compose an autocomplete URL. Can be extended.
*
* @protected
* @param {string} [q] A query.
* @return {string|Promise}
*/
getAutocompleteUrl(q) {
let url = this.foreignScope + '?maxSize=' + this.getAutocompleteMaxCount();
if (!this.forceSelectAllAttributes) {
const mandatorySelectAttributeList = this.getMandatorySelectAttributeList();
let select = ['id', this.foreignNameAttribute];
if (mandatorySelectAttributeList) {
select = select.concat(mandatorySelectAttributeList);
}
url += '&select=' + select.join(',');
}
if (this.panelDefs.selectHandler) {
return new Promise(resolve => {
this._getSelectFilters().then(filters => {
if (filters.bool) {
url += '&' + $.param({
'boolFilterList': filters.bool
});
}
if (filters.primary) {
url += '&' + $.param({
'primaryFilter': filters.primary
});
}
if (filters.advanced && Object.keys(filters.advanced).length) {
url += '&' + $.param({
'where': filters.advanced
});
}
const orderBy = filters.orderBy || this.panelDefs.selectOrderBy;
const orderDirection = filters.orderBy ? filters.order : this.panelDefs.selectOrderDirection;
if (orderBy) {
url += '&' + $.param({
orderBy: orderBy,
order: orderDirection || 'asc'
});
}
resolve(url);
});
});
}
const boolList = [...(this.getSelectBoolFilterList() || []), ...(this.panelDefs.selectBoolFilterList || [])];
const primary = this.getSelectPrimaryFilterName() || this.panelDefs.selectPrimaryFilterName;
if (boolList.length) {
url += '&' + $.param({
'boolFilterList': boolList
});
}
if (primary) {
url += '&' + $.param({
'primaryFilter': primary
});
}
if (this.panelDefs.selectOrderBy) {
const direction = this.panelDefs.selectOrderDirection || 'asc';
url += '&' + $.param({
orderBy: this.panelDefs.selectOrderBy,
order: direction
});
}
return url;
}
/** @inheritDoc */
afterRender() {
if (this.isEditMode() || this.isSearchMode()) {
this.$elementId = this.$el.find('input[data-name="' + this.idName + '"]');
this.$elementName = this.$el.find('input[data-name="' + this.nameName + '"]');
this.$elementName.on('change', () => {
if (this.$elementName.val() === '') {
this.clearLink();
}
});
this.$elementName.on('blur', e => {
setTimeout(() => {
if (this.mode === this.MODE_EDIT && this.model.has(this.nameName)) {
e.currentTarget.value = this.model.get(this.nameName) || this.model.get(this.idName);
}
}, 100);
});
const $elementName = this.$elementName;
if (!this.autocompleteDisabled) {
/** @type {module:ajax.AjaxPromise & Promise} */
let lastAjaxPromise;
const autocomplete = new _autocomplete.default(this.$elementName.get(0), {
name: this.name,
handleFocusMode: 2,
autoSelectFirst: true,
forceHide: true,
triggerSelectOnValidInput: false,
catchFastEnter: true,
onSelect: item => {
this.getModelFactory().create(this.foreignScope, async model => {
model.set(item.attributes);
await (this.select(model) ?? (await Promise.resolve()));
this.$elementName.focus();
});
},
lookupFunction: query => {
if (!this.autocompleteOnEmpty && query.length === 0) {
const onEmptyPromise = this.getOnEmptyAutocomplete();
if (onEmptyPromise) {
return onEmptyPromise.then(list => this._transformAutocompleteResult({
list: list
}));
}
return Promise.resolve([]);
}
return Promise.resolve(this.getAutocompleteUrl(query)).then(url => {
if (lastAjaxPromise && lastAjaxPromise.getReadyState() < 4) {
lastAjaxPromise.abort();
}
lastAjaxPromise = Espo.Ajax.getRequest(url, {
q: query
});
return lastAjaxPromise;
}).then(response => this._transformAutocompleteResult(response));
}
});
this.once('render remove', () => autocomplete.dispose());
if (this.isSearchMode()) {
const $elementOneOf = this.$el.find('input.element-one-of');
/** @type {module:ajax.AjaxPromise & Promise} */
let lastAjaxPromise;
const autocomplete = new _autocomplete.default($elementOneOf.get(0), {
minChars: 1,
focusOnSelect: true,
handleFocusMode: 3,
autoSelectFirst: true,
triggerSelectOnValidInput: false,
forceHide: true,
onSelect: item => {
this.getModelFactory().create(this.foreignScope, model => {
model.set(item.attributes);
this.selectOneOf([model]);
$elementOneOf.val('');
setTimeout(() => $elementOneOf.focus(), 50);
});
},
lookupFunction: query => {
return Promise.resolve(this.getAutocompleteUrl(query)).then(url => {
if (lastAjaxPromise && lastAjaxPromise.getReadyState() < 4) {
lastAjaxPromise.abort();
}
lastAjaxPromise = Espo.Ajax.getRequest(url, {
q: query
});
return lastAjaxPromise;
}).then(/** {list: Record[]} */response => {
return response.list.map(item => ({
value: item.name,
attributes: item
}));
});
}
});
this.once('render remove', () => autocomplete.dispose());
this.$el.find('select.search-type').on('change', () => {
this.trigger('change');
});
}
}
$elementName.on('change', () => {
if (!this.isSearchMode() && !this.model.get(this.idName)) {
$elementName.val(this.model.get(this.nameName));
}
});
}
if (this.isSearchMode()) {
const type = this.$el.find('select.search-type').val();
this.handleSearchType(type);
if (~['isOneOf', 'isNotOneOf', 'isNotOneOfAndIsNotEmpty'].indexOf(type)) {
this.searchData.oneOfIdList.forEach(id => {
this.addLinkOneOfHtml(id, this.searchData.oneOfNameHash[id]);
});
}
}
}
/**
* @private
*/
_transformAutocompleteResult(response) {
const list = [];
response.list.forEach(item => {
const name = item[this.foreignNameAttribute] || item.name || item.id;
const attributes = item;
if (this.foreignNameAttribute !== 'name') {
attributes[this.foreignNameAttribute] = name;
}
list.push({
id: item.id,
name: name,
data: item.id,
value: name,
attributes: attributes
});
});
return list;
}
/** @inheritDoc */
getValueForDisplay() {
return this.model.get(this.nameName);
}
/** @inheritDoc */
validateRequired() {
if (this.isRequired()) {
if (this.model.get(this.idName) == null) {
const msg = this.translate('fieldIsRequired', 'messages').replace('{field}', this.getLabelText());
this.showValidationMessage(msg);
return true;
}
}
}
/**
* Delete a one-of item. For search mode.
*
* @param {string} id An ID.
*/
deleteLinkOneOf(id) {
this.deleteLinkOneOfHtml(id);
const index = this.searchData.oneOfIdList.indexOf(id);
if (index > -1) {
this.searchData.oneOfIdList.splice(index, 1);
}
delete this.searchData.oneOfNameHash[id];
this.trigger('change');
}
/**
* Add a one-of item. For search mode.
*
* @param {string} id An ID.
* @param {string} name A name.
*/
addLinkOneOf(id, name) {
if (!~this.searchData.oneOfIdList.indexOf(id)) {
this.searchData.oneOfIdList.push(id);
this.searchData.oneOfNameHash[id] = name;
this.addLinkOneOfHtml(id, name);
this.trigger('change');
}
}
/**
* @protected
* @param {string} id An ID.
*/
deleteLinkOneOfHtml(id) {
this.$el.find('.link-one-of-container .link-' + id).remove();
}
/**
* @protected
* @param {string} id An ID.
* @param {string} name A name.
* @return {JQuery}
*/
addLinkOneOfHtml(id, name) {
const $container = this.$el.find('.link-one-of-container');
const $el = $('').addClass('link-' + id).addClass('list-group-item');
$el.append($('
').attr('role', 'button').addClass('pull-right').attr('data-id', id).attr('data-action', 'clearLinkOneOf').append($('').addClass('fas fa-times')), $('').text(name), ' ');
$container.append($el);
return $el;
}
/** @inheritDoc */
fetch() {
const data = {};
data[this.nameName] = this.$el.find('[data-name="' + this.nameName + '"]').val() || null;
data[this.idName] = this.$el.find('[data-name="' + this.idName + '"]').val() || null;
return data;
}
/** @inheritDoc */
fetchSearch() {
const type = this.$el.find('select.search-type').val();
const value = this.$el.find('[data-name="' + this.idName + '"]').val();
if (~['isOneOf', 'isNotOneOf'].indexOf(type) && !this.searchData.oneOfIdList.length) {
return {
type: 'isNotNull',
attribute: 'id',
data: {
type: type
}
};
}
if (type === 'isEmpty') {
return {
type: 'isNull',
attribute: this.idName,
data: {
type: type
}
};
}
if (type === 'isNotEmpty') {
return {
type: 'isNotNull',
attribute: this.idName,
data: {
type: type
}
};
}
if (type === 'isOneOf') {
return {
type: 'in',
attribute: this.idName,
value: this.searchData.oneOfIdList,
data: {
type: type,
oneOfIdList: this.searchData.oneOfIdList,
oneOfNameHash: this.searchData.oneOfNameHash
}
};
}
if (type === 'isNotOneOf') {
return {
type: 'or',
value: [{
type: 'notIn',
attribute: this.idName,
value: this.searchData.oneOfIdList
}, {
type: 'isNull',
attribute: this.idName
}],
data: {
type: type,
oneOfIdList: this.searchData.oneOfIdList,
oneOfNameHash: this.searchData.oneOfNameHash
}
};
}
if (type === 'isNotOneOfAndIsNotEmpty') {
return {
type: 'notIn',
attribute: this.idName,
value: this.searchData.oneOfIdList,
data: {
type: type,
oneOfIdList: this.searchData.oneOfIdList,
oneOfNameHash: this.searchData.oneOfNameHash
}
};
}
if (type === 'isNot') {
if (!value) {
return false;
}
const nameValue = this.$el.find('[data-name="' + this.nameName + '"]').val();
return {
type: 'or',
value: [{
type: 'notEquals',
attribute: this.idName,
value: value
}, {
type: 'isNull',
attribute: this.idName
}],
data: {
type: type,
idValue: value,
nameValue: nameValue
}
};
}
if (type === 'isNotAndIsNotEmpty') {
if (!value) {
return false;
}
const nameValue = this.$el.find('[data-name="' + this.nameName + '"]').val();
return {
type: 'notEquals',
attribute: this.idName,
value: value,
data: {
type: type,
idValue: value,
nameValue: nameValue
}
};
}
if (!value) {
return false;
}
const nameValue = this.$el.find('[data-name="' + this.nameName + '"]').val();
return {
type: 'equals',
attribute: this.idName,
value: value,
data: {
type: type,
idValue: value,
nameValue: nameValue
}
};
}
/** @inheritDoc */
getSearchType() {
return this.getSearchParamsData().type || this.searchParams.typeFront || this.searchParams.type;
}
/**
* @protected
*/
quickView() {
const id = this.model.get(this.idName);
if (!id) {
return;
}
const entityType = this.foreignScope;
const helper = new _recordModal.default();
helper.showDetail(this, {
id: id,
entityType: entityType
});
}
/**
* @protected
* @return {function(): Promise>}
*/
getCreateAttributesProvider() {
return () => {
const attributes = this.getCreateAttributes() || {};
if (!this.panelDefs.createHandler) {
return Promise.resolve(attributes);
}
return new Promise(resolve => {
Espo.loader.requirePromise(this.panelDefs.createHandler).then(Handler => new Handler(this.getHelper())).then(/** import('handlers/create-related').default */handler => {
handler.getAttributes(this.model, this.name).then(additionalAttributes => {
resolve({
...attributes,
...additionalAttributes
});
});
});
});
};
}
/**
* @protected
*/
async actionSelect() {
const viewName = this.panelDefs.selectModalView || this.getMetadata().get(`clientDefs.${this.foreignScope}.modalViews.select`) || this.selectRecordsView;
const mandatorySelectAttributeList = this.getMandatorySelectAttributeList();
const createButton = this.isEditMode() && (!this.createDisabled || this.forceCreateButton);
const createAttributesProvider = createButton ? this.getCreateAttributesProvider() : null;
Espo.Ui.notifyWait();
const filters = await this._getSelectFilters();
const orderBy = filters.orderBy || this.panelDefs.selectOrderBy;
const orderDirection = filters.orderBy ? filters.order : this.panelDefs.selectOrderDirection;
/** @type {module:views/modals/select-records~Options} */
const options = {
entityType: this.foreignScope,
createButton: createButton,
filters: filters.advanced,
boolFilterList: filters.bool,
primaryFilterName: filters.primary,
mandatorySelectAttributeList: mandatorySelectAttributeList,
forceSelectAllAttributes: this.forceSelectAllAttributes,
filterList: this.getSelectFilterList(),
createAttributesProvider: createAttributesProvider,
layoutName: this.panelDefs.selectLayout,
orderBy: orderBy,
orderDirection: orderDirection,
onSelect: models => {
this.select(models[0]);
}
};
const view = await this.createView('modal', viewName, options);
await view.render();
Espo.Ui.notify();
}
/**
* @param {Object} advanced
* @private
*/
_applyAdditionalFilter(advanced) {
const foreignLink = this.model.getLinkParam(this.name, 'foreign');
if (!foreignLink) {
return;
}
if (advanced[foreignLink]) {
return;
}
const linkType = this.model.getLinkParam(this.name, 'type');
const foreignLinkType = this.getMetadata().get(['entityDefs', this.foreignScope, 'links', foreignLink, 'type']);
const foreignFieldType = this.getMetadata().get(['entityDefs', this.foreignScope, 'fields', foreignLink, 'type']);
if (!foreignFieldType) {
return;
}
const isOneToOne = (linkType === 'hasOne' || foreignLinkType === 'hasOne') && ['link', 'linkOne'].includes(foreignFieldType);
if (!isOneToOne) {
return;
}
advanced[foreignLink] = {
type: 'isNull',
attribute: foreignLink + 'Id',
data: {
type: 'isEmpty'
}
};
}
/**
* @private
* @return {Promise<{
* bool?: string[],
* advanced?: Object,
* primary?: string,
* orderBy?: string,
* order?: 'asc'|'desc',
* }>}
*/
_getSelectFilters() {
const handler = this.panelDefs.selectHandler;
const localBoolFilterList = this.getSelectBoolFilterList();
if (!handler || this.isSearchMode()) {
const boolFilterList = localBoolFilterList || this.panelDefs.selectBoolFilterList ? [...(localBoolFilterList || []), ...(this.panelDefs.selectBoolFilterList || [])] : undefined;
const advanced = this.getSelectFilters() || {};
this._applyAdditionalFilter(advanced);
return Promise.resolve({
primary: this.getSelectPrimaryFilterName() || this.panelDefs.selectPrimaryFilterName,
bool: boolFilterList,
advanced: advanced
});
}
return new Promise(resolve => {
Espo.loader.requirePromise(handler).then(Handler => new Handler(this.getHelper())).then(/** module:handlers/select-related */handler => {
return handler.getFilters(this.model);
}).then(filters => {
const advanced = {
...(this.getSelectFilters() || {}),
...(filters.advanced || {})
};
const primaryFilter = this.getSelectPrimaryFilterName() || filters.primary || this.panelDefs.selectPrimaryFilterName;
const boolFilterList = localBoolFilterList || filters.bool || this.panelDefs.selectBoolFilterList ? [...(localBoolFilterList || []), ...(filters.bool || []), ...(this.panelDefs.selectBoolFilterList || [])] : undefined;
this._applyAdditionalFilter(advanced);
const orderBy = filters.orderBy;
const order = orderBy ? filters.order : undefined;
resolve({
bool: boolFilterList,
primary: primaryFilter,
advanced: advanced,
orderBy: orderBy,
order: order
});
});
});
}
actionSelectOneOf() {
Espo.Ui.notifyWait();
const viewName = this.getMetadata().get(['clientDefs', this.foreignScope, 'modalViews', 'select']) || this.selectRecordsView;
this.createView('dialog', viewName, {
scope: this.foreignScope,
createButton: false,
filters: this.getSelectFilters(),
boolFilterList: this.getSelectBoolFilterList(),
primaryFilterName: this.getSelectPrimaryFilterName(),
multiple: true,
layoutName: this.panelDefs.selectLayout
}, view => {
view.render();
Espo.Ui.notify(false);
this.listenToOnce(view, 'select', models => {
this.clearView('dialog');
if (Object.prototype.toString.call(models) !== '[object Array]') {
models = [models];
}
this.selectOneOf(models);
});
});
}
/**
* Get an empty autocomplete result.
*
* @protected
* @return {Promise<[{name: ?string, id: string} & Record]>}
*/
getOnEmptyAutocomplete() {
return undefined;
}
/**
* @protected
*/
async actionCreateLink() {
const helper = new _recordModal.default();
const attributes = await this.getCreateAttributesProvider()();
await helper.showCreate(this, {
entityType: this.foreignScope,
fullFormDisabled: true,
attributes: attributes,
afterSave: model => this.select(model)
});
}
/**
* @protected
* @param {module:model[]} models
* @since 8.0.4
*/
selectOneOf(models) {
models.forEach(model => {
this.addLinkOneOf(model.id, model.get(this.foreignNameAttribute));
});
}
}
var _default = _exports.default = LinkFieldView;
});
define("views/fields/float", ["exports", "views/fields/int"], function (_exports, _int) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
_int = _interopRequireDefault(_int);
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* 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.
************************************************************************/
/** @module views/fields/float */
/**
* A float field.
*
* @extends IntFieldView
*/
class FloatFieldView extends _int.default {
/**
* @typedef {Object} module:views/fields/float~options
* @property {
* module:views/fields/float~params &
* module:views/fields/base~params &
* Record
* } [params] Parameters.
*/
/**
* @typedef {Object} module:views/fields/float~params
* @property {number} [min] A max value.
* @property {number} [max] A max value.
* @property {boolean} [required] Required.
* @property {boolean} [disableFormatting] Disable formatting.
* @property {number|null} [decimalPlaces] A number of decimal places.
*/
/**
* @param {
* module:views/fields/float~options &
* module:views/fields/base~options
* } options Options.
*/
constructor(options) {
super(options);
}
type = 'float';
editTemplate = 'fields/float/edit';
decimalMark = '.';
decimalPlacesRawValue = 10;
/**
* @inheritDoc
* @type {Array<(function (): boolean)|string>}
*/
validations = ['required', 'float', 'range'];
/** @inheritDoc */
setup() {
super.setup();
if (this.getPreferences().has('decimalMark')) {
this.decimalMark = this.getPreferences().get('decimalMark');
} else if (this.getConfig().has('decimalMark')) {
this.decimalMark = this.getConfig().get('decimalMark');
}
if (!this.decimalMark) {
this.decimalMark = '.';
}
if (this.decimalMark === this.thousandSeparator) {
this.thousandSeparator = '';
}
}
/** @inheritDoc */
setupAutoNumericOptions() {
// noinspection JSValidateTypes
this.autoNumericOptions = {
digitGroupSeparator: this.thousandSeparator || '',
decimalCharacter: this.decimalMark,
modifyValueOnWheel: false,
selectOnFocus: false,
decimalPlaces: this.decimalPlacesRawValue,
decimalPlacesRawValue: this.decimalPlacesRawValue,
allowDecimalPadding: false,
showWarnings: false,
formulaMode: true
};
}
getValueForDisplay() {
const value = isNaN(this.model.get(this.name)) ? null : this.model.get(this.name);
return this.formatNumber(value);
}
formatNumber(value) {
if (this.disableFormatting) {
return value;
}
return this.formatNumberDetail(value);
}
formatNumberDetail(value) {
if (value === null) {
return '';
}
const decimalPlaces = this.params.decimalPlaces;
if (decimalPlaces === 0) {
value = Math.round(value);
} else if (decimalPlaces) {
value = Math.round(value * Math.pow(10, decimalPlaces)) / Math.pow(10, decimalPlaces);
}
const parts = value.toString().split(".");
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, this.thousandSeparator);
if (decimalPlaces === 0) {
return parts[0];
} else if (decimalPlaces) {
let decimalPartLength = 0;
if (parts.length > 1) {
decimalPartLength = parts[1].length;
} else {
parts[1] = '';
}
if (decimalPlaces && decimalPartLength < decimalPlaces) {
const limit = decimalPlaces - decimalPartLength;
for (let i = 0; i < limit; i++) {
parts[1] += '0';
}
}
}
return parts.join(this.decimalMark);
}
validateFloat() {
const value = this.model.get(this.name);
if (isNaN(value)) {
const msg = this.translate('fieldShouldBeFloat', 'messages').replace('{field}', this.getLabelText());
this.showValidationMessage(msg);
return true;
}
}
parse(value) {
value = value !== '' ? value : null;
if (value === null) {
return null;
}
value = value.split(this.thousandSeparator).join('').split(this.decimalMark).join('.');
return parseFloat(value);
}
fetch() {
let value = this.$element.val();
value = this.parse(value);
const data = {};
data[this.name] = value;
return data;
}
}
var _default = _exports.default = FloatFieldView;
});
define("views/fields/date", ["exports", "views/fields/base", "moment", "ui/datepicker"], function (_exports, _base, _moment, _datepicker) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
_base = _interopRequireDefault(_base);
_moment = _interopRequireDefault(_moment);
_datepicker = _interopRequireDefault(_datepicker);
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* 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.
************************************************************************/
/** @module views/fields/date */
/**
* A date field.
*
* @extends BaseFieldView
*/
class DateFieldView extends _base.default {
/**
* @typedef {Object} module:views/fields/date~options
* @property {
* module:views/fields/date~params &
* module:views/fields/base~params &
* Record
* } [params] Parameters.
* @property {string} [otherFieldLabelText] A label text of other field. Used in before/after validations.
*/
/**
* @typedef {Object} module:views/fields/date~params
* @property {boolean} [required] Required.
* @property {boolean} [useNumericFormat] Use numeric format.
* @property {string} [after] Validate to be after another date field.
* @property {string} [before] Validate to be before another date field.
* @property {boolean} [afterOrEqual] Allow an equal date for 'after' validation.
*/
/**
* @param {
* module:views/fields/date~options &
* module:views/fields/base~options
* } options Options.
*/
constructor(options) {
super(options);
}
type = 'date';
listTemplate = 'fields/date/list';
listLinkTemplate = 'fields/date/list-link';
detailTemplate = 'fields/date/detail';
editTemplate = 'fields/date/edit';
searchTemplate = 'fields/date/search';
/**
* @inheritDoc
* @type {Array<(function (): boolean)|string>}
*/
validations = ['required', 'date', 'after', 'before'];
/**
* @protected
* @type {string[]}
*/
searchTypeList = ['lastSevenDays', 'ever', 'isEmpty', 'currentMonth', 'lastMonth', 'nextMonth', 'currentQuarter', 'lastQuarter', 'currentYear', 'lastYear', 'today', 'past', 'future', 'lastXDays', 'nextXDays', 'olderThanXDays', 'afterXDays', 'on', 'after', 'before', 'between'];
/**
* @protected
* @type {string[]}
*/
searchWithPrimaryTypeList = ['on', 'notOn', 'after', 'before'];
/**
* @protected
* @type {string[]}
*/
searchWithRangeTypeList = ['between'];
/**
* @protected
* @type {string[]}
*/
searchWithAdditionalNumberTypeList = ['lastXDays', 'nextXDays', 'olderThanXDays', 'afterXDays'];
/**
* @inheritDoc
*/
initialSearchIsNotIdle = true;
/**
* @private
* @type {import('ui/datepicker').default}
*/
datepicker;
/**
* @protected
* @type {boolean}
*/
useNumericFormat;
setup() {
super.setup();
if (this.getConfig().get('fiscalYearShift')) {
this.searchTypeList = Espo.Utils.clone(this.searchTypeList);
if (this.getConfig().get('fiscalYearShift') % 3 !== 0) {
this.searchTypeList.push('currentFiscalQuarter');
this.searchTypeList.push('lastFiscalQuarter');
}
this.searchTypeList.push('currentFiscalYear');
this.searchTypeList.push('lastFiscalYear');
}
if (this.params.after) {
this.listenTo(this.model, `change:${this.params.after}`, async () => {
if (!this.isEditMode()) {
return;
}
await this.whenRendered();
// Timeout prevents the picker popping one when the duration field adjusts the date end.
setTimeout(() => {
this.onAfterChange();
this.datepicker.setStartDate(this.getStartDateForDatePicker());
}, 100);
});
}
this.useNumericFormat = this.getConfig().get('readableDateFormatDisabled') || this.params.useNumericFormat;
}
// noinspection JSCheckFunctionSignatures
data() {
const data = super.data();
data.dateValue = this.getDateStringValue();
data.isNone = data.dateValue === null;
if (data.dateValue === -1) {
data.dateValue = null;
data.isLoading = true;
}
if (this.isSearchMode()) {
const value = this.getSearchParamsData().value || this.searchParams.dateValue;
const valueTo = this.getSearchParamsData().valueTo || this.searchParams.dateValueTo;
data.dateValue = this.getDateTime().toDisplayDate(value);
data.dateValueTo = this.getDateTime().toDisplayDate(valueTo);
if (this.searchWithAdditionalNumberTypeList.includes(this.getSearchType())) {
data.number = this.searchParams.value;
}
}
if (this.isListMode()) {
data.titleDateValue = data.dateValue;
}
if (this.useNumericFormat) {
data.useNumericFormat = true;
}
// noinspection JSValidateTypes
return data;
}
setupSearch() {
this.addHandler('change', 'select.search-type', (e, /** HTMLSelectElement */target) => {
this.handleSearchType(target.value);
this.trigger('change');
});
this.addHandler('change', 'input.number', () => this.trigger('change'));
}
stringifyDateValue(value) {
if (!value) {
if (this.mode === this.MODE_EDIT || this.mode === this.MODE_SEARCH || this.mode === this.MODE_LIST || this.mode === this.MODE_LIST_LINK) {
return '';
}
return null;
}
if (this.mode === this.MODE_LIST || this.mode === this.MODE_DETAIL || this.mode === this.MODE_LIST_LINK) {
return this.convertDateValueForDetail(value);
}
return this.getDateTime().toDisplayDate(value);
}
convertDateValueForDetail(value) {
if (this.useNumericFormat) {
return this.getDateTime().toDisplayDate(value);
}
const timezone = this.getDateTime().getTimeZone();
const internalDateTimeFormat = this.getDateTime().internalDateTimeFormat;
const readableFormat = this.getDateTime().getReadableDateFormat();
const valueWithTime = value + ' 00:00:00';
const today = _moment.default.tz(timezone).startOf('day');
let dateTime = _moment.default.tz(valueWithTime, internalDateTimeFormat, timezone);
const temp = today.clone();
const ranges = {
'today': [temp.unix(), temp.add(1, 'days').unix()],
'tomorrow': [temp.unix(), temp.add(1, 'days').unix()],
'yesterday': [temp.add(-3, 'days').unix(), temp.add(1, 'days').unix()]
};
if (dateTime.unix() >= ranges['today'][0] && dateTime.unix() < ranges['today'][1]) {
return this.translate('Today');
}
if (dateTime.unix() >= ranges['tomorrow'][0] && dateTime.unix() < ranges['tomorrow'][1]) {
return this.translate('Tomorrow');
}
if (dateTime.unix() >= ranges['yesterday'][0] && dateTime.unix() < ranges['yesterday'][1]) {
return this.translate('Yesterday');
}
// Need to use UTC, otherwise there's a DST issue with old dates.
dateTime = _moment.default.utc(valueWithTime, internalDateTimeFormat);
if (dateTime.format('YYYY') === today.format('YYYY')) {
return dateTime.format(readableFormat);
}
return dateTime.format(readableFormat + ', YYYY');
}
getDateStringValue() {
if (this.mode === this.MODE_DETAIL && !this.model.has(this.name)) {
return -1;
}
const value = this.model.get(this.name);
return this.stringifyDateValue(value);
}
/**
* @protected
* @return {string|undefined}
*/
getStartDateForDatePicker() {
if (!this.isEditMode() || !this.params.after) {
return undefined;
}
/** @type {string} */
let date = this.model.attributes[this.params.after];
if (date == null) {
return undefined;
}
if (date.length > 10) {
date = this.getDateTime().toDisplay(date);
[date] = date.split(' ');
return date;
}
return this.getDateTime().toDisplayDate(date);
}
afterRender() {
if (this.isEditMode() || this.isSearchMode()) {
var _this$element, _this$mainInputElemen;
this.mainInputElement = (_this$element = this.element) === null || _this$element === void 0 ? void 0 : _this$element.querySelector(`[data-name="${this.name}"]`);
this.$element = $(this.mainInputElement);
const options = {
format: this.getDateTime().dateFormat,
weekStart: this.getDateTime().weekStart,
startDate: this.getStartDateForDatePicker(),
todayButton: this.getConfig().get('datepickerTodayButton') || false
};
this.datepicker = undefined;
if (this.mainInputElement instanceof HTMLInputElement) {
this.datepicker = new _datepicker.default(this.mainInputElement, {
...options,
onChange: () => this.trigger('change')
});
}
if (this.isSearchMode()) {
var _this$element2;
const additionalGroup = (_this$element2 = this.element) === null || _this$element2 === void 0 ? void 0 : _this$element2.querySelector('.input-group.additional');
if (additionalGroup) {
new _datepicker.default(additionalGroup, options);
this.initDatePickerEventHandlers('input.filter-from');
this.initDatePickerEventHandlers('input.filter-to');
}
}
const button = (_this$mainInputElemen = this.mainInputElement) === null || _this$mainInputElemen === void 0 ? void 0 : _this$mainInputElemen.parentNode.querySelector('button.date-picker-btn');
if (button instanceof HTMLElement) {
button.addEventListener('click', () => this.datepicker.show());
}
if (this.isSearchMode()) {
const type = this.fetchSearchType();
this.handleSearchType(type);
}
}
}
/**
* @private
* @param {string} selector
*/
initDatePickerEventHandlers(selector) {
var _this$element3;
const input = (_this$element3 = this.element) === null || _this$element3 === void 0 ? void 0 : _this$element3.querySelector(selector);
if (!(input instanceof HTMLInputElement)) {
return;
}
$(input).on('change', /** Record */e => {
this.trigger('change');
if (e.isTrigger) {
if (document.activeElement !== input) {
input.focus({
preventScroll: true
});
}
}
});
}
/**
* @protected
* @param {string} type
*/
handleSearchType(type) {
var _this$element4, _this$element5, _this$element6;
const primary = (_this$element4 = this.element) === null || _this$element4 === void 0 ? void 0 : _this$element4.querySelector('div.primary');
const additional = (_this$element5 = this.element) === null || _this$element5 === void 0 ? void 0 : _this$element5.querySelector('div.additional');
const additionalNumber = (_this$element6 = this.element) === null || _this$element6 === void 0 ? void 0 : _this$element6.querySelector('div.additional-number');
primary === null || primary === void 0 || primary.classList.add('hidden');
additional === null || additional === void 0 || additional.classList.add('hidden');
additionalNumber === null || additionalNumber === void 0 || additionalNumber.classList.add('hidden');
if (this.searchWithPrimaryTypeList.includes(type)) {
primary === null || primary === void 0 || primary.classList.remove('hidden');
return;
}
if (this.searchWithAdditionalNumberTypeList.includes(type)) {
additionalNumber === null || additionalNumber === void 0 || additionalNumber.classList.remove('hidden');
return;
}
if (this.searchWithRangeTypeList.includes(type)) {
additional === null || additional === void 0 || additional.classList.remove('hidden');
}
}
/**
* @protected
* @param {string} string
* @return {string|-1}
*/
parseDate(string) {
return this.getDateTime().fromDisplayDate(string);
}
/**
* @param {string} string
* @return {string|-1|null}
*/
parse(string) {
if (!string) {
return null;
}
return this.parseDate(string);
}
/**
* @inheritDoc
*/
fetch() {
var _this$mainInputElemen2;
const data = {};
data[this.name] = this.parse(((_this$mainInputElemen2 = this.mainInputElement) === null || _this$mainInputElemen2 === void 0 ? void 0 : _this$mainInputElemen2.value) ?? '');
return data;
}
/**
* @inheritDoc
*/
fetchSearch() {
const type = this.fetchSearchType();
if (this.searchWithRangeTypeList.includes(type)) {
var _this$element7, _this$element8;
const inputFrom = (_this$element7 = this.element) === null || _this$element7 === void 0 ? void 0 : _this$element7.querySelector('input.filter-from');
const inputTo = (_this$element8 = this.element) === null || _this$element8 === void 0 ? void 0 : _this$element8.querySelector('input.filter-to');
const valueFrom = inputFrom instanceof HTMLInputElement ? this.parseDate(inputFrom.value) : undefined;
const valueTo = inputTo instanceof HTMLInputElement ? this.parseDate(inputTo.value) : undefined;
if (!valueFrom || !valueTo) {
return null;
}
return {
type: type,
value: [valueFrom, valueTo],
data: {
value: valueFrom,
valueTo: valueTo
}
};
}
if (this.searchWithAdditionalNumberTypeList.includes(type)) {
var _this$element9;
const input = (_this$element9 = this.element) === null || _this$element9 === void 0 ? void 0 : _this$element9.querySelector('input.number');
const number = input instanceof HTMLInputElement ? input.value : undefined;
return {
type: type,
value: number,
date: true
};
}
if (this.searchWithPrimaryTypeList.includes(type)) {
var _this$element0;
const input = (_this$element0 = this.element) === null || _this$element0 === void 0 ? void 0 : _this$element0.querySelector(`[data-name="${this.name}"]`);
const value = input instanceof HTMLInputElement ? this.parseDate(input.value) : undefined;
if (!value) {
return null;
}
return {
type: type,
value: value,
data: {
value: value
}
};
}
if (type === 'isEmpty') {
return {
type: 'isNull',
data: {
type: type
}
};
}
return {
type: type,
date: true
};
}
getSearchType() {
return this.getSearchParamsData().type || this.searchParams.typeFront || this.searchParams.type;
}
validateRequired() {
if (!this.isRequired()) {
return;
}
if (this.model.get(this.name) === null) {
const msg = this.translate('fieldIsRequired', 'messages').replace('{field}', this.getLabelText());
this.showValidationMessage(msg);
return true;
}
}
// noinspection JSUnusedGlobalSymbols
validateDate() {
if (this.model.get(this.name) === -1) {
const msg = this.translate('fieldShouldBeDate', 'messages').replace('{field}', this.getLabelText());
this.showValidationMessage(msg);
return true;
}
}
// noinspection JSUnusedGlobalSymbols
validateAfter() {
const field = this.params.after;
if (!field) {
return false;
}
const value = this.model.get(this.name);
const otherValue = this.model.get(field);
if (!(value && otherValue)) {
return false;
}
const unix = (0, _moment.default)(value).unix();
const otherUnix = (0, _moment.default)(otherValue).unix();
if (this.params.afterOrEqual && unix === otherUnix) {
return false;
}
if (unix <= otherUnix) {
const otherFieldLabelText = this.options.otherFieldLabelText || this.translate(field, 'fields', this.entityType);
const msg = this.translate('fieldShouldAfter', 'messages').replace('{field}', this.getLabelText()).replace('{otherField}', otherFieldLabelText);
this.showValidationMessage(msg);
return true;
}
return false;
}
// noinspection JSUnusedGlobalSymbols
validateBefore() {
const field = this.params.before;
if (!field) {
return false;
}
const value = this.model.get(this.name);
const otherValue = this.model.get(field);
if (!(value && otherValue)) {
return;
}
if ((0, _moment.default)(value).unix() >= (0, _moment.default)(otherValue).unix()) {
const msg = this.translate('fieldShouldBefore', 'messages').replace('{field}', this.getLabelText()).replace('{otherField}', this.translate(field, 'fields', this.entityType));
this.showValidationMessage(msg);
return true;
}
}
/**
* @protected
* @since 9.2.0
*/
onAfterChange() {
/** @type {string} */
const from = this.model.attributes[this.params.after];
/** @type {string} */
const currentValue = this.model.attributes[this.name];
if (!from || !currentValue || from.length !== currentValue.length) {
return;
}
if (this.getDateTime().toMomentDate(currentValue).isBefore(this.getDateTime().toMomentDate(from))) {
this.model.set(this.name, from);
}
}
}
var _default = _exports.default = DateFieldView;
});
define("views/detail/modes", ["exports", "view"], function (_exports, _view) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
_view = _interopRequireDefault(_view);
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* 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.
************************************************************************/
class DetailModesView extends _view.default {
// language=Handlebars
templateContent = `
`;
/** @private */
disabled = false;
/**
* @param {{
* modeList: string[],
* mode: string,
* scope: string.
* }} options
*/
constructor(options) {
super(options);
/** @private */
this.modeList = options.modeList;
/** @private */
this.mode = options.mode;
/** @private */
this.scope = options.scope;
/**
* @private
* @type {Object.}
*/
this.hiddenMap = {};
}
data() {
return {
disabled: this.disabled,
modeDataList: this.modeList.filter(mode => !this.hiddenMap[mode] || mode === this.mode).map(mode => ({
name: mode,
active: mode === this.mode,
label: this.translate(mode, 'detailViewModes', this.scope)
}))
};
}
/**
* Change mode.
*
* @param {string} mode
* @return {Promise}
*/
changeMode(mode) {
this.mode = mode;
return this.reRender();
}
/**
* Hide a mode.
*
* @param {string} mode
*/
async hideMode(mode) {
this.hiddenMap[mode] = true;
await this.reRender();
}
/**
* Show a mode.
*
* @param {string} mode
*/
async showMode(mode) {
delete this.hiddenMap[mode];
await this.reRender();
}
/**
* Disable.
*
* @return {Promise}
*/
disable() {
this.disabled = true;
return this.reRender();
}
/**
* Enable.
*
* @return {Promise}
*/
enable() {
this.disabled = false;
return this.reRender();
}
}
var _default = _exports.default = DetailModesView;
});
define("helpers/file-upload", ["exports", "di", "models/settings"], function (_exports, _di, _settings) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
_settings = _interopRequireDefault(_settings);
var _staticBlock;
let _init_config, _init_extra_config;
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* 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.
************************************************************************/
/** @module helpers/file-upload */
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _applyDecs(e, t, n, r, o, i) { var a, c, u, s, f, l, p, d = Symbol.metadata || Symbol.for("Symbol.metadata"), m = Object.defineProperty, h = Object.create, y = [h(null), h(null)], v = t.length; function g(t, n, r) { return function (o, i) { n && (i = o, o = e); for (var a = 0; a < t.length; a++) i = t[a].apply(o, r ? [i] : []); return r ? i : o; }; } function b(e, t, n, r) { if ("function" != typeof e && (r || void 0 !== e)) throw new TypeError(t + " must " + (n || "be") + " a function" + (r ? "" : " or undefined")); return e; } function applyDec(e, t, n, r, o, i, u, s, f, l, p) { function d(e) { if (!p(e)) throw new TypeError("Attempted to access private element on non-instance"); } var h = [].concat(t[0]), v = t[3], w = !u, D = 1 === o, S = 3 === o, j = 4 === o, E = 2 === o; function I(t, n, r) { return function (o, i) { return n && (i = o, o = e), r && r(o), P[t].call(o, i); }; } if (!w) { var P = {}, k = [], F = S ? "get" : j || D ? "set" : "value"; if (f ? (l || D ? P = { get: _setFunctionName(function () { return v(this); }, r, "get"), set: function (e) { t[4](this, e); } } : P[F] = v, l || _setFunctionName(P[F], r, E ? "" : F)) : l || (P = Object.getOwnPropertyDescriptor(e, r)), !l && !f) { if ((c = y[+s][r]) && 7 !== (c ^ o)) throw Error("Decorating two elements with the same name (" + P[F].name + ") is not supported yet"); y[+s][r] = o < 3 ? 1 : o; } } for (var N = e, O = h.length - 1; O >= 0; O -= n ? 2 : 1) { var T = b(h[O], "A decorator", "be", !0), z = n ? h[O - 1] : void 0, A = {}, H = { kind: ["field", "accessor", "method", "getter", "setter", "class"][o], name: r, metadata: a, addInitializer: function (e, t) { if (e.v) throw new TypeError("attempted to call addInitializer after decoration was finished"); b(t, "An initializer", "be", !0), i.push(t); }.bind(null, A) }; if (w) c = T.call(z, N, H), A.v = 1, b(c, "class decorators", "return") && (N = c);else if (H.static = s, H.private = f, c = H.access = { has: f ? p.bind() : function (e) { return r in e; } }, j || (c.get = f ? E ? function (e) { return d(e), P.value; } : I("get", 0, d) : function (e) { return e[r]; }), E || S || (c.set = f ? I("set", 0, d) : function (e, t) { e[r] = t; }), N = T.call(z, D ? { get: P.get, set: P.set } : P[F], H), A.v = 1, D) { if ("object" == typeof N && N) (c = b(N.get, "accessor.get")) && (P.get = c), (c = b(N.set, "accessor.set")) && (P.set = c), (c = b(N.init, "accessor.init")) && k.unshift(c);else if (void 0 !== N) throw new TypeError("accessor decorators must return an object with get, set, or init properties or undefined"); } else b(N, (l ? "field" : "method") + " decorators", "return") && (l ? k.unshift(N) : P[F] = N); } return o < 2 && u.push(g(k, s, 1), g(i, s, 0)), l || w || (f ? D ? u.splice(-1, 0, I("get", s), I("set", s)) : u.push(E ? P[F] : b.call.bind(P[F])) : m(e, r, P)), N; } function w(e) { return m(e, d, { configurable: !0, enumerable: !0, value: a }); } return void 0 !== i && (a = i[d]), a = h(null == a ? null : a), f = [], l = function (e) { e && f.push(g(e)); }, p = function (t, r) { for (var i = 0; i < n.length; i++) { var a = n[i], c = a[1], l = 7 & c; if ((8 & c) == t && !l == r) { var p = a[2], d = !!a[3], m = 16 & c; applyDec(t ? e : e.prototype, a, m, d ? "#" + p : _toPropertyKey(p), l, l < 2 ? [] : t ? s = s || [] : u = u || [], f, !!t, d, r, t && d ? function (t) { return _checkInRHS(t) === e; } : o); } } }, p(8, 0), p(0, 0), p(8, 1), p(0, 1), l(u), l(s), c = f, v || w(e), { e: c, get c() { var n = []; return v && [w(e = applyDec(e, [t], r, e.name, 5, n)), g(n, 1)]; } }; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
function _setFunctionName(e, t, n) { "symbol" == typeof t && (t = (t = t.description) ? "[" + t + "]" : ""); try { Object.defineProperty(e, "name", { configurable: !0, value: n ? n + " " + t : t }); } catch (e) {} return e; }
function _checkInRHS(e) { if (Object(e) !== e) throw TypeError("right-hand side of 'in' should be an object, got " + (null !== e ? typeof e : "null")); return e; }
/**
* A file-upload helper.
*/
class FileUploadExport {
constructor() {
_init_extra_config(this);
}
/**
* @private
* @type {Settings}
*/
config = _init_config(this);
/**
* @typedef {Object} module:helpers/file-upload~Options
*
* @property {function(number):void} [afterChunkUpload] After every chunk is uploaded.
* @property {function(module:model):void} [afterAttachmentSave] After an attachment is saved.
* @property {{isCanceled?: boolean}} [mediator] A mediator.
*/
/**
* Upload.
*
* @param {File} file A file.
* @param {module:model} attachment An attachment model.
* @param {module:helpers/file-upload~Options} [options] Options.
* @returns {Promise}
*/
upload(file, attachment, options) {
options = options || {};
options.afterChunkUpload = options.afterChunkUpload || (() => {});
options.afterAttachmentSave = options.afterAttachmentSave || (() => {});
options.mediator = options.mediator || {};
attachment.set('name', file.name);
attachment.set('type', file.type || 'text/plain');
attachment.set('size', file.size);
if (this._useChunks(file)) {
return this._uploadByChunks(file, attachment, options);
}
return new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.onload = e => {
attachment.set('file', e.target.result);
attachment.save({}, {
timeout: 0
}).then(() => resolve()).catch(() => reject());
};
fileReader.readAsDataURL(file);
});
}
/**
* @private
*/
_uploadByChunks(file, attachment, options) {
return new Promise((resolve, reject) => {
attachment.set('isBeingUploaded', true);
attachment.save().then(() => {
options.afterAttachmentSave(attachment);
return this._uploadChunks(file, attachment, resolve, reject, options);
}).catch(() => reject());
});
}
/**
* @private
*/
_uploadChunks(file, attachment, resolve, reject, options, start) {
start = start || 0;
let end = start + this._getChunkSize() + 1;
if (end > file.size) {
end = file.size;
}
if (options.mediator.isCanceled) {
reject();
return;
}
const blob = file.slice(start, end);
const fileReader = new FileReader();
fileReader.onloadend = e => {
if (e.target.readyState !== FileReader.DONE) {
return;
}
Espo.Ajax.postRequest('Attachment/chunk/' + attachment.id, e.target.result, {
headers: {
contentType: 'multipart/form-data'
}
}).then(() => {
options.afterChunkUpload(end);
if (end === file.size) {
resolve();
return;
}
this._uploadChunks(file, attachment, resolve, reject, options, end);
}).catch(() => reject());
};
fileReader.readAsDataURL(blob);
}
/**
* @private
*/
_useChunks(file) {
const chunkSize = this._getChunkSize();
if (!chunkSize) {
return false;
}
if (file.size > chunkSize) {
return true;
}
return false;
}
/**
* @private
*/
_getChunkSize() {
return (this.config.get('attachmentUploadChunkSize') || 0) * 1024 * 1024;
}
static #_ = _staticBlock = () => [_init_config, _init_extra_config] = _applyDecs(this, [], [[(0, _di.inject)(_settings.default), 0, "config"]]).e;
}
_staticBlock();
var _default = _exports.default = FileUploadExport;
});
define("helpers/record/select-related", ["exports", "di", "metadata"], function (_exports, _di, _metadata) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
_metadata = _interopRequireDefault(_metadata);
var _staticBlock;
let _init_metadata, _init_extra_metadata;
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* 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.
************************************************************************/
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _applyDecs(e, t, n, r, o, i) { var a, c, u, s, f, l, p, d = Symbol.metadata || Symbol.for("Symbol.metadata"), m = Object.defineProperty, h = Object.create, y = [h(null), h(null)], v = t.length; function g(t, n, r) { return function (o, i) { n && (i = o, o = e); for (var a = 0; a < t.length; a++) i = t[a].apply(o, r ? [i] : []); return r ? i : o; }; } function b(e, t, n, r) { if ("function" != typeof e && (r || void 0 !== e)) throw new TypeError(t + " must " + (n || "be") + " a function" + (r ? "" : " or undefined")); return e; } function applyDec(e, t, n, r, o, i, u, s, f, l, p) { function d(e) { if (!p(e)) throw new TypeError("Attempted to access private element on non-instance"); } var h = [].concat(t[0]), v = t[3], w = !u, D = 1 === o, S = 3 === o, j = 4 === o, E = 2 === o; function I(t, n, r) { return function (o, i) { return n && (i = o, o = e), r && r(o), P[t].call(o, i); }; } if (!w) { var P = {}, k = [], F = S ? "get" : j || D ? "set" : "value"; if (f ? (l || D ? P = { get: _setFunctionName(function () { return v(this); }, r, "get"), set: function (e) { t[4](this, e); } } : P[F] = v, l || _setFunctionName(P[F], r, E ? "" : F)) : l || (P = Object.getOwnPropertyDescriptor(e, r)), !l && !f) { if ((c = y[+s][r]) && 7 !== (c ^ o)) throw Error("Decorating two elements with the same name (" + P[F].name + ") is not supported yet"); y[+s][r] = o < 3 ? 1 : o; } } for (var N = e, O = h.length - 1; O >= 0; O -= n ? 2 : 1) { var T = b(h[O], "A decorator", "be", !0), z = n ? h[O - 1] : void 0, A = {}, H = { kind: ["field", "accessor", "method", "getter", "setter", "class"][o], name: r, metadata: a, addInitializer: function (e, t) { if (e.v) throw new TypeError("attempted to call addInitializer after decoration was finished"); b(t, "An initializer", "be", !0), i.push(t); }.bind(null, A) }; if (w) c = T.call(z, N, H), A.v = 1, b(c, "class decorators", "return") && (N = c);else if (H.static = s, H.private = f, c = H.access = { has: f ? p.bind() : function (e) { return r in e; } }, j || (c.get = f ? E ? function (e) { return d(e), P.value; } : I("get", 0, d) : function (e) { return e[r]; }), E || S || (c.set = f ? I("set", 0, d) : function (e, t) { e[r] = t; }), N = T.call(z, D ? { get: P.get, set: P.set } : P[F], H), A.v = 1, D) { if ("object" == typeof N && N) (c = b(N.get, "accessor.get")) && (P.get = c), (c = b(N.set, "accessor.set")) && (P.set = c), (c = b(N.init, "accessor.init")) && k.unshift(c);else if (void 0 !== N) throw new TypeError("accessor decorators must return an object with get, set, or init properties or undefined"); } else b(N, (l ? "field" : "method") + " decorators", "return") && (l ? k.unshift(N) : P[F] = N); } return o < 2 && u.push(g(k, s, 1), g(i, s, 0)), l || w || (f ? D ? u.splice(-1, 0, I("get", s), I("set", s)) : u.push(E ? P[F] : b.call.bind(P[F])) : m(e, r, P)), N; } function w(e) { return m(e, d, { configurable: !0, enumerable: !0, value: a }); } return void 0 !== i && (a = i[d]), a = h(null == a ? null : a), f = [], l = function (e) { e && f.push(g(e)); }, p = function (t, r) { for (var i = 0; i < n.length; i++) { var a = n[i], c = a[1], l = 7 & c; if ((8 & c) == t && !l == r) { var p = a[2], d = !!a[3], m = 16 & c; applyDec(t ? e : e.prototype, a, m, d ? "#" + p : _toPropertyKey(p), l, l < 2 ? [] : t ? s = s || [] : u = u || [], f, !!t, d, r, t && d ? function (t) { return _checkInRHS(t) === e; } : o); } } }, p(8, 0), p(0, 0), p(8, 1), p(0, 1), l(u), l(s), c = f, v || w(e), { e: c, get c() { var n = []; return v && [w(e = applyDec(e, [t], r, e.name, 5, n)), g(n, 1)]; } }; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
function _setFunctionName(e, t, n) { "symbol" == typeof t && (t = (t = t.description) ? "[" + t + "]" : ""); try { Object.defineProperty(e, "name", { configurable: !0, value: n ? n + " " + t : t }); } catch (e) {} return e; }
function _checkInRHS(e) { if (Object(e) !== e) throw TypeError("right-hand side of 'in' should be an object, got " + (null !== e ? typeof e : "null")); return e; }
/**
* @internal
*/
class SelectRelatedHelper {
/**
* @private
* @type {Metadata}
*/
metadata = _init_metadata(this);
/**
* @param {import('view').default} view
*/
constructor(view) {
_init_extra_metadata(this);
/** @private */
this.view = view;
}
/**
* @param {import('model').default} model
* @param {string} link
* @param {{
* foreignEntityType?: string,
* massSelect?: boolean,
* primaryFilterName?: string,
* boolFilterList?: string[]|string,
* viewKey?: string,
* hasCreate?: boolean,
* onCreate?: function(): void,
* }} options
*/
process(model, link) {
let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
if (!options.foreignEntityType && !model.defs['links'][link]) {
throw new Error(`Link ${link} does not exist.`);
}
const scope = options.foreignEntityType || model.defs['links'][link].entity;
/** @var {Object.} */
const panelDefs = this.metadata.get(['clientDefs', model.entityType, 'relationshipPanels', link]) || {};
const massRelateEnabled = options.massSelect || panelDefs.massSelect;
let advanced = {};
const foreignLink = model.getLinkParam(link, 'foreign');
if (foreignLink && scope) {
// Select only records not related with any.
const foreignLinkType = this.metadata.get(['entityDefs', scope, 'links', foreignLink, 'type']);
const foreignLinkFieldType = this.metadata.get(['entityDefs', scope, 'fields', foreignLink, 'type']);
if (['belongsTo', 'belongsToParent'].includes(foreignLinkType) && foreignLinkFieldType && !advanced[foreignLink] && ['link', 'linkParent'].includes(foreignLinkFieldType)) {
advanced[foreignLink] = {
type: 'isNull',
attribute: foreignLink + 'Id',
data: {
type: 'isEmpty'
}
};
}
}
let primaryFilterName = options.primaryFilterName || null;
if (typeof primaryFilterName === 'function') {
primaryFilterName = primaryFilterName.call(this);
}
let dataBoolFilterList = options.boolFilterList;
if (typeof options.boolFilterList === 'string') {
dataBoolFilterList = options.boolFilterList.split(',');
}
let boolFilterList = dataBoolFilterList || panelDefs.selectBoolFilterList;
if (typeof boolFilterList === 'function') {
boolFilterList = boolFilterList.call(this);
}
boolFilterList = Espo.Utils.clone(boolFilterList);
primaryFilterName = primaryFilterName || panelDefs.selectPrimaryFilterName || null;
const viewKey = options.viewKey || 'select';
const viewName = panelDefs.selectModalView || this.metadata.get(['clientDefs', scope, 'modalViews', viewKey]) || 'views/modals/select-records';
Espo.Ui.notifyWait();
const handler = panelDefs.selectHandler || null;
new Promise(resolve => {
if (!handler) {
resolve({});
return;
}
Espo.loader.requirePromise(handler).then(Handler => new Handler(this.view.getHelper())).then(/** module:handlers/select-related */handler => {
handler.getFilters(model).then(filters => resolve(filters));
});
}).then(filters => {
advanced = {
...advanced,
...(filters.advanced || {})
};
if (boolFilterList || filters.bool) {
boolFilterList = [...(boolFilterList || []), ...(filters.bool || [])];
}
if (filters.primary && !primaryFilterName) {
primaryFilterName = filters.primary;
}
const orderBy = filters.orderBy || panelDefs.selectOrderBy;
const orderDirection = filters.orderBy ? filters.order : panelDefs.selectOrderDirection;
const createButton = options.hasCreate === true && options.onCreate !== undefined;
/** @type {import('views/modals/select-records').default} */
let modalView;
this.view.createView('dialogSelectRelated', viewName, {
scope: scope,
multiple: true,
filters: advanced,
massRelateEnabled: massRelateEnabled,
primaryFilterName: primaryFilterName,
boolFilterList: boolFilterList,
mandatorySelectAttributeList: panelDefs.selectMandatoryAttributeList,
layoutName: panelDefs.selectLayout,
orderBy: orderBy,
orderDirection: orderDirection,
createButton: createButton,
onCreate: () => {
modalView.close();
if (options.onCreate) {
options.onCreate();
}
}
}, view => {
modalView = view;
view.render();
Espo.Ui.notify(false);
this.view.listenToOnce(view, 'select', selectObj => {
const data = {};
if (Object.prototype.toString.call(selectObj) === '[object Array]') {
const ids = [];
selectObj.forEach(model => ids.push(model.id));
data.ids = ids;
} else if (selectObj.massRelate) {
data.massRelate = true;
data.where = selectObj.where;
data.searchParams = selectObj.searchParams;
} else {
data.id = selectObj.id;
}
const url = `${model.entityType}/${model.id}/${link}`;
Espo.Ajax.postRequest(url, data).then(() => {
Espo.Ui.success(this.view.translate('Linked'));
model.trigger(`update-related:${link}`);
model.trigger('after:relate');
model.trigger(`after:relate:${link}`);
});
});
});
});
}
static #_ = _staticBlock = () => [_init_metadata, _init_extra_metadata] = _applyDecs(this, [], [[(0, _di.inject)(_metadata.default), 0, "metadata"]]).e;
}
_staticBlock();
var _default = _exports.default = SelectRelatedHelper;
});
define("helpers/record/create-related", ["exports", "di", "metadata", "helpers/record-modal"], function (_exports, _di, _metadata, _recordModal) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
_metadata = _interopRequireDefault(_metadata);
_recordModal = _interopRequireDefault(_recordModal);
var _staticBlock;
let _init_metadata, _init_extra_metadata;
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* 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.
************************************************************************/
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _applyDecs(e, t, n, r, o, i) { var a, c, u, s, f, l, p, d = Symbol.metadata || Symbol.for("Symbol.metadata"), m = Object.defineProperty, h = Object.create, y = [h(null), h(null)], v = t.length; function g(t, n, r) { return function (o, i) { n && (i = o, o = e); for (var a = 0; a < t.length; a++) i = t[a].apply(o, r ? [i] : []); return r ? i : o; }; } function b(e, t, n, r) { if ("function" != typeof e && (r || void 0 !== e)) throw new TypeError(t + " must " + (n || "be") + " a function" + (r ? "" : " or undefined")); return e; } function applyDec(e, t, n, r, o, i, u, s, f, l, p) { function d(e) { if (!p(e)) throw new TypeError("Attempted to access private element on non-instance"); } var h = [].concat(t[0]), v = t[3], w = !u, D = 1 === o, S = 3 === o, j = 4 === o, E = 2 === o; function I(t, n, r) { return function (o, i) { return n && (i = o, o = e), r && r(o), P[t].call(o, i); }; } if (!w) { var P = {}, k = [], F = S ? "get" : j || D ? "set" : "value"; if (f ? (l || D ? P = { get: _setFunctionName(function () { return v(this); }, r, "get"), set: function (e) { t[4](this, e); } } : P[F] = v, l || _setFunctionName(P[F], r, E ? "" : F)) : l || (P = Object.getOwnPropertyDescriptor(e, r)), !l && !f) { if ((c = y[+s][r]) && 7 !== (c ^ o)) throw Error("Decorating two elements with the same name (" + P[F].name + ") is not supported yet"); y[+s][r] = o < 3 ? 1 : o; } } for (var N = e, O = h.length - 1; O >= 0; O -= n ? 2 : 1) { var T = b(h[O], "A decorator", "be", !0), z = n ? h[O - 1] : void 0, A = {}, H = { kind: ["field", "accessor", "method", "getter", "setter", "class"][o], name: r, metadata: a, addInitializer: function (e, t) { if (e.v) throw new TypeError("attempted to call addInitializer after decoration was finished"); b(t, "An initializer", "be", !0), i.push(t); }.bind(null, A) }; if (w) c = T.call(z, N, H), A.v = 1, b(c, "class decorators", "return") && (N = c);else if (H.static = s, H.private = f, c = H.access = { has: f ? p.bind() : function (e) { return r in e; } }, j || (c.get = f ? E ? function (e) { return d(e), P.value; } : I("get", 0, d) : function (e) { return e[r]; }), E || S || (c.set = f ? I("set", 0, d) : function (e, t) { e[r] = t; }), N = T.call(z, D ? { get: P.get, set: P.set } : P[F], H), A.v = 1, D) { if ("object" == typeof N && N) (c = b(N.get, "accessor.get")) && (P.get = c), (c = b(N.set, "accessor.set")) && (P.set = c), (c = b(N.init, "accessor.init")) && k.unshift(c);else if (void 0 !== N) throw new TypeError("accessor decorators must return an object with get, set, or init properties or undefined"); } else b(N, (l ? "field" : "method") + " decorators", "return") && (l ? k.unshift(N) : P[F] = N); } return o < 2 && u.push(g(k, s, 1), g(i, s, 0)), l || w || (f ? D ? u.splice(-1, 0, I("get", s), I("set", s)) : u.push(E ? P[F] : b.call.bind(P[F])) : m(e, r, P)), N; } function w(e) { return m(e, d, { configurable: !0, enumerable: !0, value: a }); } return void 0 !== i && (a = i[d]), a = h(null == a ? null : a), f = [], l = function (e) { e && f.push(g(e)); }, p = function (t, r) { for (var i = 0; i < n.length; i++) { var a = n[i], c = a[1], l = 7 & c; if ((8 & c) == t && !l == r) { var p = a[2], d = !!a[3], m = 16 & c; applyDec(t ? e : e.prototype, a, m, d ? "#" + p : _toPropertyKey(p), l, l < 2 ? [] : t ? s = s || [] : u = u || [], f, !!t, d, r, t && d ? function (t) { return _checkInRHS(t) === e; } : o); } } }, p(8, 0), p(0, 0), p(8, 1), p(0, 1), l(u), l(s), c = f, v || w(e), { e: c, get c() { var n = []; return v && [w(e = applyDec(e, [t], r, e.name, 5, n)), g(n, 1)]; } }; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
function _setFunctionName(e, t, n) { "symbol" == typeof t && (t = (t = t.description) ? "[" + t + "]" : ""); try { Object.defineProperty(e, "name", { configurable: !0, value: n ? n + " " + t : t }); } catch (e) {} return e; }
function _checkInRHS(e) { if (Object(e) !== e) throw TypeError("right-hand side of 'in' should be an object, got " + (null !== e ? typeof e : "null")); return e; }
/**
* @internal
*/
class CreateRelatedHelper {
/**
* @private
* @type {Metadata}
*/
metadata = _init_metadata(this);
/**
* @param {import('view').default} view
*/
constructor(view) {
_init_extra_metadata(this);
/** @private */
this.view = view;
}
/**
* @param {import('model').default} model
* @param {string} link
* @param {{
* afterSave: function(import('model').default),
* }} [options]
*/
async process(model, link) {
let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
const scope = model.defs['links'][link].entity;
const foreignLink = model.defs['links'][link].foreign;
/** @type {Record} */
const panelDefs = this.metadata.get(`clientDefs.${model.entityType}.relationshipPanels.${link}`) || {};
const attributeMap = panelDefs.createAttributeMap || {};
const handler = panelDefs.createHandler;
let attributes = {};
Object.keys(attributeMap).forEach(attr => attributes[attributeMap[attr]] = model.get(attr));
if (handler) {
const Handler = await Espo.loader.requirePromise(handler);
/** @type {import('handlers/create-related').default} */
const handlerObj = new Handler(this.view.getHelper());
const additionalAttributes = await handlerObj.getAttributes(model, link);
attributes = {
...attributes,
...additionalAttributes
};
}
const helper = new _recordModal.default();
await helper.showCreate(this.view, {
entityType: scope,
relate: {
model: model,
link: foreignLink
},
attributes: attributes,
afterSave: m => {
if (options.afterSave) {
options.afterSave(m);
}
model.trigger(`update-related:${link}`);
model.trigger('after:relate');
model.trigger(`after:relate:${link}`);
}
});
}
static #_ = _staticBlock = () => [_init_metadata, _init_extra_metadata] = _applyDecs(this, [], [[(0, _di.inject)(_metadata.default), 0, "metadata"]]).e;
}
_staticBlock();
var _default = _exports.default = CreateRelatedHelper;
});
define("helpers/misc/stored-text-search", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* 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.
************************************************************************/
/** @module helpers/misc/stored-text-search */
class _default {
/**
* @param {module:storage} storage
* @param {string} scope
* @param {Number} [maxCount]
*/
constructor(scope, storage, maxCount) {
this.scope = scope;
this.storage = storage;
this.key = 'textSearches';
this.maxCount = maxCount || 100;
/** @type {string[]|null} */
this.list = null;
}
/**
* Match.
*
* @param {string} text
* @param {Number} [limit]
* @return {string[]}
*/
match(text, limit) {
text = text.toLowerCase().trim();
const list = this.get();
const matchedList = [];
for (const item of list) {
if (item.toLowerCase().startsWith(text)) {
matchedList.push(item);
}
if (limit !== undefined && matchedList.length === limit) {
break;
}
}
return matchedList;
}
/**
* Get stored text filters.
*
* @private
* @return {string[]}
*/
get() {
if (this.list === null) {
this.list = this.getFromStorage();
}
return this.list;
}
/**
* @private
* @return {string[]}
*/
getFromStorage() {
/** @var {string[]} */
return this.storage.get(this.key, this.scope) || [];
}
/**
* Store a text filter.
*
* @param {string} text
*/
store(text) {
text = text.trim();
let list = this.getFromStorage();
const index = list.indexOf(text);
if (index !== -1) {
list.splice(index, 1);
}
list.unshift(text);
if (list.length > this.maxCount) {
list = list.slice(0, this.maxCount);
}
this.list = list;
this.storage.set(this.key, this.scope, list);
}
/**
* Remove a text filter.
*
* @param {string} text
*/
remove(text) {
text = text.trim();
const list = this.getFromStorage();
const index = list.indexOf(text);
if (index === -1) {
return;
}
list.splice(index, 1);
this.list = list;
this.storage.set(this.key, this.scope, list);
}
}
_exports.default = _default;
});
define("helpers/misc/foreign-field", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* 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.
************************************************************************/
/** @module helpers/misc/foreign-field */
class _default {
/**
* @private
* @type {string}
*/
entityType;
/**
* @param {module:views/fields/base} view A field view.
*/
constructor(view) {
/**
* @private
* @type {module:views/fields/base}
*/
this.view = view;
const metadata = view.getMetadata();
const model = view.model;
const field = view.params.field;
const link = view.params.link;
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;
const ignoreList = ['default', 'audited', 'readOnly', 'required'];
/** @private */
this.foreignParams = {};
view.getFieldManager().getParamList(type).forEach(defs => {
const name = defs.name;
if (ignoreList.includes(name)) {
return;
}
this.foreignParams[name] = fieldDefs[name] || null;
});
}
/**
* @return {Object.}
*/
getForeignParams() {
return Espo.Utils.cloneDeep(this.foreignParams);
}
/**
* @return {string}
*/
getEntityType() {
return this.entityType;
}
}
_exports.default = _default;
});
define("helpers/misc/attachment-insert-from-source", ["exports", "di", "metadata", "model-factory"], function (_exports, _di, _metadata, _modelFactory) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
_metadata = _interopRequireDefault(_metadata);
_modelFactory = _interopRequireDefault(_modelFactory);
var _staticBlock;
let _init_metadata, _init_extra_metadata, _init_modelFactory, _init_extra_modelFactory;
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* 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.
************************************************************************/
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _applyDecs(e, t, n, r, o, i) { var a, c, u, s, f, l, p, d = Symbol.metadata || Symbol.for("Symbol.metadata"), m = Object.defineProperty, h = Object.create, y = [h(null), h(null)], v = t.length; function g(t, n, r) { return function (o, i) { n && (i = o, o = e); for (var a = 0; a < t.length; a++) i = t[a].apply(o, r ? [i] : []); return r ? i : o; }; } function b(e, t, n, r) { if ("function" != typeof e && (r || void 0 !== e)) throw new TypeError(t + " must " + (n || "be") + " a function" + (r ? "" : " or undefined")); return e; } function applyDec(e, t, n, r, o, i, u, s, f, l, p) { function d(e) { if (!p(e)) throw new TypeError("Attempted to access private element on non-instance"); } var h = [].concat(t[0]), v = t[3], w = !u, D = 1 === o, S = 3 === o, j = 4 === o, E = 2 === o; function I(t, n, r) { return function (o, i) { return n && (i = o, o = e), r && r(o), P[t].call(o, i); }; } if (!w) { var P = {}, k = [], F = S ? "get" : j || D ? "set" : "value"; if (f ? (l || D ? P = { get: _setFunctionName(function () { return v(this); }, r, "get"), set: function (e) { t[4](this, e); } } : P[F] = v, l || _setFunctionName(P[F], r, E ? "" : F)) : l || (P = Object.getOwnPropertyDescriptor(e, r)), !l && !f) { if ((c = y[+s][r]) && 7 !== (c ^ o)) throw Error("Decorating two elements with the same name (" + P[F].name + ") is not supported yet"); y[+s][r] = o < 3 ? 1 : o; } } for (var N = e, O = h.length - 1; O >= 0; O -= n ? 2 : 1) { var T = b(h[O], "A decorator", "be", !0), z = n ? h[O - 1] : void 0, A = {}, H = { kind: ["field", "accessor", "method", "getter", "setter", "class"][o], name: r, metadata: a, addInitializer: function (e, t) { if (e.v) throw new TypeError("attempted to call addInitializer after decoration was finished"); b(t, "An initializer", "be", !0), i.push(t); }.bind(null, A) }; if (w) c = T.call(z, N, H), A.v = 1, b(c, "class decorators", "return") && (N = c);else if (H.static = s, H.private = f, c = H.access = { has: f ? p.bind() : function (e) { return r in e; } }, j || (c.get = f ? E ? function (e) { return d(e), P.value; } : I("get", 0, d) : function (e) { return e[r]; }), E || S || (c.set = f ? I("set", 0, d) : function (e, t) { e[r] = t; }), N = T.call(z, D ? { get: P.get, set: P.set } : P[F], H), A.v = 1, D) { if ("object" == typeof N && N) (c = b(N.get, "accessor.get")) && (P.get = c), (c = b(N.set, "accessor.set")) && (P.set = c), (c = b(N.init, "accessor.init")) && k.unshift(c);else if (void 0 !== N) throw new TypeError("accessor decorators must return an object with get, set, or init properties or undefined"); } else b(N, (l ? "field" : "method") + " decorators", "return") && (l ? k.unshift(N) : P[F] = N); } return o < 2 && u.push(g(k, s, 1), g(i, s, 0)), l || w || (f ? D ? u.splice(-1, 0, I("get", s), I("set", s)) : u.push(E ? P[F] : b.call.bind(P[F])) : m(e, r, P)), N; } function w(e) { return m(e, d, { configurable: !0, enumerable: !0, value: a }); } return void 0 !== i && (a = i[d]), a = h(null == a ? null : a), f = [], l = function (e) { e && f.push(g(e)); }, p = function (t, r) { for (var i = 0; i < n.length; i++) { var a = n[i], c = a[1], l = 7 & c; if ((8 & c) == t && !l == r) { var p = a[2], d = !!a[3], m = 16 & c; applyDec(t ? e : e.prototype, a, m, d ? "#" + p : _toPropertyKey(p), l, l < 2 ? [] : t ? s = s || [] : u = u || [], f, !!t, d, r, t && d ? function (t) { return _checkInRHS(t) === e; } : o); } } }, p(8, 0), p(0, 0), p(8, 1), p(0, 1), l(u), l(s), c = f, v || w(e), { e: c, get c() { var n = []; return v && [w(e = applyDec(e, [t], r, e.name, 5, n)), g(n, 1)]; } }; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
function _setFunctionName(e, t, n) { "symbol" == typeof t && (t = (t = t.description) ? "[" + t + "]" : ""); try { Object.defineProperty(e, "name", { configurable: !0, value: n ? n + " " + t : t }); } catch (e) {} return e; }
function _checkInRHS(e) { if (Object(e) !== e) throw TypeError("right-hand side of 'in' should be an object, got " + (null !== e ? typeof e : "null")); return e; }
/**
* @internal
*/
class AttachmentInsertSourceFromHelper {
/**
* @param {import('views/fields/attachment-multiple').default|import('views/fields/file').default} view
*/
constructor(view) {
_init_extra_modelFactory(this);
/** @private */
this.view = view;
/** @private */
this.model = view.model;
}
/**
* @type {Metadata}
* @private
*/
metadata = _init_metadata(this);
/**
* @type {ModelFactory}
* @private
*/
modelFactory = (_init_extra_metadata(this), _init_modelFactory(this));
/**
* @param {{
* source: string,
* onInsert: function(import('model').default[]),
* }} params
*/
insert(params) {
const source = params.source;
const viewName = this.metadata.get(['clientDefs', 'Attachment', 'sourceDefs', source, 'insertModalView']) || this.metadata.get(['clientDefs', source, 'modalViews', 'select']) || 'views/modals/select-records';
let filters = {};
if ('getSelectFilters' + source in this.view) {
filters = this.view['getSelectFilters' + source]() || {};
}
// @todo EntityType => link mapping defined in metadata for automatic filtering.
if (this.model.attributes.parentId && this.model.attributes.parentType === 'Account') {
if (this.metadata.get(`entityDefs.${source}.fields.account.type`) === 'link' && this.metadata.get(`entityDefs.${source}.links.account.entity`) === 'Account') {
filters = {
account: {
type: 'equals',
attribute: 'accountId',
value: this.model.attributes.parentId,
data: {
type: 'is',
idValue: this.model.attributes.parentId,
nameValue: this.model.attributes.parentType
}
},
...filters
};
}
}
let boolFilterList = this.metadata.get(`clientDefs.Attachment.sourceDefs.${source}.boolFilterList`);
if ('getSelectBoolFilterList' + source in this.view) {
boolFilterList = this.view['getSelectBoolFilterList' + source]();
}
let primaryFilterName = this.metadata.get(`clientDefs.Attachment.sourceDefs.${source}.primaryFilter`);
if ('getSelectPrimaryFilterName' + source in this.view) {
primaryFilterName = this.view['getSelectPrimaryFilterName' + source]();
}
/** @type {module:views/modals/select-records~Options} */
const options = {
entityType: source,
createButton: false,
filters: filters,
boolFilterList: boolFilterList,
primaryFilterName: primaryFilterName,
multiple: true,
onSelect: models => {
models.forEach(async model => {
if (model.entityType === 'Attachment') {
params.onInsert([model]);
return;
}
/** @type {Record[]} */
const attachmentDataList = await Espo.Ajax.postRequest(`${source}/action/getAttachmentList`, {
id: model.id,
field: this.view.name,
parentType: this.view.entityType
});
const attachmentSeed = await this.modelFactory.create('Attachment');
for (const item of attachmentDataList) {
const attachment = attachmentSeed.clone();
attachment.set(item);
params.onInsert([attachment]);
}
});
}
};
Espo.Ui.notifyWait();
this.view.createView('modal', viewName, options, view => {
view.render();
Espo.Ui.notify();
});
}
static #_ = _staticBlock = () => [_init_metadata, _init_extra_metadata, _init_modelFactory, _init_extra_modelFactory] = _applyDecs(this, [], [[(0, _di.inject)(_metadata.default), 0, "metadata"], [(0, _di.inject)(_modelFactory.default), 0, "modelFactory"]]).e;
}
_exports.default = AttachmentInsertSourceFromHelper;
_staticBlock();
});
define("theme-manager", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* 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.
************************************************************************/
/** @module theme-manager */
/**
* A theme manager.
*/
class ThemeManager {
/**
* @param {module:models/settings} config A config.
* @param {module:models/preferences} preferences Preferences.
* @param {module:metadata} metadata Metadata.
* @param {string|null} [name] A name. If not set, then will be obtained from config and preferences.
*/
constructor(config, preferences, metadata, name) {
/**
* @private
* @type {module:models/settings}
*/
this.config = config;
/**
* @private
* @type {module:models/preferences}
*/
this.preferences = preferences;
/**
* @private
* @type {module:metadata}
*/
this.metadata = metadata;
/**
* @private
* @type {boolean}
*/
this.useConfig = !name;
/**
* @private
* @type {string|null}
*/
this.name = name || null;
}
/**
* @private
*/
defaultParams = {
screenWidthXs: 768,
dashboardCellHeight: 40,
dashboardCellMargin: 16
};
/**
* Get a theme name for the current user.
*
* @returns {string}
*/
getName() {
if (this.name) {
return this.name;
}
if (!this.config.get('userThemesDisabled')) {
const name = this.preferences.get('theme');
if (name && name !== '') {
return name;
}
}
return this.config.get('theme');
}
/**
* Get a theme name currently applied to the DOM.
*
* @returns {string|null} Null if not applied.
*/
getAppliedName() {
const name = window.getComputedStyle(document.body).getPropertyValue('--theme-name');
if (!name) {
return null;
}
return name.trim();
}
/**
* Whether a current theme is applied to the DOM.
*
* @returns {boolean}
*/
isApplied() {
const appliedName = this.getAppliedName();
if (!appliedName) {
return true;
}
return this.getName() === appliedName;
}
/**
* Get a stylesheet path for a current theme.
*
* @returns {string}
*/
getStylesheet() {
let link = this.getParam('stylesheet') || 'client/css/espo/espo.css';
if (this.config.get('cacheTimestamp')) {
link += '?r=' + this.config.get('cacheTimestamp').toString();
}
return link;
}
/**
* Get an iframe stylesheet path for a current theme.
*
* @returns {string}
*/
getIframeStylesheet() {
let link = this.getParam('stylesheetIframe') || 'client/css/espo/espo-iframe.css';
if (this.config.get('cacheTimestamp')) {
link += '?r=' + this.config.get('cacheTimestamp').toString();
}
return link;
}
/**
* Get an iframe-fallback stylesheet path for a current theme.
*
* @returns {string}
*/
getIframeFallbackStylesheet() {
let link = this.getParam('stylesheetIframeFallback') || 'client/css/espo/espo-iframe.css';
if (this.config.get('cacheTimestamp')) {
link += '?r=' + this.config.get('cacheTimestamp').toString();
}
return link;
}
/**
* Get a theme parameter.
*
* @param {string} name A parameter name.
* @returns {*} Null if not set.
*/
getParam(name) {
if (name !== 'params' && name !== 'mappedParams') {
const varValue = this.getVarParam(name);
if (varValue !== null) {
return varValue;
}
const mappedValue = this.getMappedParam(name);
if (mappedValue !== null) {
return mappedValue;
}
}
let value = this.metadata.get(['themes', this.getName(), name]);
if (value !== null) {
return value;
}
value = this.metadata.get(['themes', this.getParentName(), name]);
if (value !== null) {
return value;
}
return this.defaultParams[name] || null;
}
/**
* @private
* @param {string} name
* @returns {*}
*/
getVarParam(name) {
const params = this.getParam('params') || {};
if (!(name in params)) {
return null;
}
let values = null;
if (this.useConfig && !this.config.get('userThemesDisabled') && this.preferences.get('theme')) {
values = this.preferences.get('themeParams');
}
if (!values && this.useConfig) {
values = this.config.get('themeParams');
}
if (values && name in values) {
return values[name];
}
if ('default' in params[name]) {
return params[name].default;
}
return null;
}
/**
* @private
* @param {string} name
* @returns {*}
*/
getMappedParam(name) {
const mappedParams = this.getParam('mappedParams') || {};
if (!(name in mappedParams)) {
return null;
}
const mapped = mappedParams[name].param;
const valueMap = mappedParams[name].valueMap;
if (mapped && valueMap) {
const key = this.getParam(mapped);
return valueMap[key];
}
return null;
}
/**
* @private
* @returns {string}
*/
getParentName() {
return this.metadata.get(['themes', this.getName(), 'parent']) || 'Espo';
}
/**
* Whether a current theme is different from a system default theme.
*
* @returns {boolean}
*/
isUserTheme() {
if (this.config.get('userThemesDisabled')) {
return false;
}
const name = this.preferences.get('theme');
if (!name || name === '') {
return false;
}
return name !== this.config.get('theme');
}
/**
* Get a font-size factor. To adjust px sizes based on font-size.
*
* @return {number}
* @since 9.0.0
* @internal Experimental.
*/
getFontSizeFactor() {
const paramFontSize = this.getParam('fontSize') || 14;
const fontSize = parseInt(getComputedStyle(document.body).fontSize);
return Math.round(fontSize / paramFontSize * 10000) / 10000;
}
}
var _default = _exports.default = ThemeManager;
});
define("session-storage", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* 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.
************************************************************************/
/** @module session-storage */
/**
* A session storage. Cleared when a page session ends.
*/
class SessionStorage {
/** @private */
storageObject = sessionStorage;
/**
* Get a value.
*
* @param {string} name A name.
* @returns {*} Null if not set.
*/
get(name) {
let stored;
try {
stored = this.storageObject.getItem(name);
} catch (error) {
console.error(error);
return null;
}
if (stored) {
let result = stored;
if (stored.length > 9 && stored.substring(0, 9) === '__JSON__:') {
const jsonString = stored.slice(9);
try {
result = JSON.parse(jsonString);
} catch (error) {
result = stored;
}
}
return result;
}
return null;
}
/**
* Set (store) a value.
*
* @param {string} name A name.
* @param {*} value A value.
*/
set(name, value) {
if (value === null) {
this.clear(name);
return;
}
if (value instanceof Object || Array.isArray(value) || value === true || value === false || typeof value === 'number') {
value = '__JSON__:' + JSON.stringify(value);
}
try {
this.storageObject.setItem(name, value);
} catch (error) {
console.error(error);
}
}
/**
* Has a value.
*
* @param {string} name A name.
* @returns {boolean}
*/
has(name) {
return this.storageObject.getItem(name) !== null;
}
/**
* Clear a value.
*
* @param {string} name A name.
*/
clear(name) {
for (const i in this.storageObject) {
if (i === name) {
delete this.storageObject[i];
}
}
}
}
var _default = _exports.default = SessionStorage;
});
define("page-title", ["exports", "jquery"], function (_exports, _jquery) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
_jquery = _interopRequireDefault(_jquery);
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* 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.
************************************************************************/
/** @module page-title */
/**
* A page-title util.
*/
class PageTitle {
/**
* @class
* @param {module:models/settings} config A config.
*/
constructor(config) {
/**
* @private
* @type {boolean}
*/
this.displayNotificationNumber = config.get('newNotificationCountInTitle') || false;
/**
* @private
* @type {string}
*/
this.title = (0, _jquery.default)('head title').text() || '';
/**
* @private
* @type {number}
*/
this.notificationNumber = 0;
}
/**
* Set a title.
*
* @param {string} title A title.
*/
setTitle(title) {
this.title = title;
this.update();
}
/**
* Set a notification number.
*
* @param {number} notificationNumber A number.
*/
setNotificationNumber(notificationNumber) {
this.notificationNumber = notificationNumber;
if (this.displayNotificationNumber) {
this.update();
}
}
/**
* Update a page title.
*/
update() {
let value = '';
if (this.displayNotificationNumber && this.notificationNumber) {
value = '(' + this.notificationNumber.toString() + ')';
if (this.title) {
value += ' ';
}
}
value += this.title;
(0, _jquery.default)('head title').text(value);
}
}
var _default = _exports.default = PageTitle;
});
define("number-util", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* 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.
************************************************************************/
/** @module num-util */
/**
* A number util.
*/
class NumberUtil {
/**
* @param {module:models/settings} config A config.
* @param {module:models/preferences} preferences Preferences.
*/
constructor(config, preferences) {
/**
* @private
* @type {module:models/settings}
*/
this.config = config;
/**
* @private
* @type {module:models/preferences}
*/
this.preferences = preferences;
/**
* A thousand separator.
*
* @private
* @type {string|null}
*/
this.thousandSeparator = null;
/**
* A decimal mark.
*
* @private
* @type {string|null}
*/
this.decimalMark = null;
this.config.on('change', () => {
this.thousandSeparator = null;
this.decimalMark = null;
});
this.preferences.on('change', () => {
this.thousandSeparator = null;
this.decimalMark = null;
});
/**
* A max decimal places.
*
* @private
* @type {number}
*/
this.maxDecimalPlaces = 10;
}
/**
* Format an integer number.
*
* @param {number} value A value.
* @returns {string}
*/
formatInt(value) {
if (value === null || value === undefined) {
return '';
}
let stringValue = value.toString();
stringValue = stringValue.replace(/\B(?=(\d{3})+(?!\d))/g, this.getThousandSeparator());
return stringValue;
}
// noinspection JSUnusedGlobalSymbols
/**
* Format a float number.
*
* @param {number} value A value.
* @param {number} [decimalPlaces] Decimal places.
* @returns {string}
*/
formatFloat(value, decimalPlaces) {
if (value === null || value === undefined) {
return '';
}
if (decimalPlaces === 0) {
value = Math.round(value);
} else if (decimalPlaces) {
value = Math.round(value * Math.pow(10, decimalPlaces)) / Math.pow(10, decimalPlaces);
} else {
value = Math.round(value * Math.pow(10, this.maxDecimalPlaces)) / Math.pow(10, this.maxDecimalPlaces);
}
const parts = value.toString().split('.');
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, this.getThousandSeparator());
if (decimalPlaces === 0) {
return parts[0];
}
if (decimalPlaces) {
let decimalPartLength = 0;
if (parts.length > 1) {
decimalPartLength = parts[1].length;
} else {
parts[1] = '';
}
if (decimalPlaces && decimalPartLength < decimalPlaces) {
const limit = decimalPlaces - decimalPartLength;
for (let i = 0; i < limit; i++) {
parts[1] += '0';
}
}
}
return parts.join(this.getDecimalMark());
}
/**
* @private
* @returns {string}
*/
getThousandSeparator() {
if (this.thousandSeparator !== null) {
return this.thousandSeparator;
}
let thousandSeparator = '.';
if (this.preferences.has('thousandSeparator')) {
thousandSeparator = this.preferences.get('thousandSeparator');
} else if (this.config.has('thousandSeparator')) {
thousandSeparator = this.config.get('thousandSeparator');
}
/**
* A thousand separator.
*
* @private
* @type {string|null}
*/
this.thousandSeparator = thousandSeparator;
return thousandSeparator;
}
/**
* @private
* @returns {string}
*/
getDecimalMark() {
if (this.decimalMark !== null) {
return this.decimalMark;
}
let decimalMark = '.';
if (this.preferences.has('decimalMark')) {
decimalMark = this.preferences.get('decimalMark');
} else {
if (this.config.has('decimalMark')) {
decimalMark = this.config.get('decimalMark');
}
}
/**
* A decimal mark.
*
* @private
* @type {string|null}
*/
this.decimalMark = decimalMark;
return decimalMark;
}
}
var _default = _exports.default = NumberUtil;
});
define("email-helper", ["exports", "di", "language", "models/user", "date-time", "acl-manager"], function (_exports, _di, _language, _user, _dateTime, _aclManager) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
_language = _interopRequireDefault(_language);
_user = _interopRequireDefault(_user);
_dateTime = _interopRequireDefault(_dateTime);
_aclManager = _interopRequireDefault(_aclManager);
var _staticBlock;
let _init_language, _init_extra_language, _init_user, _init_extra_user, _init_dateTime, _init_extra_dateTime, _init_acl, _init_extra_acl;
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* 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.
************************************************************************/
/** @module email-helper */
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _applyDecs(e, t, n, r, o, i) { var a, c, u, s, f, l, p, d = Symbol.metadata || Symbol.for("Symbol.metadata"), m = Object.defineProperty, h = Object.create, y = [h(null), h(null)], v = t.length; function g(t, n, r) { return function (o, i) { n && (i = o, o = e); for (var a = 0; a < t.length; a++) i = t[a].apply(o, r ? [i] : []); return r ? i : o; }; } function b(e, t, n, r) { if ("function" != typeof e && (r || void 0 !== e)) throw new TypeError(t + " must " + (n || "be") + " a function" + (r ? "" : " or undefined")); return e; } function applyDec(e, t, n, r, o, i, u, s, f, l, p) { function d(e) { if (!p(e)) throw new TypeError("Attempted to access private element on non-instance"); } var h = [].concat(t[0]), v = t[3], w = !u, D = 1 === o, S = 3 === o, j = 4 === o, E = 2 === o; function I(t, n, r) { return function (o, i) { return n && (i = o, o = e), r && r(o), P[t].call(o, i); }; } if (!w) { var P = {}, k = [], F = S ? "get" : j || D ? "set" : "value"; if (f ? (l || D ? P = { get: _setFunctionName(function () { return v(this); }, r, "get"), set: function (e) { t[4](this, e); } } : P[F] = v, l || _setFunctionName(P[F], r, E ? "" : F)) : l || (P = Object.getOwnPropertyDescriptor(e, r)), !l && !f) { if ((c = y[+s][r]) && 7 !== (c ^ o)) throw Error("Decorating two elements with the same name (" + P[F].name + ") is not supported yet"); y[+s][r] = o < 3 ? 1 : o; } } for (var N = e, O = h.length - 1; O >= 0; O -= n ? 2 : 1) { var T = b(h[O], "A decorator", "be", !0), z = n ? h[O - 1] : void 0, A = {}, H = { kind: ["field", "accessor", "method", "getter", "setter", "class"][o], name: r, metadata: a, addInitializer: function (e, t) { if (e.v) throw new TypeError("attempted to call addInitializer after decoration was finished"); b(t, "An initializer", "be", !0), i.push(t); }.bind(null, A) }; if (w) c = T.call(z, N, H), A.v = 1, b(c, "class decorators", "return") && (N = c);else if (H.static = s, H.private = f, c = H.access = { has: f ? p.bind() : function (e) { return r in e; } }, j || (c.get = f ? E ? function (e) { return d(e), P.value; } : I("get", 0, d) : function (e) { return e[r]; }), E || S || (c.set = f ? I("set", 0, d) : function (e, t) { e[r] = t; }), N = T.call(z, D ? { get: P.get, set: P.set } : P[F], H), A.v = 1, D) { if ("object" == typeof N && N) (c = b(N.get, "accessor.get")) && (P.get = c), (c = b(N.set, "accessor.set")) && (P.set = c), (c = b(N.init, "accessor.init")) && k.unshift(c);else if (void 0 !== N) throw new TypeError("accessor decorators must return an object with get, set, or init properties or undefined"); } else b(N, (l ? "field" : "method") + " decorators", "return") && (l ? k.unshift(N) : P[F] = N); } return o < 2 && u.push(g(k, s, 1), g(i, s, 0)), l || w || (f ? D ? u.splice(-1, 0, I("get", s), I("set", s)) : u.push(E ? P[F] : b.call.bind(P[F])) : m(e, r, P)), N; } function w(e) { return m(e, d, { configurable: !0, enumerable: !0, value: a }); } return void 0 !== i && (a = i[d]), a = h(null == a ? null : a), f = [], l = function (e) { e && f.push(g(e)); }, p = function (t, r) { for (var i = 0; i < n.length; i++) { var a = n[i], c = a[1], l = 7 & c; if ((8 & c) == t && !l == r) { var p = a[2], d = !!a[3], m = 16 & c; applyDec(t ? e : e.prototype, a, m, d ? "#" + p : _toPropertyKey(p), l, l < 2 ? [] : t ? s = s || [] : u = u || [], f, !!t, d, r, t && d ? function (t) { return _checkInRHS(t) === e; } : o); } } }, p(8, 0), p(0, 0), p(8, 1), p(0, 1), l(u), l(s), c = f, v || w(e), { e: c, get c() { var n = []; return v && [w(e = applyDec(e, [t], r, e.name, 5, n)), g(n, 1)]; } }; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
function _setFunctionName(e, t, n) { "symbol" == typeof t && (t = (t = t.description) ? "[" + t + "]" : ""); try { Object.defineProperty(e, "name", { configurable: !0, value: n ? n + " " + t : t }); } catch (e) {} return e; }
function _checkInRHS(e) { if (Object(e) !== e) throw TypeError("right-hand side of 'in' should be an object, got " + (null !== e ? typeof e : "null")); return e; }
/**
* An email helper.
*/
class EmailHelper {
/**
* @private
* @type {Language}
*/
language = _init_language(this);
/**
* @private
* @type {User}
*/
user = (_init_extra_language(this), _init_user(this));
/**
* @private
* @type {DateTime}
*/
dateTime = (_init_extra_user(this), _init_dateTime(this));
/**
* @private
* @type {AclManager}
*/
acl = (_init_extra_dateTime(this), _init_acl(this));
constructor() {
_init_extra_acl(this);
/** @private */
this.erasedPlaceholder = 'ERASED:';
}
/**
* Get reply email attributes.
*
* @param {module:model} model An email model.
* @param {Object|null} [data=null] Action data. Unused.
* @param {boolean} [cc=false] To include CC (reply-all).
* @returns {Object.}
*/
getReplyAttributes(model, data, cc) {
const attributes = {
status: 'Draft',
isHtml: model.attributes.isHtml
};
const subject = model.attributes.name || '';
attributes['name'] = subject.toUpperCase().indexOf('RE:') !== 0 ? 'Re: ' + subject : subject;
let to = '';
let isReplyOnSent = false;
const nameHash = model.attributes.nameHash || {};
const replyToAddressString = model.attributes.replyTo || null;
const replyToString = model.attributes.replyToString || null;
const userEmailAddressList = this.user.attributes.emailAddressList || [];
const idHash = model.attributes.idHash || {};
const typeHash = model.attributes.typeHash || {};
if (replyToAddressString) {
const replyToAddressList = replyToAddressString.split(';');
to = replyToAddressList.join(';');
} else if (replyToString) {
const a = [];
replyToString.split(';').forEach(item => {
const part = item.trim();
const address = this.parseAddressFromStringAddress(item);
if (address) {
a.push(address);
const name = this.parseNameFromStringAddress(part);
if (name && name !== address) {
nameHash[address] = name;
}
}
});
to = a.join(';');
}
if ((!to || !to.includes('@')) && model.attributes.from) {
if (!userEmailAddressList.includes(model.attributes.from)) {
to = model.attributes.from;
if (!nameHash[to]) {
const fromString = model.attributes.fromString || model.attributes.fromName;
if (fromString) {
const name = this.parseNameFromStringAddress(fromString);
if (name !== to) {
nameHash[to] = name;
}
}
}
} else {
isReplyOnSent = true;
}
}
attributes.to = to;
if (cc) {
attributes.cc = model.attributes.cc || '';
/** @type {string[]} */
const excludeFromReplyEmailAddressList = this.user.get('excludeFromReplyEmailAddressList') || [];
(model.get('to') || '').split(';').forEach(item => {
item = item.trim();
if (item === this.user.get('emailAddress')) {
return;
}
if (excludeFromReplyEmailAddressList.includes(item)) {
return;
}
if (isReplyOnSent) {
if (attributes.to) {
attributes.to += ';';
}
attributes.to += item;
return;
}
if (attributes.cc) {
attributes.cc += ';';
}
attributes.cc += item;
});
attributes.cc = attributes.cc.replace(/^(; )/, "");
}
if (attributes.to) {
let toList = attributes.to.split(';');
toList = toList.filter(item => {
if (item.indexOf(this.erasedPlaceholder) === 0) {
return false;
}
return true;
});
attributes.to = toList.join(';');
}
/** @type {string[]} */
const personalAddresses = this.user.get('userEmailAddressList') || [];
const lcPersonalAddresses = personalAddresses.map(it => it.toLowerCase());
if (attributes.cc) {
const ccList = attributes.cc.split(';').filter(item => {
if (lcPersonalAddresses.includes(item.toLowerCase())) {
return false;
}
if (item.indexOf(this.erasedPlaceholder) === 0) {
return false;
}
return true;
});
attributes.cc = ccList.join(';');
}
if (model.get('parentId')) {
attributes['parentId'] = model.get('parentId');
attributes['parentName'] = model.get('parentName');
attributes['parentType'] = model.get('parentType');
}
if (model.get('teamsIds') && model.get('teamsIds').length) {
attributes.teamsIds = Espo.Utils.clone(model.get('teamsIds'));
attributes.teamsNames = Espo.Utils.clone(model.get('teamsNames') || {});
const defaultTeamId = this.user.get('defaultTeamId');
if (defaultTeamId && !~attributes.teamsIds.indexOf(defaultTeamId)) {
attributes.teamsIds.push(this.user.get('defaultTeamId'));
attributes.teamsNames[this.user.get('defaultTeamId')] = this.user.get('defaultTeamName');
}
attributes.teamsIds = attributes.teamsIds.filter(teamId => this.acl.checkTeamAssignmentPermission(teamId));
}
attributes.nameHash = nameHash;
attributes.typeHash = typeHash;
attributes.idHash = idHash;
attributes.repliedId = model.id;
attributes.inReplyTo = model.get('messageId');
/** @type {string[]} */
const lcToAddresses = (model.attributes.to || '').split(';').map(it => it.toLowerCase());
for (const address of personalAddresses) {
if (lcToAddresses.includes(address.toLowerCase())) {
attributes.from = address;
break;
}
}
this.addReplyBodyAttributes(model, attributes);
return attributes;
}
/**
* Get forward email attributes.
*
* @param {module:model} model An email model.
* @returns {Object}
*/
getForwardAttributes(model) {
const attributes = {
status: 'Draft',
isHtml: model.get('isHtml')
};
const subject = model.get('name');
if (~!subject.toUpperCase().indexOf('FWD:') && ~!subject.toUpperCase().indexOf('FW:')) {
attributes['name'] = 'Fwd: ' + subject;
} else {
attributes['name'] = subject;
}
if (model.get('parentId')) {
attributes['parentId'] = model.get('parentId');
attributes['parentName'] = model.get('parentName');
attributes['parentType'] = model.get('parentType');
}
this.addForwardBodyAttributes(model, attributes);
return attributes;
}
/**
* Add body attributes for a forward email.
*
* @param {module:model} model An email model.
* @param {Object} attributes
*/
addForwardBodyAttributes(model, attributes) {
let prepending = '';
if (model.get('isHtml')) {
prepending = '
' + '------' + this.language.translate('Forwarded message', 'labels', 'Email') + '------';
} else {
prepending = '\n\n' + '------' + this.language.translate('Forwarded message', 'labels', 'Email') + '------';
}
const list = [];
if (model.get('from')) {
const from = model.get('from');
let line = this.language.translate('from', 'fields', 'Email') + ': ';
const nameHash = model.get('nameHash') || {};
if (from in nameHash) {
line += nameHash[from] + ' ';
}
if (model.get('isHtml')) {
line += '<' + from + '>';
} else {
line += '<' + from + '>';
}
list.push(line);
}
if (model.get('dateSent')) {
let line = this.language.translate('dateSent', 'fields', 'Email') + ': ';
line += this.dateTime.toDisplay(model.get('dateSent'));
list.push(line);
}
if (model.get('name')) {
let line = this.language.translate('subject', 'fields', 'Email') + ': ';
line += model.get('name');
list.push(line);
}
if (model.get('to')) {
let line = this.language.translate('to', 'fields', 'Email') + ': ';
const partList = [];
model.get('to').split(';').forEach(to => {
const nameHash = model.get('nameHash') || {};
let line = '';
if (to in nameHash) {
line += nameHash[to] + ' ';
}
if (model.get('isHtml')) {
line += '<' + to + '>';
} else {
line += '<' + to + '>';
}
partList.push(line);
});
line += partList.join(';');
list.push(line);
}
list.forEach(line => {
if (model.get('isHtml')) {
prepending += '
' + line;
} else {
prepending += '\n' + line;
}
});
if (model.get('isHtml')) {
const body = model.get('body');
attributes['body'] = prepending + '
' + body;
} else {
const bodyPlain = model.get('body') || model.get('bodyPlain') || '';
attributes['bodyPlain'] = attributes['body'] = prepending + '\n\n' + bodyPlain;
}
}
/**
* Parse a name from a string address.
*
* @param {string} value A string address. E.g. `Test Name `.
* @returns {string|null}
*/
parseNameFromStringAddress(value) {
if (!value.includes('<')) {
return null;
}
let name = value.replace(/<(.*)>/, '').trim();
if (name.charAt(0) === '"' && name.charAt(name.length - 1) === '"') {
name = name.slice(1, name.length - 2);
}
return name;
}
/**
* Parse an address from a string address.
*
* @param {string} value A string address. E.g. `Test Name `.
* @returns {string|null}
*/
parseAddressFromStringAddress(value) {
const r = value.match(/<(.*)>/);
let address;
if (r && r.length > 1) {
address = r[1];
} else {
address = value.trim();
}
return address;
}
/**
* Add body attributes for a reply email.
*
* @param {module:model} model An email model.
* @param {Object.} attributes
*/
addReplyBodyAttributes(model, attributes) {
const format = this.dateTime.getReadableShortDateTimeFormat();
const dateSent = model.get('dateSent');
let dateSentString = null;
if (dateSent) {
const dateSentMoment = this.dateTime.toMoment(dateSent);
dateSentString = dateSentMoment.format(format);
if (dateSentMoment.year() !== this.dateTime.getNowMoment().year()) {
dateSentString += ', ' + dateSentMoment.year();
}
}
let replyHeadString = dateSentString || this.language.translate('Original message', 'labels', 'Email');
let fromName = model.get('fromName');
if (!fromName && model.get('from')) {
fromName = (model.get('nameHash') || {})[model.get('from')];
if (fromName) {
replyHeadString += ', ' + fromName;
}
}
replyHeadString += ':';
if (model.get('isHtml')) {
const body = model.get('body');
attributes['body'] = `
` + `${replyHeadString}
${body}
`;
return;
}
let bodyPlain = model.get('body') || model.get('bodyPlain') || '';
let b = '\n\n';
b += replyHeadString + '\n';
bodyPlain.split('\n').forEach(line => {
b += '> ' + line + '\n';
});
bodyPlain = b;
attributes['body'] = bodyPlain;
attributes['bodyPlain'] = bodyPlain;
}
static #_ = _staticBlock = () => [_init_language, _init_extra_language, _init_user, _init_extra_user, _init_dateTime, _init_extra_dateTime, _init_acl, _init_extra_acl] = _applyDecs(this, [], [[(0, _di.inject)(_language.default), 0, "language"], [(0, _di.inject)(_user.default), 0, "user"], [(0, _di.inject)(_dateTime.default), 0, "dateTime"], [(0, _di.inject)(_aclManager.default), 0, "acl"]]).e;
}
_staticBlock();
var _default = _exports.default = EmailHelper;
});
define("collection", ["exports", "model", "bullbone", "underscore"], function (_exports, _model, _bullbone, _underscore) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
_model = _interopRequireDefault(_model);
_underscore = _interopRequireDefault(_underscore);
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* 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.
************************************************************************/
/** @module collection */
/**
* On sync with backend.
*
* @event Collection#sync
* @param {Collection} collection A collection.
* @param {Object} response Response from backend.
* @param {Object} o Options.
*/
/**
* Any number of models have been added, removed or changed.
*
* @event Collection#update
* @param {Collection} collection A collection.
* @param {Object} o Options.
*/
/**
* On reset.
*
* @event Collection#reset
* @param {Collection} collection A collection.
* @param {Object} o Options.
*/
/**
* On model sync.
*
* @event Collection#model-sync
* @param {Model} model A model.
* @param {Record & {action?: 'fetch'|'save'|'destroy'}} o Options.
* @since 9.1.0
*/
/**
* @typedef {Object} module:collection~Data
* @property {string|null} [primaryFilter]
* @property {string[]} [boolFilterList]
* @property {string} [textFilter]
* @property {string} [select]
* @property {string} [q]
*/
/**
* A collection.
*
* @mixes Bull.Events
* @copyright Credits to Backbone.js.
*/
class Collection {
/**
* An entity type.
*
* @type {string|null}
*/
entityType = null;
/**
* A total number of records.
*
* @type {number}
*/
total = 0;
/**
* A current offset (for pagination).
*
* @type {number}
*/
offset = 0;
/**
* A max size (for pagination).
*
* @type {number}
*/
maxSize = 20;
/**
* An order.
*
* @type {boolean|'asc'|'desc'|null}
*/
order = null;
/**
* An order-by field.
*
* @type {string|null}
*/
orderBy = null;
/**
* A where clause.
*
* @type {module:search-manager~whereItem[]|null}
*/
where = null;
/**
* @deprecated
* @type {module:search-manager~whereItem[]|null}
*/
whereAdditional = null;
/**
* A length correction.
*
* @type {number}
*/
lengthCorrection = 0;
/**
* A max max-size.
*
* @type {number}
*/
maxMaxSize = 0;
/**
* A where function.
*
* @type {function(): module:search-manager~whereItem[]}
*/
whereFunction;
/**
* A last sync request promise.
*
* @type {module:ajax.AjaxPromise|null}
*/
lastSyncPromise = null;
/**
* A parent model. To be used for own purposes. E.g. to have access to a parent from related models.
*
* @type {import('model').default}
*/
parentModel;
/**
* @param {Model[]|Record[]|null} [models] Models.
* @param {{
* entityType?: string,
* model?: Model.prototype,
* defs?: module:model~defs,
* order?: 'asc'|'desc'|boolean|null,
* orderBy?: string|null,
* urlRoot?: string,
* url?: string,
* maxSize?: number,
* }} [options] Options.
*/
constructor(models, options) {
options = {
...options
};
if (options.model) {
this.model = options.model;
}
if (options.maxSize !== undefined) {
this.maxSize = options.maxSize;
}
this._reset();
if (options.entityType) {
this.entityType = options.entityType;
/** @deprecated */
this.name = this.entityType;
}
/**
* A root URL.
*
* @public
* @type {string|null}
*/
this.urlRoot = options.urlRoot || this.urlRoot || this.entityType;
/**
* An URL.
*
* @type {string|null}
*/
this.url = options.url || this.url || this.urlRoot;
this.orderBy = options.orderBy || this.orderBy;
this.order = options.order || this.order;
this.defaultOrder = this.order;
this.defaultOrderBy = this.orderBy;
/** @type {module:model~defs} */
this.defs = options.defs || {};
/**
* @type {module:collection~Data | Record}
*/
this.data = {};
/**
* @private
* @type {Model#}
*/
this.model = options.model || _model.default;
if (models) {
this.reset(models, {
silent: true,
...options
});
}
}
/**
* Add models or a model.
*
* @param {Model[]|Model|Record[]|Record} models Models ar a model.
* @param {{
* merge?: boolean,
* at?: number,
* silent?: boolean,
* }} [options] Options. `at` – position; `merge` – merge existing models, otherwise, they are ignored.
* @return {this}
* @fires Collection#update
*/
add(models, options) {
this.set(models, {
merge: false,
...options,
...addOptions
});
return this;
}
/**
* Remove models or a model.
*
* @param {Model[]|Model|string} models Models, a model or a model ID.
* @param {{
* silent?: boolean,
* } & Object.} [options] Options.
* @return {this}
* @fires Collection#update
*/
remove(models, options) {
options = {
...options
};
const singular = !_underscore.default.isArray(models);
models = singular ? [models] : models.slice();
const removed = this._removeModels(models, options);
if (!options.silent && removed.length) {
options.changes = {
added: [],
merged: [],
removed: removed
};
this.trigger('update', this, options);
}
return this;
}
/**
* @protected
* @param {Model[]|Model|Record[]} models Models ar a model.
* @param {{
* silent?: boolean,
* at?: number,
* prepare?: boolean,
* add?: boolean,
* merge?: boolean,
* remove?: boolean,
* index?: number,
* } & Object.} [options]
* @return {Model[]}
*/
set(models, options) {
if (models == null) {
return [];
}
options = {
...setOptions,
...options
};
if (options.prepare && !this._isModel(models)) {
models = this.prepareAttributes(models, options) || [];
}
const singular = !_underscore.default.isArray(models);
models = singular ? [models] : models.slice();
let at = options.at;
if (at != null) {
at = +at;
}
if (at > this.length) {
at = this.length;
}
if (at < 0) {
at += this.length + 1;
}
const set = [];
const toAdd = [];
const toMerge = [];
const toRemove = [];
const modelMap = {};
const add = options.add;
const merge = options.merge;
const remove = options.remove;
let model, i;
for (i = 0; i < models.length; i++) {
model = models[i];
const existing = this._get(model);
if (existing) {
if (merge && model !== existing) {
let attributes = this._isModel(model) ? model.attributes : model;
if (options.prepare) {
attributes = existing.prepareAttributes(attributes, options);
}
existing.set(attributes, options);
toMerge.push(existing);
}
if (!modelMap[existing.cid]) {
modelMap[existing.cid] = true;
set.push(existing);
}
models[i] = existing;
} else if (add) {
model = models[i] = this._prepareModel(model);
if (model) {
toAdd.push(model);
this._addReference(model, options);
modelMap[model.cid] = true;
set.push(model);
}
}
}
// Remove stale models.
if (remove) {
for (i = 0; i < this.length; i++) {
model = this.models[i];
if (!modelMap[model.cid]) {
toRemove.push(model);
}
}
if (toRemove.length) {
this._removeModels(toRemove, options);
}
}
let orderChanged = false;
const replace = add && remove;
if (set.length && replace) {
orderChanged = this.length !== set.length || _underscore.default.some(this.models, (m, index) => {
return m !== set[index];
});
this.models.length = 0;
splice(this.models, set, 0);
this.length = this.models.length;
} else if (toAdd.length) {
splice(this.models, toAdd, at == null ? this.length : at);
this.length = this.models.length;
}
if (!options.silent) {
for (i = 0; i < toAdd.length; i++) {
if (at != null) {
options.index = at + i;
}
model = toAdd[i];
model.trigger('add', model, this, options);
}
if (orderChanged) {
this.trigger('sort', this, options);
}
if (toAdd.length || toRemove.length || toMerge.length) {
options.changes = {
added: toAdd,
removed: toRemove,
merged: toMerge
};
this.trigger('update', this, options);
}
}
return models;
}
/**
* Reset.
*
* @param {Model[]|null} [models] Models to replace the collection with.
* @param {{
* silent?: boolean,
* } & Object.} [options]
* @return {this}
* @fires Collection#reset
*/
reset(models, options) {
this.lengthCorrection = 0;
options = options ? _underscore.default.clone(options) : {};
for (let i = 0; i < this.models.length; i++) {
this._removeReference(this.models[i], options);
}
options.previousModels = this.models;
this._reset();
if (models) {
this.add(models, {
silent: true,
...options
});
}
if (!options.silent) {
this.trigger('reset', this, options);
}
return this;
}
/**
* Add a model at the end.
*
* @param {Model} model A model.
* @param {{
* silent?: boolean,
* }} [options] Options
* @return {this}
*/
push(model, options) {
this.add(model, {
at: this.length,
...options
});
return this;
}
/**
* Remove and return the last model.
*
* @param {{
* silent?: boolean,
* }} [options] Options
* @return {Model|null}
*/
pop(options) {
const model = this.at(this.length - 1);
if (!model) {
return null;
}
this.remove(model, options);
return model;
}
/**
* Add a model to the beginning.
*
* @param {Model} model A model.
* @param {{
* silent?: boolean,
* }} [options] Options
* @return {this}
*/
unshift(model, options) {
this.add(model, {
at: 0,
...options
});
return this;
}
/**
* Remove and return the first model.
*
* @param {{
* silent?: boolean,
* }} [options] Options
* @return {Model|null}
*/
shift(options) {
const model = this.at(0);
if (!model) {
return null;
}
this.remove(model, options);
return model;
}
/**
* Get a model by an ID.
*
* @todo Usage to _get.
* @param {string} id An ID.
* @return {Model|undefined}
*/
get(id) {
return this._get(id);
}
/**
* Whether a model in the collection.
*
* @todo Usage to _has.
* @param {string} id An ID.
* @return {boolean}
*/
has(id) {
return this._has(id);
}
/**
* Get a model by index.
*
* @param {number} index An index. Can be negative, then counted from the end.
* @return {Model|undefined}
*/
at(index) {
if (index < 0) {
index += this.length;
}
return this.models[index];
}
/**
* Iterates through a collection.
*
* @param {function(Model)} callback A function.
* @param {Object} [context] A context.
*/
forEach(callback, context) {
return this.models.forEach(callback, context);
}
/**
* Get an index of a model. Returns -1 if not found.
*
* @param {Model} model A model
* @return {number}
*/
indexOf(model) {
return this.models.indexOf(model);
}
/**
* @private
* @param {string|Object.|Model} obj
* @return {boolean}
*/
_has(obj) {
return !!this._get(obj);
}
/**
* @private
* @param {string|Object.|Model} obj
* @return {Model|undefined}
*/
_get(obj) {
if (obj == null) {
return void 0;
}
return this._byId[obj] || this._byId[this.modelId(obj.attributes || obj)] || obj.cid && this._byId[obj.cid];
}
/**
* @protected
* @param {Object.} attributes
* @return {*}
*/
modelId(attributes) {
return attributes['id'];
}
/** @private */
_reset() {
/**
* A number of records.
*/
this.length = 0;
/**
* Models.
*
* @type {Model[]}
*/
this.models = [];
/** @private */
this._byId = {};
}
/**
* @param {string} orderBy An order field.
* @param {bool|null|'desc'|'asc'} [order] True for desc.
* @returns {Promise}
*/
sort(orderBy, order) {
this.orderBy = orderBy;
if (order === true) {
order = 'desc';
} else if (order === false) {
order = 'asc';
}
this.order = order || 'asc';
return this.fetch();
}
/**
* Has previous page.
*
* @return {boolean}
*/
hasPreviousPage() {
return this.offset > 0;
}
/**
* Has next page.
*
* @return {boolean}
*/
hasNextPage() {
return this.total - this.offset > this.length || this.total === -1;
}
/**
* Next page.
*
* @returns {Promise}
*/
nextPage() {
return this.setOffset(this.offset + this.length);
}
/**
* Previous page.
*
* @returns {Promise}
*/
previousPage() {
return this.setOffset(Math.max(0, this.offset - this.maxSize));
}
/**
* First page.
*
* @returns {Promise}
*/
firstPage() {
return this.setOffset(0);
}
/**
* Last page.
*
* @returns {Promise}
*/
lastPage() {
let offset = this.total - this.total % this.maxSize;
if (offset === this.total) {
offset = this.total - this.maxSize;
}
return this.setOffset(offset);
}
/**
* Set an offset.
*
* @param {number} offset Offset.
* @returns {Promise}
*/
setOffset(offset) {
if (offset < 0) {
throw new RangeError('offset can not be less than 0');
}
if (offset > this.total && this.total !== -1 && this.total !== -2 && offset > 0) {
throw new RangeError('offset can not be larger than total count');
}
this.offset = offset;
return this.fetch({
maxSize: this.maxSize
});
}
/**
* Has more.
*
* @return {boolean}
*/
hasMore() {
return this.total > this.length + this.offset + this.lengthCorrection || this.total === -1;
}
/**
* Prepare attributes.
*
* @protected
* @param {Object.|Record[]} response A response from the backend.
* @param {Object.} options Options.
* @returns {Object.[]}
*/
prepareAttributes(response, options) {
this.total = response.total;
// noinspection JSUnusedGlobalSymbols
/**
* @deprecated As of v8.4. Use 'sync' event to obtain any additional data from a response.
*/
this.dataAdditional = response.additionalData || null;
return response.list;
}
/**
* Fetch from the backend.
*
* @param {{
* remove?: boolean,
* more?: boolean,
* offset?: number,
* maxSize?: number,
* orderBy?: string,
* order?: 'asc'|'desc',
* } & Object.} [options] Options.
* @returns {Promise}
* @fires Collection#sync Unless `{silent: true}`.
*/
fetch(options) {
options = {
...options
};
options.data = {
...options.data,
...this.data
};
this.offset = options.offset || this.offset;
this.orderBy = options.orderBy || this.orderBy;
this.order = options.order || this.order;
this.where = options.where || this.where;
const length = this.length + this.lengthCorrection;
if ('maxSize' in options) {
options.data.maxSize = options.maxSize;
} else {
options.data.maxSize = options.more ? this.maxSize : Math.max(length, this.maxSize);
if (this.maxMaxSize && options.data.maxSize > this.maxMaxSize) {
options.data.maxSize = this.maxMaxSize;
}
}
options.data.offset = options.more ? this.offset + length : this.offset;
options.data.orderBy = this.orderBy;
options.data.order = this.order;
options.data.whereGroup = this.getWhere();
if (options.data.select) {
options.data.attributeSelect = options.data.select;
delete options.data.select;
}
options = {
prepare: true,
...options
};
const success = options.success;
options.success = response => {
options.reset ? this.reset(response, options) : this.set(response, options);
if (success) {
success.call(options.context, this, response, options);
}
this.trigger('sync', this, response, options);
};
const error = options.error;
options.error = response => {
if (error) {
error.call(options.context, this, response, options);
}
this.trigger('error', this, response, options);
};
this.lastSyncPromise = _model.default.prototype.sync.call(this, 'read', this, options);
return this.lastSyncPromise;
}
/**
* Is being fetched.
*
* @return {boolean}
*/
isBeingFetched() {
return this.lastSyncPromise && this.lastSyncPromise.getReadyState() < 4;
}
/**
* Abort the last fetch.
*/
abortLastFetch() {
if (this.isBeingFetched()) {
this.lastSyncPromise.abort();
}
}
/**
* Get a where clause.
*
* @returns {module:search-manager~whereItem[]}
*/
getWhere() {
let where = (this.where ?? []).concat(this.whereAdditional || []);
if (this.whereFunction) {
where = where.concat(this.whereFunction() || []);
}
return where;
}
/**
* Get an entity type.
*
* @returns {string}
*/
getEntityType() {
return this.entityType || this.name;
}
/**
* Reset the order to default.
*/
resetOrderToDefault() {
this.orderBy = this.defaultOrderBy;
this.order = this.defaultOrder;
}
/**
* Set an order.
*
* @param {string|null} orderBy
* @param {boolean|'asc'|'desc'|null} [order]
* @param {boolean} [setDefault]
*/
setOrder(orderBy, order, setDefault) {
this.orderBy = orderBy;
this.order = order;
if (setDefault) {
this.defaultOrderBy = orderBy;
this.defaultOrder = order;
}
}
/**
* Clone.
*
* @param {{withModels?: boolean}} [options]
* @return {Collection}
*/
clone() {
let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
let models = this.models;
if (options.withModels) {
models = this.models.map(m => m.clone());
}
const collection = new this.constructor(models, {
model: this.model,
entityType: this.entityType,
defs: this.defs,
orderBy: this.orderBy,
order: this.order
});
collection.name = this.name;
collection.urlRoot = this.urlRoot;
collection.url = this.url;
collection.defaultOrder = this.defaultOrder;
collection.defaultOrderBy = this.defaultOrderBy;
collection.data = Espo.Utils.cloneDeep(this.data);
collection.where = Espo.Utils.cloneDeep(this.where);
collection.whereAdditional = Espo.Utils.cloneDeep(this.whereAdditional);
collection.total = this.total;
collection.offset = this.offset;
collection.maxSize = this.maxSize;
collection.maxMaxSize = this.maxMaxSize;
collection.whereFunction = this.whereFunction;
collection.parentModel = this.parentModel;
return collection;
}
/**
* Prepare an empty model instance.
*
* @return {Model}
*/
prepareModel() {
return this._prepareModel({});
}
// noinspection JSUnusedGlobalSymbols
/**
* Compose a URL for syncing. Called from Model.sync.
*
* @protected
* @return {string}
*/
composeSyncUrl() {
return this.url;
}
/** @private */
_isModel(object) {
return object instanceof _model.default;
}
/** @private */
_removeModels(models, options) {
const removed = [];
for (let i = 0; i < models.length; i++) {
const model = this.get(models[i]);
if (!model) {
continue;
}
const index = this.models.indexOf(model);
this.models.splice(index, 1);
this.length--;
delete this._byId[model.cid];
const id = this.modelId(model.attributes);
if (id != null) {
delete this._byId[id];
}
if (!options.silent) {
options.index = index;
model.trigger('remove', model, this, options);
}
removed.push(model);
this._removeReference(model, options);
}
return removed;
}
/** @private */
_addReference(model) {
this._byId[model.cid] = model;
const id = this.modelId(model.attributes);
if (id != null) {
this._byId[id] = model;
}
model.on('all', this._onModelEvent, this);
}
/** @private */
_removeReference(model) {
delete this._byId[model.cid];
const id = this.modelId(model.attributes);
if (id != null) {
delete this._byId[id];
}
if (this === model.collection) {
delete model.collection;
}
model.off('all', this._onModelEvent, this);
}
/** @private */
_onModelEvent(event, model, collection, options) {
// @todo Revise. Never triggerred? Remove?
if (event === 'sync' && collection !== this) {
return;
}
if (!model) {
this.trigger.apply(this, arguments);
return;
}
if ((event === 'add' || event === 'remove') && collection !== this) {
return;
}
if (event === 'destroy') {
this.remove(model, options);
}
if (event === 'change') {
const prevId = this.modelId(model.previousAttributes());
const id = this.modelId(model.attributes);
if (prevId !== id) {
if (prevId != null) {
delete this._byId[prevId];
}
if (id != null) {
this._byId[id] = model;
}
}
}
this.trigger.apply(this, arguments);
}
// noinspection JSDeprecatedSymbols
/** @private*/
_prepareModel(attributes) {
if (this._isModel(attributes)) {
if (!attributes.collection) {
attributes.collection = this;
}
return attributes;
}
const ModelClass = this.model;
// noinspection JSValidateTypes
return new ModelClass(attributes, {
collection: this,
entityType: this.entityType || this.name,
defs: this.defs
});
}
}
Object.assign(Collection.prototype, _bullbone.Events);
Collection.extend = _bullbone.View.extend;
const setOptions = {
add: true,
remove: true,
merge: true
};
const addOptions = {
add: true,
remove: false
};
const splice = (array, insert, at) => {
at = Math.min(Math.max(at, 0), array.length);
const tail = Array(array.length - at);
const length = insert.length;
let i;
for (i = 0; i < tail.length; i++) {
tail[i] = array[i + at];
}
for (i = 0; i < length; i++) {
array[i + at] = insert[i];
}
for (i = 0; i < tail.length; i++) {
array[i + length + at] = tail[i];
}
};
var _default = _exports.default = Collection;
});
define("collection-factory", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* 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.
************************************************************************/
/** @module collection-factory */
/**
* A collection factory.
*/
class CollectionFactory {
/**
* @param {module:model-factory} modelFactory
* @param {module:models/settings} config
* @param {module:metadata} metadata
*/
constructor(modelFactory, config, metadata) {
/** @private */
this.modelFactory = modelFactory;
/** @private */
this.metadata = metadata;
/** @private */
this.recordListMaxSizeLimit = config.get('recordListMaxSizeLimit') || 200;
}
/**
* Create a collection.
*
* @param {string} entityType An entity type.
* @param {Function} [callback] Deprecated.
* @param {Object} [context] Deprecated.
* @returns {Promise}
*/
create(entityType, callback, context) {
return new Promise(resolve => {
context = context || this;
this.modelFactory.getSeed(entityType, Model => {
const orderBy = this.modelFactory.metadata.get(['entityDefs', entityType, 'collection', 'orderBy']);
const order = this.modelFactory.metadata.get(['entityDefs', entityType, 'collection', 'order']);
const className = this.modelFactory.metadata.get(['clientDefs', entityType, 'collection']) || 'collection';
const defs = this.metadata.get(['entityDefs', entityType]) || {};
Espo.loader.require(className, Collection => {
const collection = new Collection(null, {
entityType: entityType,
orderBy: orderBy,
order: order,
defs: defs
});
collection.model = Model;
collection.entityType = entityType;
collection.maxMaxSize = this.recordListMaxSizeLimit;
if (callback) {
callback.call(context, collection);
}
resolve(collection);
});
});
});
}
}
var _default = _exports.default = CollectionFactory;
});
define("cache", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* 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.
************************************************************************/
/** @module cache */
/**
* Cache for source and resource files.
*/
class Cache {
/**
* @param {Number} [cacheTimestamp] A cache timestamp.
*/
constructor(cacheTimestamp) {
this.basePrefix = this.prefix;
if (cacheTimestamp) {
this.prefix = this.basePrefix + '-' + cacheTimestamp;
}
if (!this.get('app', 'timestamp')) {
this.storeTimestamp();
}
}
/** @private */
prefix = 'cache';
/**
* Handle actuality. Clears cache if not actual.
*
* @param {Number} cacheTimestamp A cache timestamp.
*/
handleActuality(cacheTimestamp) {
const storedTimestamp = this.getCacheTimestamp();
if (storedTimestamp) {
if (storedTimestamp !== cacheTimestamp) {
this.clear();
this.set('app', 'cacheTimestamp', cacheTimestamp);
this.storeTimestamp();
}
return;
}
this.clear();
this.set('app', 'cacheTimestamp', cacheTimestamp);
this.storeTimestamp();
}
/**
* Get a cache timestamp.
*
* @returns {number}
*/
getCacheTimestamp() {
return parseInt(this.get('app', 'cacheTimestamp') || 0);
}
/**
* @todo Revise whether is needed.
*/
storeTimestamp() {
const frontendCacheTimestamp = Date.now();
this.set('app', 'timestamp', frontendCacheTimestamp);
}
/**
* @private
* @param {string} type
* @returns {string}
*/
composeFullPrefix(type) {
return this.prefix + '-' + type;
}
/**
* @private
* @param {string} type
* @param {string} name
* @returns {string}
*/
composeKey(type, name) {
return this.composeFullPrefix(type) + '-' + name;
}
/**
* @private
* @param {string} type
*/
checkType(type) {
if (typeof type === 'undefined' && toString.call(type) !== '[object String]') {
throw new TypeError("Bad type \"" + type + "\" passed to Cache().");
}
}
/**
* Get a stored value.
*
* @param {string} type A type/category.
* @param {string} name A name.
* @returns {string|null} Null if no stored value.
*/
get(type, name) {
this.checkType(type);
const key = this.composeKey(type, name);
let stored;
try {
stored = localStorage.getItem(key);
} catch (error) {
console.error(error);
return null;
}
if (stored) {
let result = stored;
if (stored.length > 9 && stored.substring(0, 9) === '__JSON__:') {
const jsonString = stored.slice(9);
try {
result = JSON.parse(jsonString);
} catch (error) {
result = stored;
}
}
return result;
}
return null;
}
/**
* Store a value.
*
* @param {string} type A type/category.
* @param {string} name A name.
* @param {any} value A value.
*/
set(type, name, value) {
this.checkType(type);
const key = this.composeKey(type, name);
if (value instanceof Object || Array.isArray(value)) {
value = '__JSON__:' + JSON.stringify(value);
}
try {
localStorage.setItem(key, value);
} catch (error) {
console.log('Local storage limit exceeded.');
}
}
/**
* Clear a stored value.
*
* @param {string} [type] A type/category.
* @param {string} [name] A name.
*/
clear(type, name) {
let reText;
if (typeof type !== 'undefined') {
if (typeof name === 'undefined') {
reText = '^' + this.composeFullPrefix(type);
} else {
reText = '^' + this.composeKey(type, name);
}
} else {
reText = '^' + this.basePrefix + '-';
}
const re = new RegExp(reText);
for (const i in localStorage) {
if (re.test(i)) {
delete localStorage[i];
}
}
}
}
var _default = _exports.default = Cache;
});
define("broadcast-channel", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* 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.
************************************************************************/
/** @module broadcast-channel */
class BroadcastChannel {
constructor() {
this.object = null;
if (window.BroadcastChannel) {
this.object = new window.BroadcastChannel('app');
}
}
/**
* Post a message.
*
* @param {string} message A message.
*/
postMessage(message) {
if (!this.object) {
return;
}
this.object.postMessage(message);
}
/**
* @callback module:broadcast-channel~callback
*
* @param {MessageEvent} event An event. A message can be obtained from the `data` property.
*/
/**
* Subscribe to a message.
*
* @param {module:broadcast-channel~callback} callback A callback.
*/
subscribe(callback) {
if (!this.object) {
return;
}
this.object.addEventListener('message', callback);
}
}
var _default = _exports.default = BroadcastChannel;
});
define("app-params", ["exports"], function (_exports) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* 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.
************************************************************************/
/**
* Application parameters.
*
* @since 9.0.0
*/
class AppParams {
/**
* @param {Record} params
*/
constructor() {
let params = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
/** @private */
this.params = params;
}
/**
* Get a parameter.
*
* @param {string} name A parameter.
* @return {*}
*/
get(name) {
return this.params[name];
}
/**
* Set all parameters.
*
* @internal
* @param {Record} params
*/
setAll(params) {
this.params = params;
}
/**
* Reload params from the backend.
*/
async load() {
/** @type {module:app~UserData} */
const data = await Espo.Ajax.getRequest('App/appParams');
this.params = data;
}
}
_exports.default = AppParams;
});
define("ajax", ["exports", "jquery", "utils"], function (_exports, _jquery, _utils) {
"use strict";
Object.defineProperty(_exports, "__esModule", {
value: true
});
_exports.default = void 0;
_jquery = _interopRequireDefault(_jquery);
_utils = _interopRequireDefault(_utils);
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* 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.
************************************************************************/
/** @module ajax */
let isConfigured = false;
/** @type {number} */
let defaultTimeout;
/** @type {string} */
let apiUrl;
/** @type {Espo.Ajax~Handler} */
let beforeSend;
/** @type {Espo.Ajax~Handler} */
let onSuccess;
/** @type {Espo.Ajax~Handler} */
let onError;
/** @type {Espo.Ajax~Handler} */
let onTimeout;
/** @type {function()} */
let onOffline;
/**
* @callback Espo.Ajax~Handler
* @param {XMLHttpRequest} [xhr]
* @param {Object.} [options]
*/
/**
* Options.
*
* @typedef {Object} Espo.Ajax~Options
*
* @property {Number} [timeout] A timeout.
* @property {Object.} [headers] A request headers.
* @property {'json'|'text'} [dataType] A data type.
* @property {string} [contentType] A content type.
* @property {boolean} [resolveWithXhr] To resolve with `XMLHttpRequest`.
*/
const baseUrl = _utils.default.obtainBaseUrl();
// noinspection JSUnusedGlobalSymbols
/**
* Functions for API HTTP requests.
*/
const Ajax = Espo.Ajax = {
/**
* Request.
*
* @param {string} url An URL.
* @param {'GET'|'POST'|'PUT'|'DELETE'|'PATCH'|'OPTIONS'} method An HTTP method.
* @param {*} [data] Data.
* @param {Espo.Ajax~Options & Object.} [options] Options.
* @returns {AjaxPromise}
*/
request: function (url, method, data, options) {
options = options || {};
const timeout = 'timeout' in options ? options.timeout : defaultTimeout;
const contentType = options.contentType || 'application/json';
let body;
if (options.data && !data) {
data = options.data;
}
if (apiUrl) {
url = Espo.Utils.trimSlash(apiUrl) + '/' + url;
}
if (!['GET', 'OPTIONS'].includes(method) && data) {
body = data;
if (contentType === 'application/json' && typeof data !== 'string') {
body = JSON.stringify(data);
}
}
if (method === 'GET' && data) {
const part = _jquery.default.param(data);
url.includes('?') ? url += '&' : url += '?';
url += part;
}
const urlObj = new URL(baseUrl + url);
const xhr = new Xhr();
xhr.timeout = timeout;
xhr.open(method, urlObj);
xhr.setRequestHeader('Content-Type', contentType);
if (options.headers) {
for (const key in options.headers) {
xhr.setRequestHeader(key, options.headers[key]);
}
}
if (beforeSend) {
beforeSend(xhr, options);
}
const promiseWrapper = {};
const promise = new AjaxPromise((resolve, reject) => {
const onErrorGeneral = isTimeout => {
if (options.error) {
options.error(xhr, options);
}
reject(xhr, options);
if (isTimeout) {
if (onTimeout) {
onTimeout(xhr, options);
}
return;
}
if (xhr.status === 0 && !navigator.onLine && onOffline) {
onOffline();
return;
}
if (onError) {
onError(xhr, options);
}
};
xhr.ontimeout = () => onErrorGeneral(true);
xhr.onerror = () => onErrorGeneral();
xhr.onload = () => {
if (xhr.status >= 400) {
onErrorGeneral();
return;
}
let response = xhr.responseText;
if ((options.dataType || 'json') === 'json') {
try {
response = JSON.parse(xhr.responseText);
} catch (e) {
console.error('Could not parse API response.');
onErrorGeneral();
}
}
if (options.success) {
options.success(response);
}
onSuccess(xhr, options);
if (options.resolveWithXhr) {
response = xhr;
}
resolve(response);
};
xhr.send(body);
if (promiseWrapper.promise) {
promiseWrapper.promise.xhr = xhr;
return;
}
promiseWrapper.xhr = xhr;
});
promiseWrapper.promise = promise;
promise.xhr = promise.xhr || promiseWrapper.xhr;
return promise;
},
/**
* POST request.
*
* @param {string} url An URL.
* @param {*} [data] Data.
* @param {Espo.Ajax~Options & Object.} [options] Options.
* @returns {Promise & AjaxPromise}
*/
postRequest: function (url, data, options) {
if (data) {
data = JSON.stringify(data);
}
return /** @type {Promise & AjaxPromise} */Ajax.request(url, 'POST', data, options);
},
/**
* PATCH request.
*
* @param {string} url An URL.
* @param {*} [data] Data.
* @param {Espo.Ajax~Options & Object.} [options] Options.
* @returns {Promise & AjaxPromise}
*/
patchRequest: function (url, data, options) {
if (data) {
data = JSON.stringify(data);
}
return /** @type {Promise & AjaxPromise} */Ajax.request(url, 'PATCH', data, options);
},
/**
* PUT request.
*
* @param {string} url An URL.
* @param {*} [data] Data.
* @param {Espo.Ajax~Options & Object.} [options] Options.
* @returns {Promise & AjaxPromise}
*/
putRequest: function (url, data, options) {
if (data) {
data = JSON.stringify(data);
}
return /** @type {Promise & AjaxPromise} */Ajax.request(url, 'PUT', data, options);
},
/**
* DELETE request.
*
* @param {string} url An URL.
* @param {*} [data] Data.
* @param {Espo.Ajax~Options & Object.} [options] Options.
* @returns {Promise & AjaxPromise}
*/
deleteRequest: function (url, data, options) {
if (data) {
data = JSON.stringify(data);
}
return /** @type {Promise & AjaxPromise} */Ajax.request(url, 'DELETE', data, options);
},
/**
* GET request.
*
* @param {string} url An URL.
* @param {*} [data] Data.
* @param {Espo.Ajax~Options & Object.