define("di", ["exports"], function (_exports) { "use strict"; Object.defineProperty(_exports, "__esModule", { value: true }); _exports.container = void 0; _exports.inject = inject; _exports.register = register; /************************************************************************ * This file is part of EspoCRM. * * EspoCRM – Open Source CRM application. * Copyright (C) 2014-2025 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. ************************************************************************/ const registry = new Map(); const container = _exports.container = new Map(); /** * A DI container. */ /** * A 'register' decorator. * * @param {*[]} argumentList Arguments. * @return {function(typeof Object)} */ function register() { let argumentList = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; return function (classObject) { registry.set(classObject, argumentList); }; } /** * An 'inject' decorator. * * @param {typeof Object} classObject A class. * @return {(function(*, Object): void)} */ function inject(classObject) { /** * @param {{addInitializer: function(function())}} context */ return function (value, context) { context.addInitializer(function () { let instance = container.get(classObject); if (!instance) { instance = Reflect.construct(classObject, registry.get(classObject)); container.set(classObject, instance); } this[context.name] = instance; }); }; } }); define("view", ["exports", "bullbone"], function (_exports, _bullbone) { "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-2025 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 view */ /** * A base view. All views should extend this class. * * @see https://docs.espocrm.com/development/view/ * @mixes Bull.Events */ class View extends _bullbone.View { /** * @callback module:view~actionHandlerCallback * @param {MouseEvent} event A DOM event. * @param {HTMLElement} element A target element. */ /** * A helper. * * @name _helper * @type {module:view-helper} * @memberOf View.prototype * @private */ /** * A model. * * @type {import('model').default} */ model; /** * A collection. * * @type {import('collection').default} */ collection; /** * @param {Record} options */ constructor() { let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; super(options); if (options.model) { this.model = options.model; } if (options.collection) { this.collection = options.collection; } } // noinspection JSUnusedGlobalSymbols /** * When the view is ready. Can be useful to prevent race condition when re-initialization is needed * in-between initialization and render. * * @return Promise * @todo Move to Bull.View. */ whenReady() { if (this.isReady) { return Promise.resolve(); } return new Promise(resolve => { this.once('ready', () => resolve()); }); } /** * Add a DOM click event handler for a target defined by `data-action="{name}"` attribute. * * @param {string} action An action name. * @param {module:view~actionHandlerCallback} handler A handler. */ addActionHandler(action, handler) { // The key should be in sync with one in Utils.handleAction. const fullAction = `click [data-action="${action}"]`; this.events[fullAction] = e => { // noinspection JSUnresolvedReference handler.call(this, e.originalEvent, e.currentTarget); }; } /** * Escape a string. * * @param {string} string * @returns {string} */ escapeString(string) { return Handlebars.Utils.escapeExpression(string); } /** * Show a notify-message. * * @deprecated Use `Espo.Ui.notify`. * @param {string|false} label * @param {string} [type] * @param {number} [timeout] * @param {string} [scope] */ notify(label, type, timeout, scope) { if (!label) { Espo.Ui.notify(false); return; } scope = scope || null; timeout = timeout || 2000; if (!type) { timeout = void 0; } const text = this.getLanguage().translate(label, 'labels', scope); Espo.Ui.notify(text, type, timeout); } /** * Get a view-helper. * * @returns {module:view-helper} */ getHelper() { return this._helper; } /** * Get a current user. * * @returns {module:models/user} */ getUser() { return this._helper.user; } /** * Get the preferences. * * @returns {module:models/preferences} */ getPreferences() { return this._helper.preferences; } /** * Get the config. * * @returns {module:models/settings} */ getConfig() { return this._helper.settings; } /** * Get the ACL. * * @returns {module:acl-manager} */ getAcl() { return this._helper.acl; } /** * Get the model factory. * * @returns {module:model-factory} */ getModelFactory() { return this._helper.modelFactory; } /** * Get the collection factory. * * @returns {module:collection-factory} */ getCollectionFactory() { return this._helper.collectionFactory; } /** * Get the router. * * @returns {module:router} */ getRouter() { return this._helper.router; } /** * Get the storage-util. * * @returns {module:storage} */ getStorage() { return this._helper.storage; } /** * Get the session-storage-util. * * @returns {module:session-storage} */ getSessionStorage() { return this._helper.sessionStorage; } /** * Get the language-util. * * @returns {module:language} */ getLanguage() { return this._helper.language; } /** * Get metadata. * * @returns {module:metadata} */ getMetadata() { return this._helper.metadata; } /** * Get the cache-util. * * @returns {module:cache} */ getCache() { return this._helper.cache; } /** * Get the date-time util. * * @returns {module:date-time} */ getDateTime() { return this._helper.dateTime; } /** * Get the number-util. * * @returns {module:num-util} */ getNumberUtil() { return this._helper.numberUtil; } /** * Get the field manager. * * @returns {module:field-manager} */ getFieldManager() { return this._helper.fieldManager; } /** * Get the base-controller. * * @returns {module:controllers/base} */ getBaseController() { return this._helper.baseController; } /** * Get the theme manager. * * @returns {module:theme-manager} */ getThemeManager() { return this._helper.themeManager; } /** * Update a page title. Supposed to be overridden if needed. */ updatePageTitle() { const title = this.getConfig().get('applicationName') || 'EspoCRM'; this.setPageTitle(title); } /** * Set a page title. * * @param {string} title A title. */ setPageTitle(title) { this.getHelper().pageTitle.setTitle(title); } /** * Translate a label. * * @param {string} label Label. * @param {string|'messages'|'labels'|'fields'|'links'|'scopeNames'|'scopeNamesPlural'} [category='labels'] Category. * @param {string} [scope='Global'] Scope. * @returns {string} */ translate(label, category, scope) { return this.getLanguage().translate(label, category, scope); } /** * Get a base path. * * @returns {string} */ getBasePath() { return this._helper.basePath || ''; } /** * @typedef {Object} module:view~ConfirmOptions * * @property {string} message A message. * @property {string} [confirmText] A confirm-button text. * @property {string} [cancelText] A cancel-button text. * @property {'danger'|'success'|'warning'|'default'} [confirmStyle='danger'] A confirm-button style. * @property {'static'|boolean} [backdrop=false] A backdrop. * @property {function():void} [cancelCallback] A cancel-callback. */ /** * Show a confirmation dialog. * * @param {string|module:view~ConfirmOptions} o A message or options. * @param [callback] A callback. Deprecated, use a promise. * @param [context] A context. Deprecated. * @returns {Promise} To be resolved if confirmed. */ confirm(o, callback, context) { let message; if (typeof o === 'string' || o instanceof String) { message = o; o = /** @type {module:view~ConfirmOptions} */{}; } else { o = o || {}; message = o.message; } if (message) { message = this.getHelper().transformMarkdownText(message, { linksInNewTab: true }).toString(); } const confirmText = o.confirmText || this.translate('Yes'); const confirmStyle = o.confirmStyle || null; const cancelText = o.cancelText || this.translate('Cancel'); return Espo.Ui.confirm(message, { confirmText: confirmText, cancelText: cancelText, confirmStyle: confirmStyle, backdrop: 'backdrop' in o ? o.backdrop : true, isHtml: true, cancelCallback: o.cancelCallback }, callback, context); } } var _default = _exports.default = View; }); define("date-time", ["exports", "moment"], function (_exports, _moment) { "use strict"; Object.defineProperty(_exports, "__esModule", { value: true }); _exports.default = void 0; _moment = _interopRequireDefault(_moment); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } /************************************************************************ * This file is part of EspoCRM. * * EspoCRM – Open Source CRM application. * Copyright (C) 2014-2025 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 date-time */ /** * A date-time util. */ class DateTime { constructor() {} /** * A system date format. * * @type {string} */ internalDateFormat = 'YYYY-MM-DD'; /** * A system date-time format. * * @type {string} */ internalDateTimeFormat = 'YYYY-MM-DD HH:mm'; /** * A system date-time format including seconds. * * @type {string} */ internalDateTimeFullFormat = 'YYYY-MM-DD HH:mm:ss'; /** * A date format for a current user. * * @type {string} */ dateFormat = 'MM/DD/YYYY'; /** * A time format for a current user. * * @type {string} */ timeFormat = 'HH:mm'; /** * A time zone for a current user. * * @type {string|null} */ timeZone = null; /** * A system time zone. * * @type {string} */ systemTimeZone; /** * A week start for a current user. * * @type {Number} */ weekStart = 1; /** @private */ readableDateFormatMap = { 'DD.MM.YYYY': 'DD MMM', 'DD/MM/YYYY': 'DD MMM' }; /** @private */ readableShortDateFormatMap = { 'DD.MM.YYYY': 'D MMM', 'DD/MM/YYYY': 'D MMM' }; /** * Whether a time format has a meridian (am/pm). * * @returns {boolean} */ hasMeridian() { return new RegExp('A', 'i').test(this.timeFormat); } /** * Get a date format. * * @returns {string} */ getDateFormat() { return this.dateFormat; } /** * Get a time format. * * @returns {string} */ getTimeFormat() { return this.timeFormat; } /** * Get a date-time format. * * @returns {string} */ getDateTimeFormat() { return this.dateFormat + ' ' + this.timeFormat; } /** * Get a readable date format. * * @returns {string} */ getReadableDateFormat() { return this.readableDateFormatMap[this.getDateFormat()] || 'MMM DD'; } /** * Get a readable short date format. * * @returns {string} */ getReadableShortDateFormat() { return this.readableShortDateFormatMap[this.getDateFormat()] || 'MMM D'; } // noinspection JSUnusedGlobalSymbols /** * Get a readable date-time format. * * @returns {string} */ getReadableDateTimeFormat() { return this.getReadableDateFormat() + ' ' + this.timeFormat; } /** * Get a readable short date-time format. * * @returns {string} */ getReadableShortDateTimeFormat() { return this.getReadableShortDateFormat() + ' ' + this.timeFormat; } /** * Convert a date from a display representation to system. * * @param {string} string A date value. * @returns {string|-1} A system date value. */ fromDisplayDate(string) { const m = (0, _moment.default)(string, this.dateFormat); if (!m.isValid()) { return -1; } return m.format(this.internalDateFormat); } /** * Get a time-zone. * * @returns {string} */ getTimeZone() { return this.timeZone ? this.timeZone : 'UTC'; } /** * Convert a date from system to a display representation. * * @param {string} string A system date value. * @returns {string} A display date value. */ toDisplayDate(string) { if (!string || typeof string !== 'string') { return ''; } const m = (0, _moment.default)(string, this.internalDateFormat); if (!m.isValid()) { return ''; } return m.format(this.dateFormat); } /** * Convert a date-time from system to a display representation. * * @param {string} string A system date-time value. * @returns {string|-1} A display date-time value. */ fromDisplay(string) { let m; if (this.timeZone) { m = _moment.default.tz(string, this.getDateTimeFormat(), this.timeZone).utc(); } else { m = _moment.default.utc(string, this.getDateTimeFormat()); } if (!m.isValid()) { return -1; } return m.format(this.internalDateTimeFormat) + ':00'; } /** * Convert a date-time from system to a display representation. * * @param {string} string A system date value. * @returns {string} A display date-time value. */ toDisplay(string) { if (!string) { return ''; } return this.toMoment(string).format(this.getDateTimeFormat()); } /** * Get a now moment. * * @returns {moment.Moment} */ getNowMoment() { return (0, _moment.default)().tz(this.getTimeZone()); } /** * Convert a system-formatted date to a moment. * * @param {string} string A date value in a system representation. * @returns {moment.Moment} * @internal */ toMomentDate(string) { return _moment.default.tz(string, this.internalDateFormat, this.systemTimeZone); } /** * Convert a system-formatted date-time to a moment. * * @param {string} string A date-time value in a system representation. * @returns {moment.Moment} * @internal */ toMoment(string) { let m = _moment.default.utc(string, this.internalDateTimeFullFormat); if (this.timeZone) { // noinspection JSUnresolvedReference m = m.tz(this.timeZone); } return m; } /** * Convert a date-time value from ISO to a system representation. * * @param {string} string * @returns {string} A date-time value in a system representation. */ fromIso(string) { if (!string) { return ''; } const m = (0, _moment.default)(string).utc(); return m.format(this.internalDateTimeFormat); } // noinspection JSUnusedGlobalSymbols /** * Convert a date-time value from system to an ISO representation. * * @param string A date-time value in a system representation. * @returns {string} An ISO date-time value. */ toIso(string) { return this.toMoment(string).format(); } /** * Get a today date value in a system representation. * * @returns {string} */ getToday() { return (0, _moment.default)().tz(this.getTimeZone()).format(this.internalDateFormat); } /** * Get a date-time value in a system representation, shifted from now. * * @param {Number} shift A number to shift by. * @param {'minutes'|'hours'|'days'|'weeks'|'months'|'years'} type A shift unit. * @param {Number} [multiplicity] A number of minutes a value will be aliquot to. * @returns {string} A date-time value in a system representation */ getDateTimeShiftedFromNow(shift, type, multiplicity) { if (!multiplicity) { return _moment.default.utc().add(shift, type).format(this.internalDateTimeFormat); } let unix = (0, _moment.default)().unix(); unix = unix - unix % (multiplicity * 60); return _moment.default.unix(unix).utc().add(shift, type).format(this.internalDateTimeFormat); } /** * Get a date value in a system representation, shifted from today. * * @param {Number} shift A number to shift by. * @param {'days'|'weeks'|'months'|'years'} type A shift unit. * @returns {string} A date value in a system representation */ getDateShiftedFromToday(shift, type) { return _moment.default.tz(this.getTimeZone()).add(shift, type).format(this.internalDateFormat); } /** * Get a now date-time value in a system representation. * * @param {Number} [multiplicity] A number of minutes a value will be aliquot to. * @returns {string} */ getNow(multiplicity) { if (!multiplicity) { return _moment.default.utc().format(this.internalDateTimeFormat); } let unix = (0, _moment.default)().unix(); unix = unix - unix % (multiplicity * 60); return _moment.default.unix(unix).utc().format(this.internalDateTimeFormat); } /** * Set settings and preferences. * * @param {module:models/settings} settings Settings. * @param {module:models/preferences} preferences Preferences. * @internal */ setSettingsAndPreferences(settings, preferences) { if (settings.has('dateFormat')) { this.dateFormat = settings.get('dateFormat'); } if (settings.has('timeFormat')) { this.timeFormat = settings.get('timeFormat'); } if (settings.has('timeZone')) { this.timeZone = settings.get('timeZone') || null; this.systemTimeZone = this.timeZone || 'UTC'; if (this.timeZone === 'UTC') { this.timeZone = null; } } if (settings.has('weekStart')) { this.weekStart = settings.get('weekStart'); } preferences.on('change', model => { if (model.has('dateFormat') && model.get('dateFormat')) { this.dateFormat = model.get('dateFormat'); } if (model.has('timeFormat') && model.get('timeFormat')) { this.timeFormat = model.get('timeFormat'); } if (model.has('timeZone') && model.get('timeZone')) { this.timeZone = model.get('timeZone'); } if (model.has('weekStart') && model.get('weekStart') !== -1) { this.weekStart = model.get('weekStart'); } if (this.timeZone === 'UTC') { this.timeZone = null; } }); } /** * Set a language. * * @param {module:language} language A language. * @internal */ setLanguage(language) { _moment.default.updateLocale('en', { months: language.translatePath(['Global', 'lists', 'monthNames']), monthsShort: language.translatePath(['Global', 'lists', 'monthNamesShort']), weekdays: language.translatePath(['Global', 'lists', 'dayNames']), weekdaysShort: language.translatePath(['Global', 'lists', 'dayNamesShort']), weekdaysMin: language.translatePath(['Global', 'lists', 'dayNamesMin']) }); _moment.default.locale('en'); } } var _default = _exports.default = DateTime; }); define("helpers/site/shortcut-manager", ["exports", "di"], function (_exports, _di) { "use strict"; Object.defineProperty(_exports, "__esModule", { value: true }); _exports.default = void 0; var _temp; let _initClass; /************************************************************************ * This file is part of EspoCRM. * * EspoCRM – Open Source CRM application. * Copyright (C) 2014-2025 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 _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; } /** @typedef {import('view').default} View */ /** @typedef {string|function(KeyboardEvent): void} Key */ let _ShortcutManager = _exports.default = void 0; class ShortcutManager { static #_ = (_temp = [_ShortcutManager, _initClass] = _applyDecs(this, [(0, _di.register)()], []).c, _exports.default = _ShortcutManager, _temp); /** * @private * @type {number} */ level = 0; /** * @private * @type {{ * view: View[], * keys: Record., * level: number, * }[]} */ items; constructor() { this.items = []; document.addEventListener('keydown', event => this.handle(event), { capture: true }); } /** * Add a view and keys. * * @param {import('view').default} view * @param {Record.} keys * @param {{stack: boolean}} [options] */ add(view, keys) { let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; if (this.items.find(it => it.view === view)) { return; } if (options.stack) { this.level++; } this.items.push({ view: view, keys: keys, level: this.level }); } /** * Remove a view. * * @param {import('view').default} view */ remove(view) { const index = this.items.findIndex(it => it.view === view); if (index < 0) { return; } this.items.splice(index, 1); let maxLevel = 0; for (const item of this.items) { if (item.level > maxLevel) { maxLevel = item.level; } } this.level = maxLevel; } /** * Handle. * * @param {KeyboardEvent} event */ handle(event) { const items = this.items.filter(it => it.level === this.level); if (items.length === 0) { return; } const key = Espo.Utils.getKeyFromKeyEvent(event); for (const item of items) { const subject = item.keys[key]; if (!subject) { continue; } if (typeof subject === 'function') { subject.call(item.view, event); break; } event.preventDefault(); event.stopPropagation(); const methodName = 'action' + Espo.Utils.upperCaseFirst(subject); if (typeof item.view[methodName] === 'function') { item.view[methodName](); break; } } } static #_2 = _initClass(); } }); define("helpers/site/modal-bar-provider", ["exports", "di"], function (_exports, _di) { "use strict"; Object.defineProperty(_exports, "__esModule", { value: true }); _exports.default = void 0; var _temp; let _initClass; /************************************************************************ * This file is part of EspoCRM. * * EspoCRM – Open Source CRM application. * Copyright (C) 2014-2025 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 _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 */ let _ModalBarProvider = _exports.default = void 0; class ModalBarProvider { static #_ = (_temp = [_ModalBarProvider, _initClass] = _applyDecs(this, [(0, _di.register)()], []).c, _exports.default = _ModalBarProvider, _temp); /** * @private * @type {import('views/collapsed-modal-bar').default|null} */ view = null; /** * @internal * @return {import('views/collapsed-modal-bar').default|null} */ get() { return this.view; } /** * @internal * @param {import('views/collapsed-modal-bar').default|null} view */ set(view) { this.view = view; } static #_2 = _initClass(); } }); define("utils", ["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-2025 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 utils */ const IS_MAC = /Mac/.test(navigator.userAgent); /** * Utility functions. */ Espo.Utils = { /** * Handle a click event action. * * @param {module:view} view A view. * @param {MouseEvent} event An event. * @param {HTMLElement} element An element. * @param {{ * action?: string, * handler?: string, * actionFunction?: string, * actionItems?: Array<{ * onClick?: function(), * name?: string, * handler?: string, * actionFunction?: string, * }>, * className?: string, * }} [actionData] Data. If an action is not specified, it will be fetched from a target element. * @return {boolean} True if handled. */ handleAction: function (view, event, element, actionData) { actionData = actionData || {}; const $target = $(element); const action = actionData.action || $target.data('action'); const name = $target.data('name') || action; let method; let handler; if (name && actionData.actionItems && (!actionData.className || element.classList.contains(actionData.className))) { const data = actionData.actionItems.find(item => { return item.name === name || item.action === name; }); if (data && data.onClick) { data.onClick(); return true; } if (data) { handler = data.handler; method = data.actionFunction; } } if (!action && !actionData.actionFunction && !method) { return false; } if (event.ctrlKey || event.metaKey || event.shiftKey) { const href = $target.attr('href'); if (href && href !== 'javascript:') { return false; } } const data = $target.data(); method = actionData.actionFunction || method || 'action' + Espo.Utils.upperCaseFirst(action); handler = actionData.handler || handler || data.handler; let fired = false; if (handler) { event.preventDefault(); event.stopPropagation(); fired = true; Espo.loader.require(handler, Handler => { const handler = new Handler(view); handler[method].call(handler, data, event); }); } else if (typeof view[method] === 'function') { if (view !== null && view !== void 0 && view.events[`click [data-action="${action}"]`]) { // Prevents from firing if a handler is already assigned. Important. return false; } view[method].call(view, data, event); event.preventDefault(); event.stopPropagation(); fired = true; } if (!fired) { return false; } this._processAfterActionDropdown($target); return true; }, /** * @private * @param {JQuery} $target */ _processAfterActionDropdown: function ($target) { const $dropdown = $target.closest('.dropdown-menu'); if (!$dropdown.length) { return; } const $dropdownToggle = $dropdown.parent().find('[data-toggle="dropdown"]'); if (!$dropdownToggle.length) { return; } let isDisabled = false; if ($dropdownToggle.attr('disabled')) { isDisabled = true; $dropdownToggle.removeAttr('disabled').removeClass('disabled'); } // noinspection JSUnresolvedReference $dropdownToggle.dropdown('toggle'); $dropdownToggle.focus(); if (isDisabled) { $dropdownToggle.attr('disabled', 'disabled').addClass('disabled'); } }, /** * @typedef {Object} Espo.Utils~ActionAvailabilityDefs * * @property {string|null} [configCheck] A config path to check. Path items are separated * by the dot. If a config value is not empty, then the action is allowed. * The `!` prefix reverses the check. */ /** * Check action availability. * * @param {module:view-helper} helper A view helper. * @param {Espo.Utils~ActionAvailabilityDefs} item Definitions. * @returns {boolean} */ checkActionAvailability: function (helper, item) { const config = helper.config; if (item.configCheck) { let configCheck = item.configCheck; let opposite = false; if (configCheck.substring(0, 1) === '!') { opposite = true; configCheck = configCheck.substring(1); } let configCheckResult = config.getByPath(configCheck.split('.')); if (opposite) { configCheckResult = !configCheckResult; } if (!configCheckResult) { return false; } } return true; }, /** * @typedef {Object} Espo.Utils~ActionAccessDefs * * @property {'create'|'read'|'edit'|'stream'|'delete'|null} acl An ACL action to check. * @property {string|null} [aclScope] A scope to check. * @property {string|null} [scope] Deprecated. Use `aclScope`. */ /** * Check access to an action. * * @param {module:acl-manager} acl An ACL manager. * @param {string|module:model|null} [obj] A scope or a model. * @param {Espo.Utils~ActionAccessDefs} item Definitions. * @param {boolean} [isPrecise=false] To return `null` if not enough data is set in a model. * E.g. the `teams` field is not yet loaded. * @returns {boolean|null} */ checkActionAccess: function (acl, obj, item, isPrecise) { let hasAccess = true; if (item.acl) { if (!item.aclScope) { if (obj) { if (typeof obj === 'string' || obj instanceof String) { hasAccess = acl.check(obj, item.acl); } else { hasAccess = acl.checkModel(obj, item.acl, isPrecise); } } else { hasAccess = acl.check(item.scope, item.acl); } } else { hasAccess = acl.check(item.aclScope, item.acl); } } else if (item.aclScope) { hasAccess = acl.checkScope(item.aclScope); } return hasAccess; }, /** * @typedef {Object} Espo.Utils~AccessDefs * * @property {'create'|'read'|'edit'|'stream'|'delete'|null} action An ACL action to check. * @property {string|null} [scope] A scope to check. * @property {string[]} [portalIdList] A portal ID list. To check whether a user in one of portals. * @property {string[]} [teamIdList] A team ID list. To check whether a user in one of teams. * @property {boolean} [isPortalOnly=false] Allow for portal users only. * @property {boolean} [inPortalDisabled=false] Disable for portal users. * @property {boolean} [isAdminOnly=false] Allow for admin users only. */ /** * Check access to an action. * * @param {module:utils~AccessDefs[]} dataList List of definitions. * @param {module:acl-manager} acl An ACL manager. * @param {module:models/user} user A user. * @param {module:model|null} [entity] A model. * @param {boolean} [allowAllForAdmin=false] Allow all for an admin. * @returns {boolean} */ checkAccessDataList: function (dataList, acl, user, entity, allowAllForAdmin) { if (!dataList || !dataList.length) { return true; } for (const i in dataList) { const item = dataList[i]; if (item.scope) { if (item.action) { if (!acl.check(item.scope, item.action)) { return false; } } else { if (!acl.checkScope(item.scope)) { return false; } } } else if (item.action) { if (entity) { if (!acl.check(entity, item.action)) { return false; } } } if (item.teamIdList) { if (user && !(allowAllForAdmin && user.isAdmin())) { let inTeam = false; user.getLinkMultipleIdList('teams').forEach(teamId => { if (~item.teamIdList.indexOf(teamId)) { inTeam = true; } }); if (!inTeam) { return false; } } } if (item.portalIdList) { if (user && !(allowAllForAdmin && user.isAdmin())) { let inPortal = false; user.getLinkMultipleIdList('portals').forEach(portalId => { if (~item.portalIdList.indexOf(portalId)) { inPortal = true; } }); if (!inPortal) { return false; } } } if (item.isPortalOnly) { if (user && !(allowAllForAdmin && user.isAdmin())) { if (!user.isPortal()) { return false; } } } else if (item.inPortalDisabled) { if (user && !(allowAllForAdmin && user.isAdmin())) { if (user.isPortal()) { return false; } } } if (item.isAdminOnly) { if (user) { if (!user.isAdmin()) { return false; } } } } return true; }, /** * @private * @param {string} string * @param {string} p * @returns {string} */ convert: function (string, p) { if (string === null) { return string; } let result = string; switch (p) { case 'c-h': case 'C-h': result = Espo.Utils.camelCaseToHyphen(string); break; case 'h-c': result = Espo.Utils.hyphenToCamelCase(string); break; case 'h-C': result = Espo.Utils.hyphenToUpperCamelCase(string); break; } return result; }, /** * Is object. * * @param {*} obj What to check. * @returns {boolean} */ isObject: function (obj) { if (obj === null) { return false; } return typeof obj === 'object'; }, /** * A shallow clone. * * @template {*} TObject * @param {TObject} obj An object. * @returns {TObject} */ clone: function (obj) { if (!Espo.Utils.isObject(obj)) { return obj; } return _.isArray(obj) ? obj.slice() : _.extend({}, obj); }, /** * A deep clone. * * @template {*} TObject * @param {TObject} data An object. * @returns {TObject} */ cloneDeep: function (data) { data = Espo.Utils.clone(data); if (Espo.Utils.isObject(data) || _.isArray(data)) { for (const i in data) { data[i] = this.cloneDeep(data[i]); } } return data; }, /** * Deep comparison. * * @param {Object} a1 An argument 1. * @param {Object} a2 An argument 2. * @return {boolean} */ areEqual: function (a1, a2) { return _.isEqual(a1, a2); }, /** * Compose a class name. * * @param {string} module A module. * @param {string} name A name. * @param {string} [location=''] A location. * @return {string} */ composeClassName: function (module, name, location) { if (module) { module = this.camelCaseToHyphen(module); name = this.camelCaseToHyphen(name).split('.').join('/'); location = this.camelCaseToHyphen(location || ''); return module + ':' + location + '/' + name; } else { name = this.camelCaseToHyphen(name).split('.').join('/'); return location + '/' + name; } }, /** * Compose a view class name. * * @param {string} name A name. * @returns {string} */ composeViewClassName: function (name) { if (name && name[0] === name[0].toLowerCase()) { return name; } if (name.indexOf(':') !== -1) { const arr = name.split(':'); let modPart = arr[0]; let namePart = arr[1]; modPart = this.camelCaseToHyphen(modPart); namePart = this.camelCaseToHyphen(namePart).split('.').join('/'); return modPart + ':' + 'views' + '/' + namePart; } else { name = this.camelCaseToHyphen(name).split('.').join('/'); return 'views' + '/' + name; } }, /** * Convert a string from camelCase to hyphen and replace dots with hyphens. * Useful for setting to DOM attributes. * * @param {string} string A string. * @returns {string} */ toDom: function (string) { return Espo.Utils.convert(string, 'c-h').split('.').join('-'); }, /** * Lower-case a first character. * * @param {string} string A string. * @returns {string} */ lowerCaseFirst: function (string) { if (string === null) { return string; } return string.charAt(0).toLowerCase() + string.slice(1); }, /** * Upper-case a first character. * * @param {string} string A string. * @returns {string} */ upperCaseFirst: function (string) { if (string === null) { return string; } return string.charAt(0).toUpperCase() + string.slice(1); }, /** * Hyphen to UpperCamelCase. * * @param {string} string A string. * @returns {string} */ hyphenToUpperCamelCase: function (string) { if (string === null) { return string; } return this.upperCaseFirst(string.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); })); }, /** * Hyphen to camelCase. * * @param {string} string A string. * @returns {string} */ hyphenToCamelCase: function (string) { if (string === null) { return string; } return string.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); }); }, /** * CamelCase to hyphen. * * @param {string} string A string. * @returns {string} */ camelCaseToHyphen: function (string) { if (string === null) { return string; } return string.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); }, /** * Trim an ending slash. * * @param {String} str A string. * @returns {string} */ trimSlash: function (str) { if (str.slice(-1) === '/') { return str.slice(0, -1); } return str; }, /** * Parse params in string URL options. * * @param {string} string An URL part. * @returns {Object.} */ parseUrlOptionsParam: function (string) { if (!string) { return {}; } if (string.indexOf('&') === -1 && string.indexOf('=') === -1) { return {}; } const options = {}; if (typeof string !== 'undefined') { string.split('&').forEach(item => { const p = item.split('='); options[p[0]] = true; if (p.length > 1) { options[p[0]] = p[1]; } }); } return options; }, /** * Key a key from a key-event. * * @param {JQueryKeyEventObject|KeyboardEvent} e A key event. * @return {string} */ getKeyFromKeyEvent: function (e) { let key = e.code; key = keyMap[key] || key; if (e.shiftKey) { key = 'Shift+' + key; } if (e.altKey) { key = 'Alt+' + key; } if (IS_MAC ? e.metaKey : e.ctrlKey) { key = 'Control+' + key; } return key; }, /** * Check whether the pressed key is in a text input. * * @param {KeyboardEvent} e A key event. * @return {boolean} * @since 9.2.0 */ isKeyEventInTextInput: function (e) { if (!(e.target instanceof HTMLElement)) { return false; } if (e.target.tagName === 'TEXTAREA') { return true; } if (e.target instanceof HTMLInputElement) { if (e.target.type === 'radio' || e.target.type === 'checkbox') { return false; } return true; } if (e.target.classList.contains('note-editable')) { return true; } return false; }, /** * Generate an ID. Not to be used by 3rd party code. * * @internal * @return {string} */ generateId: function () { return Math.floor(Math.random() * 10000001).toString(); }, /** * Not to be used in custom code. Can be removed in future versions. * @internal * @return {string} */ obtainBaseUrl: function () { let baseUrl = window.location.origin + window.location.pathname; if (baseUrl.slice(-1) !== '/') { baseUrl = window.location.pathname.includes('.') ? baseUrl.slice(0, baseUrl.lastIndexOf('/')) + '/' : baseUrl + '/'; } return baseUrl; } }; const keyMap = { 'NumpadEnter': 'Enter' }; /** * @deprecated Use `Espo.Utils`. */ Espo.utils = Espo.Utils; var _default = _exports.default = Espo.Utils; }); define("acl", ["exports", "bullbone"], function (_exports, _bullbone) { "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-2025 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 acl */ /** * Internal class for access checking. Can be extended to customize access checking * for a specific scope. */ class Acl { /** * @param {module:models/user} user A user. * @param {string} scope A scope. * @param {Object} params Parameters. * @param {import('acl-manager').default} aclManager */ constructor(user, scope, params, aclManager) { /** * A user. * * @type {module:models/user|null} * @protected */ this.user = user || null; this.scope = scope; params = params || {}; this.aclAllowDeleteCreated = params.aclAllowDeleteCreated; this.teamsFieldIsForbidden = params.teamsFieldIsForbidden; /** * @type {string[]} */ this.forbiddenFieldList = params.forbiddenFieldList || []; /** * @protected * @type {boolean} */ this.collaboratorsFieldIsForbidden = this.forbiddenFieldList.includes('collaborators'); /** * @type {import('acl-manager').default} * @private */ this._aclManager = aclManager; } /** * Get a user. * * @returns {module:models/user} * @protected */ getUser() { return this.user; } /** * Check access to a scope. * * @param {string|boolean|Object.} data Access data. * @param {module:acl-manager~action|null} [action=null] An action. * @param {boolean} [precise=false] To return `null` if `inTeam == null`. * @param {Record.|null} [entityAccessData=null] Entity access data. `inTeam`, `isOwner`. * @returns {boolean|null} True if access allowed. */ checkScope(data, action, precise, entityAccessData) { entityAccessData = entityAccessData || {}; const inTeam = entityAccessData.inTeam; const isOwner = entityAccessData.isOwner; const isShared = entityAccessData.isShared; if (this.getUser().isAdmin()) { if (data === false) { return false; } return true; } if (data === false) { return false; } if (data === true) { return true; } if (typeof data === 'string') { return true; } if (data === null) { return false; } action = action || null; if (action === null) { return true; } if (!(action in data)) { return false; } const value = data[action]; if (value === 'all') { return true; } if (value === 'yes') { return true; } if (value === 'no') { return false; } if (isOwner === undefined) { return true; } if (isOwner) { if (value === 'own' || value === 'team') { return true; } } if (isShared) { return true; } if (inTeam) { if (value === 'team') { return true; } } let result = false; if (value === 'team') { if (inTeam === null && precise) { result = null; } } if (isOwner === null && precise) { result = null; } if (isShared === null) { result = null; } return result; } /** * Check access to model (entity). * * @param {module:model} model A model. * @param {Object.|string|null} data Access data. * @param {module:acl-manager~action|null} [action=null] Action to check. * @param {boolean} [precise=false] To return `null` if not enough data is set in a model. * E.g. the `teams` field is not yet loaded. * @returns {boolean|null} True if access allowed, null if not enough data to determine. */ checkModel(model, data, action, precise) { if (this.getUser().isAdmin()) { return true; } let isShared = false; if (action === 'read' || action === 'stream') { isShared = this.checkIsShared(model); } const entityAccessData = { isOwner: this.checkIsOwner(model), inTeam: this.checkInTeam(model), isShared: isShared }; return this.checkScope(data, action, precise, entityAccessData); } // noinspection JSUnusedGlobalSymbols /** * Check `delete` access to model. * * @param {module:model} model A model. * @param {Object.|string|null} data Access data. * @param {boolean} [precise=false] To return `null` if not enough data is set in a model. * E.g. the `teams` field is not yet loaded. * @returns {boolean} True if access allowed. */ checkModelDelete(model, data, precise) { const result = this.checkModel(model, data, 'delete', precise); if (result) { return true; } if (data === false) { return false; } const d = data || {}; if (d.read === 'no') { return false; } if (model.has('createdById') && model.get('createdById') === this.getUser().id && this.aclAllowDeleteCreated) { if (!model.has('assignedUserId')) { return true; } if (!model.get('assignedUserId')) { return true; } if (model.get('assignedUserId') === this.getUser().id) { return true; } } return result; } /** * Check if a user is owner to a model. * * @param {module:model} model A model. * @returns {boolean|null} True if owner. Null if not clear. */ checkIsOwner(model) { let result = false; if (model.hasField('assignedUser')) { if (this.getUser().id === model.get('assignedUserId')) { return true; } if (!model.has('assignedUserId')) { result = null; } } else if (model.hasField('createdBy')) { if (this.getUser().id === model.get('createdById')) { return true; } if (!model.has('createdById')) { result = null; } } if (model.hasField('assignedUsers')) { if (!model.has('assignedUsersIds')) { return null; } if ((model.get('assignedUsersIds') || []).includes(this.getUser().id)) { return true; } result = false; } return result; } /** * Check if a user in a team of a model. * * @param {module:model} model A model. * @returns {boolean|null} True if in a team. Null if not enough data to determine. */ checkInTeam(model) { const userTeamIdList = this.getUser().getTeamIdList(); if (!model.has('teamsIds')) { if (this.teamsFieldIsForbidden) { return true; } if (!model.hasField('teams')) { return false; } return null; } const teamIdList = model.getTeamIdList(); let inTeam = false; userTeamIdList.forEach(id => { if (teamIdList.includes(id)) { inTeam = true; } }); return inTeam; } /** * Check if a record is shared with the user. * * @param {module:model} model A model. * @returns {boolean|null} True if shared. Null if not enough data to determine. */ checkIsShared(model) { if (!model.has('collaboratorsIds')) { if (this.collaboratorsFieldIsForbidden) { return true; } if (!model.hasField('collaborators')) { return false; } return null; } const collaboratorsIds = model.getLinkMultipleIdList('collaborators'); return collaboratorsIds.includes(this.user.id); } /** * Get a permission level. * * @protected * @param {string} permission A permission name. * @returns {'yes'|'all'|'team'|'no'} */ getPermissionLevel(permission) { return this._aclManager.getPermissionLevel(permission); } } Acl.extend = _bullbone.View.extend; var _default = _exports.default = Acl; }); define("views/modal", ["exports", "view", "di", "helpers/site/modal-bar-provider", "helpers/site/shortcut-manager"], function (_exports, _view, _di, _modalBarProvider, _shortcutManager) { "use strict"; Object.defineProperty(_exports, "__esModule", { value: true }); _exports.default = void 0; _view = _interopRequireDefault(_view); _modalBarProvider = _interopRequireDefault(_modalBarProvider); _shortcutManager = _interopRequireDefault(_shortcutManager); let _init_modalBarProvider, _init_extra_modalBarProvider, _init_shortcutManager, _init_extra_shortcutManager; /************************************************************************ * This file is part of EspoCRM. * * EspoCRM – Open Source CRM application. * Copyright (C) 2014-2025 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/modal */ 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 modal view. Can be extended or used directly. * * @see https://docs.espocrm.com/development/modal/ */ class ModalView extends _view.default { static #_ = [_init_modalBarProvider, _init_extra_modalBarProvider, _init_shortcutManager, _init_extra_shortcutManager] = _applyDecs(this, [], [[(0, _di.inject)(_modalBarProvider.default), 0, "modalBarProvider"], [(0, _di.inject)(_shortcutManager.default), 0, "shortcutManager"]], 0, void 0, _view.default).e; /** * A button or dropdown action item. * * @typedef {Object} module:views/modal~Button * * @property {string} name A name. * @property {string} [label] A label. To be translated * (with a scope defined in the `scope` class property). * @property {string} [text] A text (not translated). * @property {string} [labelTranslation] A label translation path. * @property {string} [html] HTML. * @property {boolean} [pullLeft=false] Deprecated. Use the `position` property. * @property {'left'|'right'} [position='left'] A position. * @property {'default'|'danger'|'success'|'warning'|'info'} [style='default'] A style. * @property {boolean} [hidden=false] Is hidden. * @property {boolean} [disabled=false] Disabled. * @property {function(module:ui.Dialog): void} [onClick] Called on click. If not defined, then * the `action` class method will be called. * @property {string} [className] An additional class name. * @property {string} [title] A title text. * @property {'primary'|'danger'|'success'|'warning'|'text'} [style] A style. * @property {string} [iconHtml] An icon HTML. * @property {string} [iconClass] An icon class. * @property {number} [groupIndex] A group index. Only for the dropdown. */ /** * @typedef {Object} module:views/modal~Options * @property {string} [headerText] A header text. * @property {HTMLElement} [headerElement] A header element. * @property {'static'|boolean} [backdrop] A backdrop. * @property {module:views/modal~Button} [buttonList] Buttons. * @property {module:views/modal~Button} [dropdownItemList] Buttons. * @property {boolean} [collapseDisabled] Not collapsible. As of v9.1.0. */ /** * @param {module:views/modal~Options | Record} [options] Options. */ constructor(options) { super(options), _init_extra_shortcutManager(this); } /** * A CSS name. * * @protected */ cssName = 'modal-dialog'; /** * A class-name. Use `'dialog dialog-record'` for modals containing a record form. * * @protected */ className = 'dialog'; /** * @protected * @deprecated Use `headerHtml` */ header; /** * A header HTML. Beware of XSS. * * @protected * @type {string|null} */ headerHtml; /** * A header JQuery instance. * * @protected * @type {JQuery} */ $header; /** * A header element. * * @protected * @type {Element} */ headerElement; /** * A header text. * * @protected * @type {string} */ headerText; /** * A dialog instance. * * @protected * @type {Espo.Ui.Dialog} */ dialog; /** * A container selector. * * @protected * @type {string} */ containerSelector = ''; /** * A scope name. Used when translating button labels. * * @type {string|null} */ scope = null; /** * A backdrop. * * @protected * @type {'static'|boolean} */ backdrop = 'static'; /** * Buttons. * * @protected * @type {module:views/modal~Button[]} */ buttonList = []; /** * Dropdown action items. * * @protected * @type {Array} */ dropdownItemList = []; /** * @deprecated Use `buttonList`. * @protected * @todo Remove. */ buttons = []; /** * A width. Do not use. * @todo Consider removing. * * @protected * @type {number|null} */ width = null; /** * Not used. * * @deprecated */ fitHeight = false; /** * To disable fitting to a window height. * * @protected * @type {boolean} */ noFullHeight = false; /** * Disable the ability to close by pressing the `Esc` key. * * @protected * @type {boolean} */ escapeDisabled = false; /** * Is draggable. * * @protected * @type {boolean} */ isDraggable = false; /** * Is collapsable. * * @protected * @type {boolean} */ isCollapsible = false; /** * Is maximizable. * * @protected * @type {boolean} * @since 9.1.0 */ isMaximizable = false; /** * Is collapsed. Do not change value. Only for reading. * * @protected * @type {boolean} */ isCollapsed = false; /** * @type {HTMLElement} * @protected */ bodyElement; /** * @inheritDoc */ events = { /** @this module:views/modal */ 'click .action': function (e) { Espo.Utils.handleAction(this, e.originalEvent, e.currentTarget); } }; /** * @protected * @type {boolean|null} */ footerAtTheTop = null; /** * A shortcut-key => action map. * * @protected * @type {?Object.} */ shortcutKeys = null; /** * @protected * @type {HTMLElement} * @since 9.0.0 */ containerElement; /** * @private * @type {ModalBarProvider} */ modalBarProvider = _init_modalBarProvider(this); /** * @private * @type {ShortcutManager} */ shortcutManager = (_init_extra_modalBarProvider(this), _init_shortcutManager(this)); /** * @inheritDoc */ init() { const id = this.cssName + '-container-' + Math.floor(Math.random() * 10000 + 1).toString(); this.containerSelector = '#' + id; this.header = this.options.header || this.header; this.headerHtml = this.options.headerHtml || this.headerHtml; this.$header = this.options.$header || this.$header; this.headerElement = this.options.headerElement || this.headerElement; this.headerText = this.options.headerText || this.headerText; this.backdrop = this.options.backdrop || this.backdrop; this.setSelector(this.containerSelector); this.buttonList = this.options.buttonList || this.buttonList; this.dropdownItemList = this.options.dropdownItemList || this.dropdownItemList; this.buttonList = Espo.Utils.cloneDeep(this.buttonList); this.dropdownItemList = Espo.Utils.cloneDeep(this.dropdownItemList); if (this.shortcutKeys) { this.shortcutKeys = Espo.Utils.cloneDeep(this.shortcutKeys); } if (this.options.collapseDisabled) { this.isCollapsible = false; } this.on('render', () => { if (this.dialog) { this.dialog.close(); } // Otherwise, re-render won't work. this.element = undefined; this.isCollapsed = false; $(this.containerSelector).remove(); $('
').css('display', 'none').attr('id', id).addClass('modal-container').appendTo('body'); let modalBodyDiffHeight = 92; if (this.getThemeManager().getParam('modalBodyDiffHeight') !== null) { modalBodyDiffHeight = this.getThemeManager().getParam('modalBodyDiffHeight'); } let headerHtml = this.headerHtml || this.header; if (this.$header && this.$header.length) { headerHtml = this.$header.get(0).outerHTML; } if (this.headerElement) { headerHtml = this.headerElement.outerHTML; } if (this.headerText) { headerHtml = Handlebars.Utils.escapeExpression(this.headerText); } const footerAtTheTop = this.footerAtTheTop !== null ? this.footerAtTheTop : this.getThemeManager().getParam('modalFooterAtTheTop'); this.dialog = new Espo.Ui.Dialog({ backdrop: this.backdrop, header: headerHtml, container: this.containerSelector, body: '', buttonList: this.getDialogButtonList(), dropdownItemList: this.getDialogDropdownItemList(), width: this.width, keyboard: !this.escapeDisabled, fitHeight: this.fitHeight, draggable: this.isDraggable, className: this.className, bodyDiffHeight: modalBodyDiffHeight, footerAtTheTop: footerAtTheTop, fullHeight: !this.noFullHeight && this.getThemeManager().getParam('modalFullHeight'), screenWidthXs: this.getThemeManager().getParam('screenWidthXs'), fixedHeaderHeight: this.fixedHeaderHeight, closeButton: !this.noCloseButton, collapseButton: this.isCollapsible, maximizeButton: this.isMaximizable && !this.getHelper().isXsScreen(), onRemove: () => this.onDialogClose(), onBackdropClick: () => this.onBackdropClick(), onMaximize: () => this.onMaximize(), onMinimize: () => this.onMinimize() }); this.containerElement = document.querySelector(this.containerSelector); this.setElement(this.containerSelector + ' .body'); this.bodyElement = this.element; // @todo Review that the element is set back to the container afterwards. // Force keeping set to the body? }); this.on('after:render', () => { // Trick to delegate events for the whole modal. this.element = undefined; this.setElement(this.containerSelector); $(this.containerSelector).show(); this.dialog.show(); if (this.fixedHeaderHeight && this.flexibleHeaderFontSize) { this.adjustHeaderFontSize(); } this.adjustButtons(); if (!this.noFullHeight) { this.initBodyScrollListener(); } if (this.getParentView()) { this.getParentView().trigger('modal-shown'); } this.initShortcuts(); }); this.once('remove', () => { if (this.dialog) { this.dialog.close(); } $(this.containerSelector).remove(); }); if (this.isCollapsible) { this.addActionHandler('collapseModal', () => this.collapse()); } this.on('after:expand', () => this.afterExpand()); } /** * @private */ initShortcuts() { // Shortcuts to be added even if there's no keys set – to suppress current shortcuts. this.shortcutManager.add(this, this.shortcutKeys ?? {}, { stack: true }); this.once('remove', () => { this.shortcutManager.remove(this); }); } setupFinal() { this.initShortcuts(); } /** * Get a button list for a dialog. * * @private * @return {module:ui.Dialog~Button[]} */ getDialogButtonList() { const buttonListExt = []; // @todo remove it as deprecated. this.buttons.forEach(item => { const o = Espo.Utils.clone(item); if (!('text' in o) && 'label' in o) { o.text = this.getLanguage().translate(o.label); } buttonListExt.push(o); }); this.buttonList.forEach(item => { let o = {}; if (typeof item === 'string') { o.name = /** @type string */item; } else if (typeof item === 'object') { o = item; } else { return; } if (!o.text) { if (o.labelTranslation) { o.text = this.getLanguage().translatePath(o.labelTranslation); } else if ('label' in o) { o.text = this.translate(o.label, 'labels', this.scope); } else { o.text = this.translate(o.name, 'modalActions', this.scope); } } if (o.iconHtml && !o.html) { o.html = o.iconHtml + '' + this.getHelper().escapeString(o.text) + ''; } else if (o.iconClass && !o.html) { o.html = `` + '' + this.getHelper().escapeString(o.text) + ''; } o.onClick = o.onClick || ((d, e) => { const handler = o.handler || (o.data || {}).handler; Espo.Utils.handleAction(this, e.originalEvent, e.currentTarget, { action: o.name, handler: handler, actionFunction: o.actionFunction }); }); buttonListExt.push(o); }); return buttonListExt; } /** * Get a dropdown item list for a dialog. * * @private * @return {Array} */ getDialogDropdownItemList() { const dropdownItemListExt = []; this.dropdownItemList.forEach(item => { let o = {}; if (typeof item === 'string') { o.name = /** @type string */item; } else if (typeof item === 'object') { o = item; } else { return; } if (!o.text) { if (o.labelTranslation) { o.text = this.getLanguage().translatePath(o.labelTranslation); } else if ('label' in o) { o.text = this.translate(o.label, 'labels', this.scope); } else { o.text = this.translate(o.name, 'modalActions', this.scope); } } o.onClick = o.onClick || ((d, e) => { // noinspection ES6ConvertLetToConst let handler = o.handler || (o.data || {}).handler; Espo.Utils.handleAction(this, e.originalEvent, e.currentTarget, { action: o.name, handler: handler, actionFunction: o.actionFunction }); }); dropdownItemListExt.push(o); }); /** @type {Array} */ const dropdownGroups = []; dropdownItemListExt.forEach(item => { // For bc. if (item === false) { return; } const index = (item.groupIndex === undefined ? 9999 : item.groupIndex) + 100; if (dropdownGroups[index] === undefined) { dropdownGroups[index] = []; } dropdownGroups[index].push(item); }); const dropdownItemList = []; dropdownGroups.forEach(list => { list.forEach(it => dropdownItemList.push(it)); dropdownItemList.push(false); }); return dropdownItemList; } /** @private */ updateDialog() { if (!this.dialog) { return; } this.dialog.setActionItems(this.getDialogButtonList(), this.getDialogDropdownItemList()); } /** @private */ onDialogClose() { if (!this.isBeingRendered() && !this.isCollapsed) { this.trigger('close'); this.remove(); } this.shortcutManager.remove(this); } /** * @protected */ onBackdropClick() {} /** * A `cancel` action. */ actionCancel() { this.trigger('cancel'); this.close(); } /** * A `close` action. */ actionClose() { this.actionCancel(); } /** * Close a dialog. */ close() { this.dialog.close(); if (!this.getParentView()) { return; } const key = this.getParentView().getViewKey(this); if (key) { this.getParentView().clearView(key); } } /** * Disable a button. * * @param {string} name A button name. */ disableButton(name) { this.buttonList.forEach(d => { if (d.name !== name) { return; } d.disabled = true; }); if (!this.isRendered()) { return; } if (!this.containerElement) { return; } $(this.containerElement).find(`footer button[data-name="${name}"]`).addClass('disabled').attr('disabled', 'disabled'); } /** * Enable a button. * * @param {string} name A button name. */ enableButton(name) { this.buttonList.forEach(d => { if (d.name !== name) { return; } d.disabled = false; }); if (!this.isRendered()) { return; } if (!this.containerElement) { return; } $(this.containerElement).find('footer button[data-name="' + name + '"]').removeClass('disabled').removeAttr('disabled'); } /** * Add a button. * * @param {module:views/modal~Button} o Button definitions. * @param {boolean|string} [position=false] True prepends, false appends. If a string * then will be added after a button with a corresponding name. * @param {boolean} [doNotReRender=false] Do not re-render. */ addButton(o, position, doNotReRender) { let index = -1; this.buttonList.forEach((item, i) => { if (item.name === o.name) { index = i; } }); if (~index) { return; } if (position === true) { this.buttonList.unshift(o); } else if (typeof position === 'string') { index = -1; this.buttonList.forEach((item, i) => { if (item.name === position) { index = i; } }); if (~index) { this.buttonList.splice(index, 0, o); } else { this.buttonList.push(o); } } else { this.buttonList.push(o); } if (!doNotReRender && this.isRendered()) { this.reRenderFooter(); } } /** * Add a dropdown item. * * @param {module:views/modal~Button} o Button definitions. * @param {boolean} [toBeginning=false] To prepend. * @param {boolean} [doNotReRender=false] Do not re-render. */ addDropdownItem(o, toBeginning, doNotReRender) { if (!o) { // For bc. return; } const name = o.name; if (!name) { return; } for (const item of this.dropdownItemList) { if (item.name === name) { return; } } toBeginning ? this.dropdownItemList.unshift(o) : this.dropdownItemList.push(o); if (!doNotReRender && this.isRendered()) { this.reRenderFooter(); } } /** @private */ reRenderFooter() { if (!this.dialog) { return; } this.updateDialog(); const $footer = this.dialog.getFooter(); $(this.containerElement).find('footer.modal-footer').empty().append($footer); this.dialog.initButtonEvents(); } /** * Remove a button or a dropdown action item. * * @param {string} name A name. * @param {boolean} [doNotReRender=false] Do not re-render. */ removeButton(name, doNotReRender) { let index = -1; for (const [i, item] of this.buttonList.entries()) { if (item.name === name) { index = i; break; } } if (~index) { this.buttonList.splice(index, 1); } for (const [i, item] of this.dropdownItemList.entries()) { if (item.name === name) { this.dropdownItemList.splice(i, 1); break; } } if (this.isRendered()) { $(this.containerElement).find(`.modal-footer [data-name="${name}"]`).remove(); } if (!doNotReRender && this.isRendered()) { this.reRender(); } } /** * @deprecated Use `showActionItem`. * * @protected * @param {string} name */ showButton(name) { for (const item of this.buttonList) { if (item.name === name) { item.hidden = false; break; } } if (!this.isRendered()) { return; } if (!this.containerElement) { return; } $(this.containerElement).find(`footer button[data-name="${name}"]`).removeClass('hidden'); this.adjustButtons(); } /** * @deprecated Use `hideActionItem`. * * @protected * @param {string} name */ hideButton(name) { for (const item of this.buttonList) { if (item.name === name) { item.hidden = true; break; } } if (!this.isRendered()) { return; } if (!this.containerElement) { return; } $(this.containerElement).find(`footer button[data-name="${name}"]`).addClass('hidden'); this.adjustButtons(); } /** * Show an action item (button or dropdown item). * * @param {string} name A name. */ showActionItem(name) { for (const item of this.buttonList) { if (item.name === name) { item.hidden = false; break; } } for (const item of this.dropdownItemList) { if (item.name === name) { item.hidden = false; break; } } if (!this.isRendered()) { return; } if (!this.containerElement) { return; } const $el = $(this.containerElement); $el.find(`footer button[data-name="${name}"]`).removeClass('hidden'); $el.find(`footer li > a[data-name="${name}"]`).parent().removeClass('hidden'); if (!this.isDropdownItemListEmpty()) { const $dropdownGroup = $el.find('footer .main-btn-group > .btn-group'); $dropdownGroup.removeClass('hidden'); $dropdownGroup.find('> button').removeClass('hidden'); } this.adjustButtons(); } /** * Hide an action item (button or dropdown item). * * @param {string} name A name. */ hideActionItem(name) { for (const item of this.buttonList) { if (item.name === name) { item.hidden = true; break; } } for (const item of this.dropdownItemList) { if (item.name === name) { item.hidden = true; break; } } if (!this.isRendered()) { return; } const $el = $(this.containerElement); $el.find(`footer button[data-name="${name}"]`).addClass('hidden'); $el.find(`footer li > a[data-name="${name}"]`).parent().addClass('hidden'); if (this.isDropdownItemListEmpty()) { const $dropdownGroup = $el.find('footer .main-btn-group > .btn-group'); $dropdownGroup.addClass('hidden'); $dropdownGroup.find('> button').addClass('hidden'); } this.adjustButtons(); } /** * Whether an action item exists (hidden, disabled or not). * * @param {string} name An action item name. */ hasActionItem(name) { const hasButton = this.buttonList.findIndex(item => item.name === name) !== -1; if (hasButton) { return true; } return this.dropdownItemList.findIndex(item => item.name === name) !== -1; } /** * Whether an action item is visible and not disabled. * * @param {string} name An action item name. */ hasAvailableActionItem(name) { const hasButton = this.buttonList.findIndex(item => item.name === name && !item.disabled && !item.hidden) !== -1; if (hasButton) { return true; } return this.dropdownItemList.findIndex(item => item.name === name && !item.disabled && !item.hidden) !== -1; } /** * @private * @return {boolean} */ isDropdownItemListEmpty() { if (this.dropdownItemList.length === 0) { return true; } let isEmpty = true; this.dropdownItemList.forEach(item => { if (!item.hidden) { isEmpty = false; } }); return isEmpty; } /** * @private * @param {number} [step=0] */ adjustHeaderFontSize(step) { step = step || 0; if (!step) { this.fontSizePercentage = 100; } if (!this.containerElement) { return; } const $titleText = $(this.containerElement).find('.modal-title > .modal-title-text'); const containerWidth = $titleText.parent().width(); let textWidth = 0; $titleText.children().each((i, el) => { textWidth += $(el).outerWidth(true); }); if (containerWidth < textWidth) { if (step > 5) { const $title = $(this.containerElement).find('.modal-title'); $title.attr('title', $titleText.text()); $title.addClass('overlapped'); $titleText.children().each((i, el) => { $(el).removeAttr('title'); }); return; } this.fontSizePercentage -= 4; $(this.containerElement).find('.modal-title .font-size-flexible').css('font-size', this.fontSizePercentage + '%'); this.adjustHeaderFontSize(step + 1); } } /** * Collapse. */ async collapse() { let data = await this.beforeCollapse(); if (!this.getParentView()) { throw new Error("Can't collapse w/o parent view."); } this.isCollapsed = true; data = data || {}; let title; if (data.title) { title = data.title; } else { const titleElement = this.containerElement.querySelector('.modal-header .modal-title .modal-title-text'); if (titleElement) { title = titleElement.textContent; } } this.dialog.close(); let masterView = this; while (masterView.getParentView()) { masterView = masterView.getParentView(); } this.unchainFromParent(); const barView = this.modalBarProvider.get(); if (!barView) { return; } await barView.addModalView(this, { title: title }); } unchainFromParent() { const key = this.getParentView().getViewKey(this); this.getParentView().unchainView(key); } /** * Called before collapse. Can be extended to execute some logic, e.g. save form data. * * @protected * @return {Promise} */ beforeCollapse() { return new Promise(resolve => resolve()); } /** * Called after expanding. * * @protected * @since 9.1.0 */ afterExpand() {} /** @private */ adjustButtons() { this.adjustLeftButtons(); this.adjustRightButtons(); } /** @private */ adjustLeftButtons() { const $buttons = $(this.containerElement).find('footer.modal-footer > .main-btn-group button.btn'); $buttons.removeClass('radius-left').removeClass('radius-right'); const $buttonsVisible = $buttons.filter('button:not(.hidden)'); $buttonsVisible.first().addClass('radius-left'); $buttonsVisible.last().addClass('radius-right'); } /** @private */ adjustRightButtons() { const $buttons = $(this.containerElement).find('footer.modal-footer > .additional-btn-group button.btn:not(.btn-text)'); $buttons.removeClass('radius-left').removeClass('radius-right').removeClass('margin-right'); const $buttonsVisible = $buttons.filter('button:not(.hidden)'); $buttonsVisible.first().addClass('radius-left'); $buttonsVisible.last().addClass('radius-right'); if ($buttonsVisible.last().next().hasClass('btn-text')) { $buttonsVisible.last().addClass('margin-right'); } } /** * @private */ initBodyScrollListener() { const $body = $(this.containerElement).find('> .dialog > .modal-dialog > .modal-content > .modal-body'); const $footer = $body.parent().find('> .modal-footer'); if (!$footer.length) { return; } $body.off('scroll.footer-shadow'); $body.on('scroll.footer-shadow', () => { if ($body.scrollTop()) { $footer.addClass('shadowed'); return; } $footer.removeClass('shadowed'); }); } /** * @protected * @since 9.1.0 */ onMaximize() {} /** * @protected * @since 9.1.0 */ onMinimize() {} } var _default = _exports.default = ModalView; }); define("helpers/model/default-value-provider", ["exports", "date-time", "di"], function (_exports, _dateTime, _di) { "use strict"; Object.defineProperty(_exports, "__esModule", { value: true }); _exports.default = void 0; _dateTime = _interopRequireDefault(_dateTime); let _init_dateTime, _init_extra_dateTime; /************************************************************************ * This file is part of EspoCRM. * * EspoCRM – Open Source CRM application. * Copyright (C) 2014-2025 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; } const nowExpression = /return this\.dateTime\.getNow\(([0-9]+)\);/; const shiftTodayExpression = /return this\.dateTime\.getDateShiftedFromToday\(([0-9]+), '([a-z]+)'\);/; const shiftNowExpression = /return this\.dateTime\.getDateTimeShiftedFromNow\(([0-9]+), '([a-z]+)', ([0-9]+)\);/; class DefaultValueProvider { static #_ = [_init_dateTime, _init_extra_dateTime] = _applyDecs(this, [], [[(0, _di.inject)(_dateTime.default), 0, "dateTime"]]).e; constructor() { _init_extra_dateTime(this); } /** * @type {DateTime} */ dateTime = _init_dateTime(this); /** * Get a value. * * @param {string} key * @return {*} */ get(key) { if (key === "return this.dateTime.getToday();") { return this.dateTime.getToday(); } const matchNow = key.match(nowExpression); if (matchNow) { const multiplicity = parseInt(matchNow[1]); return this.dateTime.getNow(multiplicity); } const matchTodayShift = key.match(shiftTodayExpression); if (matchTodayShift) { const shift = parseInt(matchTodayShift[1]); const unit = matchTodayShift[2]; return this.dateTime.getDateShiftedFromToday(shift, unit); } const matchNowShift = key.match(shiftNowExpression); if (matchNowShift) { const shift = parseInt(matchNowShift[1]); const unit = matchNowShift[2]; const multiplicity = parseInt(matchNowShift[3]); return this.dateTime.getDateTimeShiftedFromNow(shift, unit, multiplicity); } return undefined; } } _exports.default = DefaultValueProvider; }); define("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-2025 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 storage */ /** * A storage. Data is saved across browser sessions, has no expiration time. */ class Storage { constructor() {} /** @protected */ prefix = 'espo'; /** @protected */ storageObject = localStorage; /** * @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]' || type === 'cache') { throw new TypeError("Bad type \"" + type + "\" passed to Espo.Storage."); } } /** * Has a value. * * @param {string} type A type (category). * @param {string} name A name. * @returns {boolean} */ has(type, name) { this.checkType(type); const key = this.composeKey(type, name); return this.storageObject.getItem(key) !== null; } /** * Get a value. * * @param {string} type A type (category). * @param {string} name A name. * @returns {*} Null if not stored. */ get(type, name) { this.checkType(type); const key = this.composeKey(type, name); let stored; try { stored = this.storageObject.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; } } else if (stored[0] === "{" || stored[0] === "[") { // for backward compatibility try { result = JSON.parse(stored); } catch (error) { result = stored; } } return result; } return null; } /** * Set (store) a value. * * @param {string} type A type (category). * @param {string} name A name. * @param {*} value A value. */ set(type, name, value) { this.checkType(type); if (value === null) { this.clear(type, name); return; } const key = this.composeKey(type, name); if (value instanceof Object || Array.isArray(value) || value === true || value === false || typeof value === 'number') { value = '__JSON__:' + JSON.stringify(value); } try { this.storageObject.setItem(key, value); } catch (error) { console.error(error); return null; } } /** * Clear a 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.prefix + '-'; } const re = new RegExp(reText); for (const i in this.storageObject) { if (re.test(i)) { delete this.storageObject[i]; } } } } var _default = _exports.default = Storage; }); define("router", ["exports", "backbone"], function (_exports, _backbone) { "use strict"; Object.defineProperty(_exports, "__esModule", { value: true }); _exports.default = void 0; _backbone = _interopRequireDefault(_backbone); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } /************************************************************************ * This file is part of EspoCRM. * * EspoCRM – Open Source CRM application. * Copyright (C) 2014-2025 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 router */ /** * On route. * * @event Backbone.Router#route * @param {string} name A route name. * @param {any[]} args Arguments. */ /** * After dispatch. * * @event module:router#routed * @param {{ * controller: string, * action:string, * options: Object., * }} data A route data. */ /** * Subscribe. * * @function on * @memberof module:router# * @param {string} event An event. * @param {function(*): void} callback A callback. */ /** * Subscribe once. * * @function once * @memberof module:router# * @param {string} event An event. * @param {function(): void} callback A callback. */ /** * Unsubscribe. * * @function off * @memberof module:router# * @param {string} event An event. */ /** * Trigger an event. * * @function trigger * @memberof module:router# * @param {string} event An event. */ // noinspection JSUnusedGlobalSymbols /** * A router. * * @class * @mixes Espo.Events */ const Router = _backbone.default.Router.extend(/** @lends Router# */{ /** * @private */ routeList: [{ route: "clearCache", resolution: "clearCache" }, { route: ":controller/view/:id/:options", resolution: "view" }, { route: ":controller/view/:id", resolution: "view" }, { route: ":controller/edit/:id/:options", resolution: "edit" }, { route: ":controller/edit/:id", resolution: "edit" }, { route: ":controller/create", resolution: "create" }, { route: ":controller/related/:id/:link", resolution: "related" }, { route: ":controller/:action/:options", resolution: "action", order: 100 }, { route: ":controller/:action", resolution: "action", order: 200 }, { route: ":controller", resolution: "defaultAction", order: 300 }, { route: "*actions", resolution: "home", order: 500 }], /** * @private */ _bindRoutes: function () {}, /** * @private */ setupRoutes: function () { this.routeParams = {}; if (this.options.routes) { const routeList = []; Object.keys(this.options.routes).forEach(route => { const item = this.options.routes[route]; routeList.push({ route: route, resolution: item.resolution || 'defaultRoute', order: item.order || 0 }); this.routeParams[route] = item.params || {}; }); this.routeList = Espo.Utils.clone(this.routeList); routeList.forEach(item => { this.routeList.push(item); }); this.routeList = this.routeList.sort((v1, v2) => { return (v1.order || 0) - (v2.order || 0); }); } this.routeList.reverse().forEach(item => { this.route(item.route, item.resolution); }); }, /** * @private */ _last: null, /** * Whether a confirm-leave-out was set. * * @public * @type {boolean} */ confirmLeaveOut: false, /** * Whether back has been processed. * * @public * @type {boolean} */ backProcessed: false, /** * @type {string} * @internal */ confirmLeaveOutMessage: 'Are you sure?', /** * @type {string} * @internal */ confirmLeaveOutConfirmText: 'Yes', /** * @type {string} * @internal */ confirmLeaveOutCancelText: 'No', /** * @private */ initialize: function (options) { this.options = options || {}; this.setupRoutes(); this._isReturn = false; this.history = []; let hashHistory = [window.location.hash]; window.addEventListener('hashchange', () => { const hash = window.location.hash; if (hashHistory.length > 1 && hashHistory[hashHistory.length - 2] === hash) { hashHistory = hashHistory.slice(0, -1); this.backProcessed = true; setTimeout(() => this.backProcessed = false, 50); return; } hashHistory.push(hash); }); this.on('route', () => { this.history.push(_backbone.default.history.fragment); }); window.addEventListener('beforeunload', event => { event = event || window.event; if (this.confirmLeaveOut || this._leaveOutMap.size || this._windowLeaveOutMap.size) { event.preventDefault(); event.returnValue = this.confirmLeaveOutMessage; return this.confirmLeaveOutMessage; } }); /** * @private * @type {Map} */ this._leaveOutMap = new Map(); /** * @private * @type {Map} */ this._windowLeaveOutMap = new Map(); }, /** * Get a current URL. * * @returns {string} */ getCurrentUrl: function () { return '#' + _backbone.default.history.fragment; }, /** * Whether there's any confirm-leave-out. * * @since 9.1.0 * @return {boolean} */ hasConfirmLeaveOut() { return this.confirmLeaveOut || this._leaveOutMap.size || this._windowLeaveOutMap.size; }, /** * Refer an object (usually a view). Page won't be possible to close or change if there's at least one object. * * @param {Object} object * @since 9.1.0 * @internal */ addLeaveOutObject(object) { this._leaveOutMap.set(object, true); }, /** * Un-refer an object. * * @param {Object} object * @since 9.1.0 * @internal */ removeLeaveOutObject(object) { this._leaveOutMap.delete(object); }, /** * Refer an object (usually a view). Window won't be possible to close if there's at least one object. * * @param {Object} object * @since 9.1.0 * @internal */ addWindowLeaveOutObject(object) { this._windowLeaveOutMap.set(object, true); }, /** * Un-refer an object. * * @param {Object} object * @since 9.1.0 * @internal */ removeWindowLeaveOutObject(object) { this._windowLeaveOutMap.delete(object); }, /** * @callback module:router~checkConfirmLeaveOutCallback */ /** * Process confirm-leave-out. * * @param {module:router~checkConfirmLeaveOutCallback} callback Proceed if confirmed. * @param {Object|null} [context] A context. * @param {boolean} [navigateBack] To navigate back if not confirmed. */ checkConfirmLeaveOut: function (callback, context, navigateBack) { if (this.confirmLeaveOutDisplayed) { this.navigateBack({ trigger: false }); this.confirmLeaveOutCanceled = true; return; } context = context || this; if (this.confirmLeaveOut || this._leaveOutMap.size) { this.confirmLeaveOutDisplayed = true; this.confirmLeaveOutCanceled = false; Espo.Ui.confirm(this.confirmLeaveOutMessage, { confirmText: this.confirmLeaveOutConfirmText, cancelText: this.confirmLeaveOutCancelText, backdrop: true, cancelCallback: () => { this.confirmLeaveOutDisplayed = false; if (navigateBack) { this.navigateBack({ trigger: false }); } } }, () => { this.confirmLeaveOutDisplayed = false; this.confirmLeaveOut = false; this._leaveOutMap.clear(); if (!this.confirmLeaveOutCanceled) { callback.call(context); } }); return; } callback.call(context); }, /** * @private */ route: function (route, name /*, callback*/) { const routeOriginal = route; if (!_.isRegExp(route)) { route = this._routeToRegExp(route); } let callback; // @todo Revise. /*if (_.isFunction(name)) { callback = name; name = ''; }*/ /*if (!callback) { callback = this['_' + name]; }*/ callback = this['_' + name]; const router = this; _backbone.default.history.route(route, function (fragment) { const args = router._extractParameters(route, fragment); const options = {}; if (name === 'defaultRoute') { const keyList = []; routeOriginal.split('/').forEach(key => { if (key && key.indexOf(':') === 0) { keyList.push(key.substr(1)); } }); keyList.forEach((key, i) => { options[key] = args[i]; }); } // @todo Revise. router.execute(callback, args, name, routeOriginal, options); //if (router.execute(callback, args, name, routeOriginal, options) !== false) { router.trigger.apply(router, ['route:' + name].concat(args)); router.trigger('route', name, args); _backbone.default.history.trigger('route', router, name, args); //} }); return this; }, /** * @private */ execute: function (callback, args, name, routeOriginal, options) { this.checkConfirmLeaveOut(() => { if (name === 'defaultRoute') { this._defaultRoute(this.routeParams[routeOriginal], options); return; } _backbone.default.Router.prototype.execute.call(this, callback, args, name); }, null, true); }, /** * Navigate. * * @param {string} fragment An URL fragment. * @param {{ * trigger?: boolean, * replace?: boolean, * isReturn?: boolean, * }} [options] Options. */ navigate: function (fragment) { let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; if (!options.trigger) { this.history.push(fragment); } if (options.isReturn) { this._isReturn = true; } return _backbone.default.Router.prototype.navigate.call(this, fragment, options); }, /** * Navigate back. * * @param {Object} [options] Options: trigger, replace. */ navigateBack: function (options) { let url; url = this.history.length > 1 ? this.history[this.history.length - 2] : this.history[0]; this.navigate(url, options); }, /** * @private */ _parseOptionsParams: function (string) { if (!string) { return {}; } if (string.indexOf('&') === -1 && string.indexOf('=') === -1) { return string; } const options = {}; if (typeof string !== 'undefined') { string.split('&').forEach(item => { const p = item.split('='); options[p[0]] = true; if (p.length > 1) { options[p[0]] = decodeURIComponent(p[1]); } }); } return options; }, /** * @private */ _defaultRoute: function (params, options) { const controller = params.controller || options.controller; const action = params.action || options.action; this.dispatch(controller, action, options); }, /** * @private */ _record: function (controller, action, id, options) { options = this._parseOptionsParams(options); options.id = id; this.dispatch(controller, action, options); }, /** * @private */ _view: function (controller, id, options) { this._record(controller, 'view', id, options); }, /** * @private */ _edit: function (controller, id, options) { this._record(controller, 'edit', id, options); }, /** * @private */ _related: function (controller, id, link, options) { options = this._parseOptionsParams(options); options.id = id; options.link = link; this.dispatch(controller, 'related', options); }, /** * @private */ _create: function (controller, options) { this._record(controller, 'create', null, options); }, /** * @private */ _action: function (controller, action, options) { this.dispatch(controller, action, this._parseOptionsParams(options)); }, /** * @private */ _defaultAction: function (controller) { this.dispatch(controller, null); }, /** * @private */ _home: function () { this.dispatch('Home', null); }, /** * @private */ _clearCache: function () { this.dispatch(null, 'clearCache'); }, /** * Process `logout` route. */ logout: function () { this.dispatch(null, 'logout'); this.navigate('', { trigger: false }); }, /** * Dispatch a controller action. * * @param {string|null} [controller] A controller. * @param {string|null} [action] An action. * @param {Object} [options] Options. * @fires module:router#routed */ dispatch: function (controller, action, options) { if (this._isReturn) { options = { ...options }; options.isReturn = true; this._isReturn = false; } const o = { controller: controller, action: action, options: options }; if (controller && /[a-z]/.test(controller[0])) { o.controllerClassName = controller; delete o.controller; } this._last = o; this.trigger('routed', o); }, /** * Get the last route data. * * @returns {Object} */ getLast: function () { return this._last; } }); var _default = _exports.default = Router; function isIOS9UIWebView() { const userAgent = window.navigator.userAgent; return /(iPhone|iPad|iPod).* OS 9_\d/.test(userAgent) && !/Version\/9\./.test(userAgent); } // Fixes issue that navigate with {trigger: false} fired // route change if there's a whitespace character. _backbone.default.history.getHash = function (window) { const match = (window || this).location.href.match(/#(.*)$/); return match ? this.decodeFragment(match[1]) : ''; }; // Override `backbone.history.loadUrl()` and `backbone.history.navigate()` // to fix the navigation issue (`location.hash` not changed immediately) on iOS9. if (isIOS9UIWebView()) { _backbone.default.history.loadUrl = function (fragment, oldHash) { fragment = this.fragment = this.getFragment(fragment); return _.any(this.handlers, function (handler) { if (handler.route.test(fragment)) { function runCallback() { handler.callback(fragment); } function wait() { if (oldHash === location.hash) { window.setTimeout(wait, 50); } else { runCallback(); } } wait(); return true; } }); }; _backbone.default.history.navigate = function (fragment, options) { const pathStripper = /#.*$/; if (!_backbone.default.History.started) { return false; } if (!options || options === true) { options = { trigger: !!options }; } let url = this.root + '#' + (fragment = this.getFragment(fragment || '')); fragment = fragment.replace(pathStripper, ''); if (this.fragment === fragment) { return; } this.fragment = fragment; if (fragment === '' && url !== '/') { url = url.slice(0, -1); } const oldHash = location.hash; if (this._hasPushState) { this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url); } else if (this._wantsHashChange) { this._updateHash(this.location, fragment, options.replace); if (this.iframe && fragment !== this.getFragment(this.getHash(this.iframe))) { if (!options.replace) { this.iframe.document.open().close(); } this._updateHash(this.iframe.location, fragment, options.replace); } } else { return this.location.assign(url); } if (options.trigger) { return this.loadUrl(fragment, oldHash); } }; } }); define("model", ["exports", "bullbone", "underscore", "helpers/model/default-value-provider"], function (_exports, _bullbone, _underscore, _defaultValueProvider) { "use strict"; Object.defineProperty(_exports, "__esModule", { value: true }); _exports.default = void 0; _underscore = _interopRequireDefault(_underscore); _defaultValueProvider = _interopRequireDefault(_defaultValueProvider); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } /************************************************************************ * This file is part of EspoCRM. * * EspoCRM – Open Source CRM application. * Copyright (C) 2014-2025 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 model */ /** * When attributes have changed. * * @event Model#change * @param {Model} model A model. * @param {Record. & {action?: string|'ui'|'save'|'fetch'|'cancel-edit'}} o Options. */ /** * On sync with backend. * * @event Model#sync * @param {Model} model A model. * @param {Object} response Response from backend. * @param {Record. & {action?: 'fetch'|'save'|'destroy'}} o Options. */ /** * Definitions. * * @typedef module:model~defs * @type {Object} * @property {Object.} [fields] Fields. * @property {Object.>} [links] Links. */ /** * Field definitions. * * @typedef module:model~fieldDefs * @type {Object} * @property {string} type A type. */ /** @typedef {import('bullbone')} Bull */ /** * A model. * * @mixes Bull.Events */ class Model { /** * A root URL. An ID will be appended. Used for syncing with backend. * * @type {string|null} */ urlRoot = null; /** * A URL. If not empty, then will be used for syncing instead of `urlRoot`. * * @type {string|null} */ url = null; /** * A name. * * @type {string|null} */ name = null; /** * An entity type. * * @type {string|null} */ entityType = null; /** * A last request promise. * * @type {module:ajax.AjaxPromise|null} */ lastSyncPromise = null; /** @private */ _pending; /** @private */ _changing; /** * @param {Object.|Model} [attributes] * @param {{ * collection?: module:collection, * entityType?: string, * urlRoot?: string, * url?: string, * defs?: module:model~defs, * user?: module:models/user, * }} [options] */ constructor(attributes, options) { options = options || {}; /** * An ID attribute. * @type {string} */ this.idAttribute = 'id'; /** * A record ID. * @type {string|null} */ this.id = null; /** * An instance ID. * @type {string} */ this.cid = _underscore.default.uniqueId('c'); /** * Attribute values. * @type {Object.} */ this.attributes = {}; if (options.collection) { this.collection = options.collection; } this.set(attributes || {}); /** * Definitions. */ this.defs = options.defs || {}; if (!this.defs.fields) { this.defs.fields = {}; } if (options.entityType) { this.entityType = options.entityType; this.name = options.entityType; this.urlRoot = options.entityType; } this.urlRoot = options.urlRoot || this.urlRoot; this.url = options.url || this.url; /** @private */ this.changed = {}; /** @private */ this._previousAttributes = null; } /** * @protected * @param {string} [method] HTTP method. * @param {Model} model * @param {Object.} [options] * @returns {module:ajax.AjaxPromise|Promise} */ sync(method, model, options) { const methodMap = { 'create': 'POST', 'update': 'PUT', 'patch': 'PUT', 'delete': 'DELETE', 'read': 'GET' }; const httpMethod = methodMap[method]; if (!httpMethod) { throw new Error(`Bad request method '${method}'.`); } options = options || {}; const url = this.composeSyncUrl(); if (!url) { throw new Error(`No 'url'.`); } const data = model && ['create', 'update', 'patch'].includes(method) ? options.attributes || model.getClonedAttributes() : null; const error = options.error; options.error = (xhr, textStatus, errorThrown) => { options.textStatus = textStatus; options.errorThrown = errorThrown; if (error) { error.call(options.context, xhr, textStatus, errorThrown); } }; const stringData = data ? JSON.stringify(data) : null; const ajaxPromise = !options.bypassRequest ? Espo.Ajax.request(url, httpMethod, stringData, options) : Promise.resolve(); options.xhr = ajaxPromise.xhr; model.trigger('request', url, httpMethod, data, ajaxPromise, options); return ajaxPromise; } /** * Set an attribute value. * * @param {(string|Object)} attribute An attribute name or a {key => value} object. * @param {*} [value] A value or options if the first argument is an object. * @param {{silent?: boolean} & Object.} [options] Options. `silent` won't trigger a `change` event. * @returns {this} * @fires Model#change Unless `{silent: true}`. */ set(attribute, value, options) { if (attribute == null) { return this; } let attributes; if (typeof attribute === 'object') { return this.setMultiple(attribute, value); } attributes = {}; attributes[attribute] = value; return this.setMultiple(attributes, options); } /** * Set attributes values. * * @param {Object.} attributes * @param {{ * silent?: boolean, * unset?: boolean, * sync?: boolean, * } & Object.} [options] Options. `silent` won't trigger a `change` event. * `sync` can be used to emulate syncing. * @return {this} * @fires Model#change Unless `{silent: true}`. * @copyright Credits to Backbone.js. */ setMultiple(attributes, options) { if (this.idAttribute in attributes) { this.id = attributes[this.idAttribute]; } options = options || {}; if (options.ui && !options.action) { options.action = 'ui'; } if (!options.ui && options.action === 'ui') { options.ui = true; } const changes = []; const changing = this._changing; this._changing = true; if (!changing) { this._previousAttributes = _underscore.default.clone(this.attributes); this.changed = {}; } const current = this.attributes; const changed = this.changed; const previous = this._previousAttributes; for (const attribute in attributes) { const value = attributes[attribute]; if (!_underscore.default.isEqual(current[attribute], value)) { changes.push(attribute); } if (!_underscore.default.isEqual(previous[attribute], value)) { changed[attribute] = value; } else { delete changed[attribute]; } options.unset ? delete current[attribute] : current[attribute] = value; } if (!options.silent) { if (changes.length) { this._pending = options; } for (let i = 0; i < changes.length; i++) { this.trigger('change:' + changes[i], this, current[changes[i]], options); } } if (options.sync) { if (this.collection) { const modelSyncOptions = { ...options, action: 'set' }; this.collection.trigger('model-sync', this, modelSyncOptions); } } if (changing) { return this; } if (!options.silent) { // Changes can be recursively nested within `change` events. while (this._pending) { options = this._pending; this._pending = false; this.trigger('change', this, options); } } this._pending = false; this._changing = false; return this; } /** * Unset an attribute. * * @param {string} attribute An attribute. * @param {{silent?: boolean} & Object.} [options] Options. * @return {Model} */ unset(attribute, options) { options = { ...options, unset: true }; const attributes = {}; attributes[attribute] = null; return this.setMultiple(attributes, options); } /** * Get an attribute value. * * @param {string} attribute An attribute name. * @returns {*} */ get(attribute) { if (attribute === this.idAttribute && this.id) { return this.id; } return this.attributes[attribute]; } /** * Whether attribute is set. * * @param {string} attribute An attribute name. * @returns {boolean} */ has(attribute) { const value = this.get(attribute); return typeof value !== 'undefined'; } /** * Removes all attributes from the model. * Fires a `change` event unless `silent` is passed as an option. * * @param {{silent?: boolean} & Object.} [options] Options. */ clear(options) { const attributes = {}; for (const key in this.attributes) { attributes[key] = void 0; } options = { ...options, unset: true }; return this.set(attributes, options); } /** * Whether is new. * * @returns {boolean} */ isNew() { return !this.id; } /** * Whether an attribute changed. To be called only within a 'change' event handler. * * @param {string} [attribute] * @return {boolean} */ hasChanged(attribute) { if (!attribute) { return !_underscore.default.isEmpty(this.changed); } return _underscore.default.has(this.changed, attribute); } /** * Get changed attribute values. To be called only within a 'change' event handler. * * @return {Object.} */ changedAttributes() { return this.hasChanged() ? _underscore.default.clone(this.changed) : {}; } /** * Get previous attributes. To be called only within a 'change' event handler. * * @return {Object.} */ previousAttributes() { return _underscore.default.clone(this._previousAttributes); } /** * Get a previous attribute value. To be called only within a 'change' event handler. * * @param attribute * @return {*} */ previous(attribute) { if (!this._previousAttributes) { return null; } return this._previousAttributes[attribute]; } /** * Fetch values from the backend. * * @param {Object.} [options] Options. * @returns {Promise} * @fires Model#sync */ fetch(options) { options = { ...options }; options.action = 'fetch'; // For bc. const success = options.success; options.success = response => { const serverAttributes = this.prepareAttributes(response, options); this.set(serverAttributes, options); if (success) { success.call(options.context, this, response, options); } this.trigger('sync', this, response, options); if (this.collection) { this.collection.trigger('model-sync', this, options); } }; this.lastSyncPromise = this.sync('read', this, options); return this.lastSyncPromise; } /** * Save values to the backend. * * @param {Object.} [attributes] Attribute values. * @param {{ * patch?: boolean, * wait?: boolean, * } & Object.} [options] Options. Use `patch` to send a PATCH request. If `wait`, attributes will be * set only after the request is completed. * @returns {Promise> & module:ajax.AjaxPromise} * @fires Model#sync * @copyright Credits to Backbone.js. */ save(attributes, options) { options = { ...options }; if (attributes && !options.wait) { this.setMultiple(attributes, options); } const success = options.success; const setAttributes = this.attributes; options.success = response => { this.attributes = setAttributes; let responseAttributes = this.prepareAttributes(response, options); if (options.wait) { responseAttributes = { ...setAttributes, ...responseAttributes }; } options.action = 'save'; if (responseAttributes) { this.setMultiple(responseAttributes, options); } if (success) { success.call(options.context, this, response, options); } this.trigger('sync', this, response, options); if (this.collection) { this.collection.trigger('model-sync', this, options); } }; const error = options.error; options.error = response => { if (error) { error.call(options.context, this, response, options); } this.trigger('error', this, response, options); }; if (attributes && options.wait) { // Set temporary attributes to properly find new IDs. this.attributes = { ...setAttributes, ...attributes }; } const method = this.isNew() ? 'create' : options.patch ? 'patch' : 'update'; if (method === 'patch') { options.attributes = attributes; } const result = this.sync(method, this, options); this.attributes = setAttributes; return result; } /** * Delete the record in the backend. * * @param {{wait?: boolean} & Object.} [options] Options. If `wait`, unsubscribing and * removal from the collection will wait for a successful response. * @returns {Promise} * @fires Model#sync * @copyright Credits to Backbone.js. */ destroy() { let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; options = { ...options }; const success = options.success; const collection = this.collection; const destroy = () => { this.stopListening(); this.trigger('destroy', this, collection, options); }; options.success = response => { if (options.wait) { destroy(); } if (success) { success.call(options.context, this, response, options); } if (!this.isNew()) { const syncOptions = { ...options }; syncOptions.action = 'destroy'; this.trigger('sync', this, response, syncOptions); if (collection) { collection.trigger('model-sync', this, syncOptions); } } }; if (this.isNew()) { _underscore.default.defer(options.success); if (!options.wait) { destroy(); } return Promise.resolve(); } const error = options.error; options.error = response => { if (error) { error.call(options.context, this, response, options); } this.trigger('error', this, response, options); }; const result = this.sync('delete', this, options); if (!options.wait) { destroy(); } return result; } /** * Compose a URL for syncing. * * @protected * @return {string} */ composeSyncUrl() { if (this.url) { return this.url; } let urlRoot = this.urlRoot; if (!urlRoot && this.collection) { urlRoot = this.collection.urlRoot; } if (!urlRoot) { throw new Error("No urlRoot."); } if (this.isNew()) { return urlRoot; } const id = this.get(this.idAttribute); return urlRoot.replace(/[^\/]$/, '$&/') + encodeURIComponent(id); } // noinspection JSUnusedLocalSymbols /** * Prepare attributes. * * @param {*} response A response from the backend. * @param {Object.} options Options. * @return {*} Attributes. * @internal */ prepareAttributes(response, options) { return response; } /** * Clone. * * @return {Model} */ clone() { return new this.constructor(Espo.Utils.cloneDeep(this.attributes), { entityType: this.entityType, urlRoot: this.urlRoot, url: this.url, defs: this.defs }); } /** * Set defs. * * @param {module:model~defs} defs */ setDefs(defs) { this.defs = defs || {}; if (!this.defs.fields) { this.defs.fields = {}; } } /** * Get cloned attribute values. * * @returns {Object.} */ getClonedAttributes() { return Espo.Utils.cloneDeep(this.attributes); } /** * Populate default values. */ populateDefaults() { let defaultHash = {}; const fieldDefs = this.defs.fields; for (const field in fieldDefs) { if (this.hasFieldParam(field, 'default')) { try { defaultHash[field] = this.parseDefaultValue(this.getFieldParam(field, 'default')); } catch (e) { console.error(e); } } const defaultAttributes = this.getFieldParam(field, 'defaultAttributes'); if (defaultAttributes) { for (const attribute in defaultAttributes) { defaultHash[attribute] = defaultAttributes[attribute]; } } } defaultHash = Espo.Utils.cloneDeep(defaultHash); for (const attr in defaultHash) { if (this.has(attr)) { delete defaultHash[attr]; } } this.set(defaultHash, { silent: true }); } /** * @private * @param {*} defaultValue * @returns {*} */ parseDefaultValue(defaultValue) { if (typeof defaultValue === 'string' && defaultValue.indexOf('javascript:') === 0) { const code = defaultValue.substring(11).trim(); const provider = new _defaultValueProvider.default(); defaultValue = provider.get(code); } return defaultValue; } /** * Get a link multiple column value. * * @param {string} field * @param {string} column * @param {string} id * @returns {*} */ getLinkMultipleColumn(field, column, id) { return ((this.get(field + 'Columns') || {})[id] || {})[column]; } /** * @typedef {Object} model:model~setRelateItem * @property {string} link A link. * @property {import('model').default} model A model. */ /** * Set relate data (when creating a related record). * * @param {model:model~setRelateItem | model:model~setRelateItem[]} data */ setRelate(data) { const setRelate = options => { const link = options.link; const model = /** @type {module:model} */options.model; if (!link || !model) { throw new Error('Bad related options'); } const type = this.defs.links[link].type; switch (type) { case 'belongsToParent': this.set(link + 'Id', model.id); this.set(link + 'Type', model.entityType); this.set(link + 'Name', model.get('name')); break; case 'belongsTo': this.set(link + 'Id', model.id); this.set(link + 'Name', model.get('name')); break; case 'hasMany': const ids = []; ids.push(model.id); const names = {}; names[model.id] = model.get('name'); this.set(link + 'Ids', ids); this.set(link + 'Names', names); break; } }; if (Object.prototype.toString.call(data) === '[object Array]') { data.forEach(options => { setRelate(options); }); return; } setRelate(data); } /** * Get a field list. * * @return {string[]} */ getFieldList() { if (!this.defs || !this.defs.fields) { return []; } return Object.keys(this.defs.fields); } /** * Get a field type. * * @param {string} field * @returns {string|null} */ getFieldType(field) { if (!this.defs || !this.defs.fields) { return null; } if (field in this.defs.fields) { return this.defs.fields[field].type || null; } return null; } /** * Get a field param. * * @param {string} field * @param {string} param * @returns {*} */ getFieldParam(field, param) { if (!this.defs || !this.defs.fields) { return null; } if (field in this.defs.fields) { if (param in this.defs.fields[field]) { return this.defs.fields[field][param]; } } return null; } hasFieldParam(field, param) { if (!this.defs || !this.defs.fields) { return false; } if (field in this.defs.fields) { if (param in this.defs.fields[field]) { return true; } } return false; } /** * Get a link type. * * @param {string} link * @returns {string|null} */ getLinkType(link) { if (!this.defs || !this.defs.links) { return null; } if (link in this.defs.links) { return this.defs.links[link].type || null; } return null; } /** * Get a link param. * * @param {string} link A link. * @param {string} param A param. * @returns {*} */ getLinkParam(link, param) { if (!this.defs || !this.defs.links) { return null; } if (link in this.defs.links) { if (param in this.defs.links[link]) { return this.defs.links[link][param]; } } return null; } /** * Is a field read-only. * * @param {string} field A field. * @returns {bool} */ isFieldReadOnly(field) { return this.getFieldParam(field, 'readOnly') || false; } /** * If a field required. * * @param {string} field A field. * @returns {bool} */ isRequired(field) { return this.getFieldParam(field, 'required') || false; } /** * Get IDs of a link-multiple field. * * @param {string} field A link-multiple field name. * @returns {string[]} */ getLinkMultipleIdList(field) { return this.get(field + 'Ids') || []; } /** * Get team IDs. * * @returns {string[]} */ getTeamIdList() { return this.get('teamsIds') || []; } /** * Whether it has a field. * * @param {string} field A field. * @returns {boolean} */ hasField(field) { return 'defs' in this && 'fields' in this.defs && field in this.defs.fields; } /** * Has a link. * * @param {string} link A link. * @returns {boolean} */ hasLink(link) { return 'defs' in this && 'links' in this.defs && link in this.defs.links; } /** * @returns {boolean} */ isEditable() { return true; } /** * @returns {boolean} */ isRemovable() { return true; } /** * Get an entity type. * * @returns {string} */ getEntityType() { return this.name; } /** * Abort the last fetch. */ abortLastFetch() { if (this.lastSyncPromise && this.lastSyncPromise.getReadyState() < 4) { this.lastSyncPromise.abort(); } } } Object.assign(Model.prototype, _bullbone.Events); Model.extend = _bullbone.View.extend; var _default = _exports.default = Model; }); define("metadata", ["exports", "bullbone"], function (_exports, _bullbone) { "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-2025 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 metadata */ /** * Application metadata. * * @mixes Bull.Events */ class Metadata { /** * Application metadata. * * @param {module:cache} [cache] A cache. */ constructor(cache) { /** * @private * @type {module:cache|null} */ this.cache = cache || null; /** * @private * @type {Object} */ this.data = {}; } /** @private */ url = 'Metadata'; /** * Load from cache or the backend (if not yet cached). * * @param {Function|null} [callback] Deprecated. Use a promise. * @param {boolean} [disableCache=false] Deprecated. * @returns {Promise} */ load(callback, disableCache) { if (!disableCache) { if (this.loadFromCache()) { this.trigger('sync'); if (callback) { callback(); } return Promise.resolve(); } } return this.fetch().then(() => { if (callback) { callback(); } }); } /** * Load from the server. * * @returns {Promise} */ loadSkipCache() { return this.load(null, true); } /** * @private * @returns {Promise} */ fetch() { return Espo.Ajax.getRequest(this.url).then(data => { this.data = data; this.storeToCache(); this.trigger('sync'); }); } /** * Get a value. * * @param {string[]|string} path A key path. * @param {*} [defaultValue] A value to return if not set. * @returns {*} Null if not set. */ get(path, defaultValue) { defaultValue = defaultValue || null; let arr; if (Array && Array.isArray && Array.isArray(path)) { arr = path; } else { arr = path.split('.'); } let pointer = this.data; let result = defaultValue; for (let i = 0; i < arr.length; i++) { const key = arr[i]; if (pointer == null || !(key in pointer)) { result = defaultValue; break; } if (arr.length - 1 === i) { result = pointer[key]; } pointer = pointer[key]; } return result; } /** * @private * @returns {boolean|null} True if success. */ loadFromCache() { if (this.cache) { const cached = this.cache.get('app', 'metadata'); if (cached) { this.data = cached; return true; } } return null; } /** @private */ storeToCache() { if (this.cache) { this.cache.set('app', 'metadata', this.data); } } /** * Clear cache. */ clearCache() { if (!this.cache) { return; } this.cache.clear('app', 'metadata'); } /** * Get a scope list. * * @returns {string[]} */ getScopeList() { const scopes = this.get('scopes') || {}; const scopeList = []; for (const scope in scopes) { const d = scopes[scope]; if (d.disabled) { continue; } scopeList.push(scope); } return scopeList; } /** * Get an object-scope list. An object-scope represents a business entity. * * @returns {string[]} */ getScopeObjectList() { const scopes = this.get('scopes') || {}; const scopeList = []; for (const scope in scopes) { const d = scopes[scope]; if (d.disabled) { continue; } if (!d.object) { continue; } scopeList.push(scope); } return scopeList; } /** * Get an entity-scope list. Scopes that represents entities. * * @returns {string[]} */ getScopeEntityList() { const scopes = this.get('scopes') || {}; const scopeList = []; for (const scope in scopes) { const d = scopes[scope]; if (d.disabled) { continue; } if (!d.entity) { continue; } scopeList.push(scope); } return scopeList; } /** * Do not use. * * @internal * @param {Record} data */ setData(data) { this.data = data; } } Object.assign(Metadata.prototype, _bullbone.Events); var _default = _exports.default = Metadata; }); define("language", ["exports", "bullbone"], function (_exports, _bullbone) { "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-2025 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 language */ /** * A language. * * @mixes Bull.Events */ class Language { /** @private */ url = 'I18n'; /** * @class * @param {module:cache} [cache] A cache. */ constructor(cache) { /** * @private * @type {module:cache|null} */ this.cache = cache || null; /** * @private * @type {Object} */ this.data = {}; /** * A name. * * @type {string} */ this.name = 'default'; } /** * Whether an item is set in language data. * * @param {string} scope A scope. * @param {string} category A category. * @param {string} name An item name. * @returns {boolean} */ has(name, category, scope) { if (scope in this.data) { if (category in this.data[scope]) { if (name in this.data[scope][category]) { return true; } } } return false; } /** * Get a value set in language data. * * @param {string} scope A scope. * @param {string} category A category. * @param {string} name An item name. * @returns {*} */ get(scope, category, name) { if (scope in this.data) { if (category in this.data[scope]) { if (name in this.data[scope][category]) { return this.data[scope][category][name]; } } } if (scope === 'Global') { return name; } return false; } /** * Translate a label. * * @param {string} name An item name. * @param {string|'messages'|'labels'|'fields'|'links'|'scopeNames'|'scopeNamesPlural'} [category='labels'] A category. * @param {string} [scope='Global'] A scope. * @returns {string} */ translate(name, category, scope) { scope = scope || 'Global'; category = category || 'labels'; let res = this.get(scope, category, name); if (res === false && scope !== 'Global') { res = this.get('Global', category, name); } return res; } /** * Translation an option item value. * * @param {string} value An option value. * @param {string} field A field name. * @param {string} [scope='Global'] A scope. * @returns {string} */ translateOption(value, field, scope) { let translation = this.translate(field, 'options', scope); if (typeof translation !== 'object') { translation = {}; } return translation[value] || value; } /** * @private */ loadFromCache(loadDefault) { let name = this.name; if (loadDefault) { name = 'default'; } if (this.cache) { const cached = this.cache.get('app', 'language-' + name); if (cached) { this.data = cached; return true; } } return null; } /** * Clear a language cache. */ clearCache() { if (this.cache) { this.cache.clear('app', 'language-' + this.name); } } /** * @private */ storeToCache(loadDefault) { let name = this.name; if (loadDefault) { name = 'default'; } if (this.cache) { this.cache.set('app', 'language-' + name, this.data); } } /** * Load data from cache or backend (if not yet cached). * * @returns {Promise} */ load() { return this._loadInternal(); } /** * @private * @param {boolean} [disableCache=false] * @param {boolean} [loadDefault=false]. * @returns {Promise} */ _loadInternal(disableCache, loadDefault) { if (!disableCache && this.loadFromCache(loadDefault)) { this.trigger('sync'); return Promise.resolve(); } return this.fetch(loadDefault); } /** * Load default-language data from the backend. * * @returns {Promise} */ loadDefault() { return this._loadInternal(false, true); } /** * Load data from the backend. * * @returns {Promise} */ loadSkipCache() { return this._loadInternal(true); } // noinspection JSUnusedGlobalSymbols /** * Load default-language data from the backend. * * @returns {Promise} */ loadDefaultSkipCache() { return this._loadInternal(true, true); } /** * @private * @param {boolean} loadDefault * @returns {Promise} */ fetch(loadDefault) { return Espo.Ajax.getRequest(this.url, { default: loadDefault }).then(data => { this.data = data; this.storeToCache(loadDefault); this.trigger('sync'); }); } /** * Sort a field list by a translated name. * * @param {string} scope An entity type. * @param {string[]} fieldList A field list. * @returns {string[]} */ sortFieldList(scope, fieldList) { return fieldList.sort((v1, v2) => { return this.translate(v1, 'fields', scope).localeCompare(this.translate(v2, 'fields', scope)); }); } /** * Sort an entity type list by a translated name. * * @param {string[]} entityList An entity type list. * @param {boolean} [plural=false] Use a plural label. * @returns {string[]} */ sortEntityList(entityList, plural) { let category = 'scopeNames'; if (plural) { category += 'Plural'; } return entityList.sort((v1, v2) => { return this.translate(v1, category).localeCompare(this.translate(v2, category)); }); } /** * Get a value by a path. * * @param {string[]|string} path A path. * @returns {*} */ translatePath(path) { if (typeof path === 'string' || path instanceof String) { path = path.split('.'); } let pointer = this.data; path.forEach(key => { if (key in pointer) { pointer = pointer[key]; } }); return pointer; } /** * Do not use. * * @param {string} [scope] * @param {Record} [data] * @internal */ setScopeData(scope, data) { this.data[scope] = data; } } Object.assign(Language.prototype, _bullbone.Events); var _default = _exports.default = Language; }); define("acl-manager", ["exports", "acl", "utils", "bullbone"], function (_exports, _acl, _utils, _bullbone) { "use strict"; Object.defineProperty(_exports, "__esModule", { value: true }); _exports.default = void 0; _acl = _interopRequireDefault(_acl); _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-2025 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 acl-manager */ /** * An action. * * @typedef {'create'|'read'|'edit'|'delete'|'stream'} module:acl-manager~action */ /** * An access checking class for a specific scope. */ class AclManager { /** @protected */ data = null; fieldLevelList = ['yes', 'no']; /** * @param {module:models/user} user A user. * @param {Object} implementationClassMap `acl` implementations. * @param {boolean} aclAllowDeleteCreated Allow a user to delete records they created regardless a * role access level. */ constructor(user, implementationClassMap, aclAllowDeleteCreated) { this.setEmpty(); /** @protected */ this.user = user || null; this.implementationClassMap = implementationClassMap || {}; this.aclAllowDeleteCreated = aclAllowDeleteCreated; } /** * @protected */ setEmpty() { this.data = { table: {}, fieldTable: {}, fieldTableQuickAccess: {} }; this.implementationHash = {}; this.forbiddenFieldsCache = {}; this.implementationClassMap = {}; this.forbiddenAttributesCache = {}; } /** * Get an `acl` implementation. * * @protected * @param {string} scope A scope. * @returns {module:acl} */ getImplementation(scope) { if (!(scope in this.implementationHash)) { let implementationClass = _acl.default; if (scope in this.implementationClassMap) { implementationClass = this.implementationClassMap[scope]; } const forbiddenFieldList = this.getScopeForbiddenFieldList(scope); const params = { aclAllowDeleteCreated: this.aclAllowDeleteCreated, teamsFieldIsForbidden: forbiddenFieldList.includes('teams'), forbiddenFieldList: forbiddenFieldList }; this.implementationHash[scope] = new implementationClass(this.getUser(), scope, params, this); } return this.implementationHash[scope]; } /** * @return {import('models/user').default} * @protected */ getUser() { return this.user; } /** * @internal */ set(data) { data = data || {}; this.data = data; this.data.table = this.data.table || {}; this.data.fieldTable = this.data.fieldTable || {}; this.data.attributeTable = this.data.attributeTable || {}; } /** * @deprecated Use `getPermissionLevel`. * * @returns {string|null} */ get(name) { return this.data[name] || null; } /** * Get a permission level. * * @param {string} permission A permission name. * @returns {'yes'|'all'|'team'|'no'} */ getPermissionLevel(permission) { let permissionKey = permission; if (permission.slice(-10) !== 'Permission') { permissionKey = permission + 'Permission'; } return this.data[permissionKey] || 'no'; } /** * Get access level to a scope action. * * @param {string} scope A scope. * @param {module:acl-manager~action} action An action. * @returns {'yes'|'all'|'team'|'own'|'no'|null} */ getLevel(scope, action) { if (!(scope in this.data.table)) { return null; } const scopeItem = this.data.table[scope]; if (typeof scopeItem !== 'object' || !(action in scopeItem)) { return null; } return scopeItem[action]; } /** * Clear access data. * * @internal */ clear() { this.setEmpty(); } /** * Check whether a scope has ACL. * * @param {string} scope A scope. * @returns {boolean} */ checkScopeHasAcl(scope) { const data = (this.data.table || {})[scope]; if (typeof data === 'undefined') { return false; } return true; } /** * Check access to a scope. * * @param {string} scope A scope. * @param {module:acl-manager~action|null} [action=null] An action. * @param {boolean} [precise=false] Deprecated. Not used. * @returns {boolean} True if access allowed. */ checkScope(scope, action, precise) { let data = (this.data.table || {})[scope]; if (typeof data === 'undefined') { data = null; } return this.getImplementation(scope).checkScope(data, action, precise); } /** * Check access to a model. * * @param {module:model} model A model. * @param {module:acl-manager~action|null} [action=null] An action. * @param {boolean} [precise=false] To return `null` if not enough data is set in a model. * E.g. the `teams` field is not yet loaded. * @returns {boolean|null} True if access allowed, null if not enough data to determine. */ checkModel(model, action, precise) { const scope = model.entityType; // todo move this to custom acl if (action === 'edit') { if (!model.isEditable()) { return false; } } if (action === 'delete') { if (!model.isRemovable()) { return false; } } let data = (this.data.table || {})[scope]; if (typeof data === 'undefined') { data = null; } const impl = this.getImplementation(scope); if (action) { const methodName = 'checkModel' + _utils.default.upperCaseFirst(action); if (methodName in impl) { return impl[methodName](model, data, precise); } } return impl.checkModel(model, data, action, precise); } /** * Check access to a scope or a model. * * @param {string|module:model} subject What to check. A scope or a model. * @param {module:acl-manager~action|null} [action=null] An action. * @param {boolean} [precise=false] To return `null` if not enough data is set in a model. * E.g. the `teams` field is not yet loaded. * @returns {boolean|null} True if access allowed, null if not enough data to determine. */ check(subject, action, precise) { if (typeof subject === 'string') { return this.checkScope(subject, action, precise); } return this.checkModel(subject, action, precise); } /** * Check if a user is owner to a model. * * @param {module:model} model A model. * @returns {boolean|null} True if owner, null if not clear. */ checkIsOwner(model) { return this.getImplementation(model.entityType).checkIsOwner(model); } // noinspection JSUnusedGlobalSymbols /** * Check if a user in a team of a model. * * @param {module:model} model A model. * @returns {boolean|null} True if in a team, null if not clear. */ checkInTeam(model) { return this.getImplementation(model.entityType).checkInTeam(model); } // noinspection JSUnusedGlobalSymbols /** * Check if a record is shared with the user. * * @param {module:model} model A model. * @returns {boolean|null} True if shared, null if not clear. */ checkIsShared(model) { return this.getImplementation(model.entityType).checkIsShared(model); } // noinspection JSUnusedGlobalSymbols /** * Check an assignment permission to a user. * * @param {module:models/user} user A user. * @returns {boolean} True if access allowed. */ checkAssignmentPermission(user) { return this.checkPermission('assignmentPermission', user); } /** * Check a user permission to a user. * * @param {module:models/user} user A user. * @returns {boolean} True if access allowed. */ checkUserPermission(user) { return this.checkPermission('userPermission', user); } /** * Check a specific permission to a user. * * @param {string} permission A permission name. * @param {module:models/user} user A user. * @returns {boolean|null} True if access allowed. Null if not enough data loaded to know for sure. */ checkPermission(permission, user) { if (this.getUser().isAdmin()) { return true; } const level = this.getPermissionLevel(permission); if (level === 'no') { if (user.id === this.getUser().id) { return true; } return false; } if (level === 'team') { if (!user.has('teamsIds')) { return null; } let result = false; const teamsIds = user.get('teamsIds') || []; teamsIds.forEach(id => { if ((this.getUser().get('teamsIds') || []).includes(id)) { result = true; } }); return result; } if (level === 'all') { return true; } if (level === 'yes') { return true; } return false; } /** * Get a list of forbidden fields for an entity type. * * @param {string} scope An entity type. * @param {'read'|'edit'} [action='read'] An action. * @param {'yes'|'no'} [thresholdLevel='no'] A threshold level. * @returns {string[]} A forbidden field list. */ getScopeForbiddenFieldList(scope, action, thresholdLevel) { action = action || 'read'; thresholdLevel = thresholdLevel || 'no'; const key = scope + '_' + action + '_' + thresholdLevel; if (key in this.forbiddenFieldsCache) { return _utils.default.clone(this.forbiddenFieldsCache[key]); } const levelList = this.fieldLevelList.slice(this.fieldLevelList.indexOf(thresholdLevel)); const fieldTableQuickAccess = this.data.fieldTableQuickAccess || {}; const scopeData = fieldTableQuickAccess[scope] || {}; const fieldsData = scopeData.fields || {}; const actionData = fieldsData[action] || {}; const fieldList = []; levelList.forEach(level => { const list = actionData[level] || []; list.forEach(field => { if (fieldList.includes(field)) { return; } fieldList.push(field); }); }); this.forbiddenFieldsCache[key] = fieldList; return _utils.default.clone(fieldList); } /** * Get a list of forbidden attributes for an entity type. * * @param {string} scope An entity type. * @param {'read'|'edit'} [action='read'] An action. * @param {'yes'|'no'} [thresholdLevel='no'] A threshold level. * @returns {string[]} A forbidden attribute list. */ getScopeForbiddenAttributeList(scope, action, thresholdLevel) { action = action || 'read'; thresholdLevel = thresholdLevel || 'no'; const key = scope + '_' + action + '_' + thresholdLevel; if (key in this.forbiddenAttributesCache) { return _utils.default.clone(this.forbiddenAttributesCache[key]); } const levelList = this.fieldLevelList.slice(this.fieldLevelList.indexOf(thresholdLevel)); const fieldTableQuickAccess = this.data.fieldTableQuickAccess || {}; const scopeData = fieldTableQuickAccess[scope] || {}; const attributesData = scopeData.attributes || {}; const actionData = attributesData[action] || {}; const attributeList = []; levelList.forEach(level => { const list = actionData[level] || []; list.forEach(attribute => { if (attributeList.includes(attribute)) { return; } attributeList.push(attribute); }); }); this.forbiddenAttributesCache[key] = attributeList; return _utils.default.clone(attributeList); } /** * Check an assignment permission to a team. * * @param {string} teamId A team ID. * @returns {boolean} True if access allowed. */ checkTeamAssignmentPermission(teamId) { if (this.getPermissionLevel('assignmentPermission') === 'all') { return true; } return this.getUser().getLinkMultipleIdList('teams').includes(teamId); } /** * Check access to a field. * @param {string} scope An entity type. * @param {string} field A field. * @param {'read'|'edit'} [action='read'] An action. * @returns {boolean} True if access allowed. */ checkField(scope, field, action) { return !this.getScopeForbiddenFieldList(scope, action).includes(field); } } AclManager.extend = _bullbone.View.extend; var _default = _exports.default = AclManager; }); define("views/modals/edit", ["exports", "views/modal", "backbone"], function (_exports, _modal, _backbone) { "use strict"; Object.defineProperty(_exports, "__esModule", { value: true }); _exports.default = void 0; _modal = _interopRequireDefault(_modal); _backbone = _interopRequireDefault(_backbone); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } /************************************************************************ * This file is part of EspoCRM. * * EspoCRM – Open Source CRM application. * Copyright (C) 2014-2025 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/modals/edit */ /** * A quick edit modal. */ class EditModalView extends _modal.default { template = 'modals/edit'; cssName = 'edit-modal'; /** @protected */ saveDisabled = false; /** @protected */ fullFormDisabled = false; /** @protected */ editView = null; escapeDisabled = true; className = 'dialog dialog-record'; /** @protected */ sideDisabled = false; /** @protected */ bottomDisabled = false; isCollapsible = true; /** * @private * @type {boolean} */ wasModified = false; /** * @private * @type {string} */ nameAttribute; shortcutKeys = { /** @this EditModalView */ 'Control+Enter': function (e) { if (this.saveDisabled) { return; } if (this.buttonList.findIndex(item => item.name === 'save' && !item.hidden && !item.disabled) === -1) { return; } e.preventDefault(); e.stopPropagation(); if (document.activeElement instanceof HTMLInputElement) { // Fields may need to fetch data first. document.activeElement.dispatchEvent(new Event('change', { bubbles: true })); } this.actionSave(); }, /** @this EditModalView */ 'Control+KeyS': function (e) { if (this.saveDisabled) { return; } if (this.buttonList.findIndex(item => item.name === 'save' && !item.hidden && !item.disabled) === -1) { return; } e.preventDefault(); e.stopPropagation(); this.actionSaveAndContinueEditing(); }, /** @this EditModalView */ 'Escape': function (e) { if (this.saveDisabled) { return; } e.stopPropagation(); e.preventDefault(); const focusedFieldView = this.getRecordView().getFocusedFieldView(); if (focusedFieldView) { this.model.set(focusedFieldView.fetch(), { skipReRender: true }); } if (this.getRecordView().isChanged) { this.confirm(this.translate('confirmLeaveOutMessage', 'messages')).then(() => this.actionClose()); return; } this.actionClose(); }, /** @this EditModalView */ 'Control+Backslash': function (e) { this.getRecordView().handleShortcutKeyControlBackslash(e); } }; /** * @typedef {Record} module:views/modals/edit~options * * @property {string} entityType An entity type. * @property {string} [id] An ID. * @property {string} [layoutName] A layout name. * @property {Record} [attributes] Attributes. * @property {model:model~setRelateItem | model:model~setRelateItem[]} [relate] A relate data. * @property {import('view-record-helper')} [recordHelper] A record helper. * @property {boolean} [saveDisabled] Disable save. * @property {boolean} [fullFormDisabled] Disable full-form. * @property {string} [headerText] A header text. * @property {boolean} [focusForCreate] Focus for create. * @property {string} [rootUrl] A root URL. * @property {string} [returnUrl] A return URL. * @property {{ * controller: string, * action: string|null, * options: {isReturn?: boolean} & Record, * }} [returnDispatchParams] Return dispatch params. * @property {string} [fullFormUrl] A full-form URL. As of v9.0. */ /** * @param {module:views/modals/edit~options} options */ constructor(options) { super(options); } setup() { this.buttonList = []; if ('saveDisabled' in this.options) { this.saveDisabled = this.options.saveDisabled; } if (!this.saveDisabled) { this.buttonList.push({ name: 'save', label: 'Save', style: 'primary', title: 'Ctrl+Enter', onClick: () => this.actionSave() }); } this.fullFormDisabled = this.options.fullFormDisabled || this.fullFormDisabled; this.layoutName = this.options.layoutName || this.layoutName; if (!this.fullFormDisabled) { this.buttonList.push({ name: 'fullForm', label: 'Full Form', onClick: () => this.actionFullForm() }); } this.buttonList.push({ name: 'cancel', label: 'Cancel', title: 'Esc' }); this.scope = this.scope || this.options.scope || this.options.entityType; this.entityType = this.options.entityType || this.scope; this.id = this.options.id; this.nameAttribute = this.getMetadata().get(`clientDefs.${this.entityType}.nameAttribute`) || 'name'; if (this.options.headerText !== undefined) { this.headerHtml = undefined; this.headerText = this.options.headerText; } this.sourceModel = this.model; this.waitForView('edit'); this.getModelFactory().create(this.entityType, model => { if (this.id) { if (this.sourceModel) { model = this.model = this.sourceModel.clone(); } else { this.model = model; model.id = this.id; } model.fetch().then(() => { if (!this.headerText) { this.headerHtml = this.composeHeaderHtml(); } this.createRecordView(model); }); return; } this.model = model; if (this.options.relate) { model.setRelate(this.options.relate); } if (this.options.attributes) { model.set(this.options.attributes); } if (!this.headerText) { this.headerHtml = this.composeHeaderHtml(); } this.createRecordView(model); }); this.listenTo(this.model, 'change', (m, o) => { if (o.ui) { this.wasModified = true; } }); } /** * @param {module:model} model * @param {function} [callback] */ createRecordView(model, callback) { const viewName = this.editView || this.getMetadata().get(['clientDefs', model.entityType, 'recordViews', 'editSmall']) || this.getMetadata().get(['clientDefs', model.entityType, 'recordViews', 'editQuick']) || 'views/record/edit-small'; const options = { model: model, fullSelector: this.containerSelector + ' .edit-container', type: 'editSmall', layoutName: this.layoutName || 'detailSmall', buttonsDisabled: true, sideDisabled: this.sideDisabled, bottomDisabled: this.bottomDisabled, focusForCreate: this.options.focusForCreate, recordHelper: this.options.recordHelper, webSocketDisabled: true, exit: () => {} }; this.handleRecordViewOptions(options); this.createView('edit', viewName, options, callback).then(/** import('views/fields/base').default */view => { this.listenTo(view, 'before:save', () => this.trigger('before:save', model)); if (this.options.relate && 'link' in this.options.relate) { const link = this.options.relate.link; if (model.hasField(link) && ['link'].includes(model.getFieldType(link))) { view.setFieldReadOnly(link); } } }); } handleRecordViewOptions(options) {} /** * @return {module:views/record/edit} */ getRecordView() { return this.getView('edit'); } onBackdropClick() { if (this.getRecordView().isChanged) { return; } this.close(); } /** * @protected * @return {string} */ composeHeaderHtml() { let html; if (!this.id) { html = $('').text(this.getLanguage().translate('Create ' + this.scope, 'labels', this.scope)).get(0).outerHTML; } else { const wrapper = document.createElement('span'); const scope = document.createElement('span'); scope.textContent = this.getLanguage().translate(this.scope, 'scopeNames'); const separator = document.createElement('span'); separator.classList.add('chevron-right'); const name = this.model.attributes[this.nameAttribute]; wrapper.append(document.createTextNode(this.getLanguage().translate('Edit') + ' · '), scope); if (name) { wrapper.append(' ', separator, ' ', name); } html = wrapper.outerHTML; } if (!this.fullFormDisabled) { const url = this.id ? '#' + this.scope + '/edit/' + this.id : '#' + this.scope + '/create'; html = $('').attr('href', url).addClass('action').attr('title', this.translate('Full Form')).attr('data-action', 'fullForm').append(html).get(0).outerHTML; } html = this.getHelper().getScopeColorIconHtml(this.scope) + html; return html; } /** * @protected * @param {{bypassClose?: boolean}} [data] */ actionSave() { let data = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; const editView = this.getRecordView(); const model = editView.model; const $buttons = this.dialog.$el.find('.modal-footer button'); $buttons.addClass('disabled').attr('disabled', 'disabled'); editView.save().then(() => { const wasNew = !this.id; if (wasNew) { this.id = model.id; } this.trigger('after:save', model, { bypassClose: data.bypassClose }); if (!data.bypassClose) { this.dialog.close(); if (wasNew) { const url = `#${this.scope}/view/${model.id}`; const name = model.attributes[this.nameAttribute] || this.model.id; const msg = this.translate('Created') + '\n' + `[${name}](${url})`; Espo.Ui.notify(msg, 'success', 4000, { suppress: true }); } return; } $(this.containerElement).find('.modal-header .modal-title-text').html(this.composeHeaderHtml()); $buttons.removeClass('disabled').removeAttr('disabled'); }).catch(() => { $buttons.removeClass('disabled').removeAttr('disabled'); }); } actionSaveAndContinueEditing() { this.actionSave({ bypassClose: true }); } actionFullForm() { let url; const router = this.getRouter(); let attributes; let model; let options; if (!this.id) { url = this.options.fullFormUrl || `#${this.scope}/create`; attributes = this.getRecordView().fetch(); model = this.getRecordView().model; attributes = { ...attributes, ...model.getClonedAttributes() }; options = { attributes: attributes, relate: this.options.relate, returnUrl: this.options.returnUrl || _backbone.default.history.fragment, returnDispatchParams: this.options.returnDispatchParams || null }; if (this.options.rootUrl) { options.rootUrl = this.options.rootUrl; } setTimeout(() => { router.dispatch(this.scope, 'create', options); router.navigate(url, { trigger: false }); }, 10); } else { url = this.options.fullFormUrl || `#${this.scope}/edit/${this.id}`; attributes = this.getRecordView().fetch(); model = this.getRecordView().model; attributes = { ...attributes, ...model.getClonedAttributes() }; options = { attributes: attributes, returnUrl: this.options.returnUrl || _backbone.default.history.fragment, returnDispatchParams: this.options.returnDispatchParams || null, model: this.sourceModel, id: this.id }; if (this.options.rootUrl) { options.rootUrl = this.options.rootUrl; } setTimeout(() => { router.dispatch(this.scope, 'edit', options); router.navigate(url, { trigger: false }); }, 10); } this.trigger('leave'); this.dialog.close(); } async beforeCollapse() { if (this.wasModified) { this.getRecordView().setConfirmLeaveOut(false); this.getRouter().addWindowLeaveOutObject(this); } } afterExpand() { if (this.wasModified) { this.getRecordView().setConfirmLeaveOut(true); } this.getRouter().removeWindowLeaveOutObject(this); } } var _default = _exports.default = EditModalView; }); define("view-helper", ["exports", "marked", "dompurify", "handlebars"], function (_exports, _marked, _dompurify, _handlebars) { "use strict"; Object.defineProperty(_exports, "__esModule", { value: true }); _exports.default = void 0; _dompurify = _interopRequireDefault(_dompurify); _handlebars = _interopRequireDefault(_handlebars); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } /************************************************************************ * This file is part of EspoCRM. * * EspoCRM – Open Source CRM application. * Copyright (C) 2014-2025 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 view-helper */ /** * A view helper. */ class ViewHelper { constructor() { this._registerHandlebarsHelpers(); /** @private */ this.mdBeforeList = [ /*{ regex: /```\n?([\s\S]*?)```/g, value: (s, string) => { return '```\n' + string.replace(/\\\>/g, '>') + '```'; }, },*/ { // Also covers triple-backtick blocks. regex: /`([\s\S]*?)`/g, value: (s, string) => { // noinspection RegExpRedundantEscape return '`' + string.replace(/\\\ 99) { node.removeAttribute('start'); } if (node instanceof HTMLFormElement) { if (node.action) { node.removeAttribute('action'); } if (node.hasAttribute('method')) { node.removeAttribute('method'); } } if (node instanceof HTMLButtonElement) { if (node.type === 'submit') { node.type = 'button'; } } }); _dompurify.default.addHook('afterSanitizeAttributes', function (node) { if (node instanceof HTMLAnchorElement) { const href = node.getAttribute('href'); if (href && !href.startsWith('#')) { node.setAttribute('rel', 'noopener noreferrer'); } if (node.targetBlank) { node.setAttribute('target', '_blank'); node.setAttribute('rel', 'noopener noreferrer'); } } }); _dompurify.default.addHook('uponSanitizeAttribute', (node, data) => { if (data.attrName === 'style') { const style = data.attrValue.split(';').map(s => s.trim()).filter(rule => { const [property, value] = rule.split(':').map(s => s.trim().toLowerCase()); if (property === 'position' && ['absolute', 'fixed', 'sticky'].includes(value)) { return false; } return true; }); data.attrValue = style.join('; '); } }); } /** * A layout manager. * * @type {module:layout-manager} */ layoutManager = null; /** * A config. * * @type {module:models/settings} */ settings = null; /** * A config. * * @type {module:models/settings} */ config = null; /** * A current user. * * @type {module:models/user} */ user = null; /** * A preferences. * * @type {module:models/preferences} */ preferences = null; /** * An ACL manager. * * @type {module:acl-manager} */ acl = null; /** * A model factory. * * @type {module:model-factory} */ modelFactory = null; /** * A collection factory. * * @type {module:collection-factory} */ collectionFactory = null; /** * A router. * * @type {module:router} */ router = null; /** * A storage. * * @type {module:storage} */ storage = null; /** * A session storage. * * @type {module:session-storage} */ sessionStorage = null; /** * A date-time util. * * @type {module:date-time} */ dateTime = null; /** * A language. * * @type {module:language} */ language = null; /** * A metadata. * * @type {module:metadata} */ metadata = null; /** * A field-manager util. * * @type {module:field-manager} */ fieldManager = null; /** * A cache. * * @type {module:cache} */ cache = null; /** * A theme manager. * * @type {module:theme-manager} */ themeManager = null; /** * A web-socket manager. Null if not enabled. * * @type {module:web-socket-manager|null} */ webSocketManager = null; /** * A number util. * * @type {module:num-util} */ numberUtil = null; /** * A page-title util. * * @type {module:page-title} */ pageTitle = null; /** * A broadcast channel. * * @type {?module:broadcast-channel} */ broadcastChannel = null; /** * A base path. * * @type {string} */ basePath = ''; /** * Application parameters. * * @type {import('app-params').default|null} */ appParams = null; /** * @private */ _registerHandlebarsHelpers() { _handlebars.default.registerHelper('img', img => { return new _handlebars.default.SafeString(`img`); }); _handlebars.default.registerHelper('prop', (object, name) => { if (object === undefined) { console.warn(`Undefined value passed to 'prop' helper.`); return undefined; } if (name in object) { return object[name]; } return undefined; }); _handlebars.default.registerHelper('var', (name, context, options) => { if (typeof context === 'undefined') { return null; } let contents = context[name]; if (options.hash.trim) { contents = contents.trim(); } return new _handlebars.default.SafeString(contents); }); _handlebars.default.registerHelper('concat', function (left, right) { return left + right; }); _handlebars.default.registerHelper('ifEqual', function (left, right, options) { // noinspection EqualityComparisonWithCoercionJS if (left == right) { return options.fn(this); } return options.inverse(this); }); _handlebars.default.registerHelper('ifNotEqual', function (left, right, options) { // noinspection EqualityComparisonWithCoercionJS if (left != right) { return options.fn(this); } return options.inverse(this); }); _handlebars.default.registerHelper('ifPropEquals', function (object, property, value, options) { // noinspection EqualityComparisonWithCoercionJS if (object[property] == value) { return options.fn(this); } return options.inverse(this); }); _handlebars.default.registerHelper('ifAttrEquals', function (model, attr, value, options) { // noinspection EqualityComparisonWithCoercionJS if (model.get(attr) == value) { return options.fn(this); } return options.inverse(this); }); _handlebars.default.registerHelper('ifAttrNotEmpty', function (model, attr, options) { const value = model.get(attr); if (value !== null && typeof value !== 'undefined') { return options.fn(this); } return options.inverse(this); }); _handlebars.default.registerHelper('get', (model, name) => model.get(name)); _handlebars.default.registerHelper('length', arr => arr.length); _handlebars.default.registerHelper('translate', (name, options) => { const scope = options.hash.scope || null; const category = options.hash.category || null; if (name === 'null') { return ''; } return this.language.translate(name, category, scope); }); _handlebars.default.registerHelper('dropdownItem', (name, options) => { const scope = options.hash.scope || null; const label = options.hash.label; const labelTranslation = options.hash.labelTranslation; const data = options.hash.data; const hidden = options.hash.hidden; const disabled = options.hash.disabled; const title = options.hash.title; const link = options.hash.link; const action = options.hash.action || name; const iconHtml = options.hash.iconHtml; const iconClass = options.hash.iconClass; let html = options.hash.html || options.hash.text || (labelTranslation ? this.language.translatePath(labelTranslation) : this.language.translate(label, 'labels', scope)); if (!options.hash.html) { html = this.escapeString(html); } if (iconHtml) { html = iconHtml + ' ' + html; } else if (iconClass) { const iconHtml = $('').addClass(iconClass).get(0).outerHTML; html = iconHtml + ' ' + html; } const $li = $('
  • ').addClass(hidden ? 'hidden' : '').addClass(disabled ? 'disabled' : ''); const $a = $('').attr('role', 'button').attr('tabindex', '0').attr('data-name', name).addClass(options.hash.className || '').addClass('action').html(html); if (action) { $a.attr('data-action', action); } $li.append($a); link ? $a.attr('href', link) : $a.attr('role', 'button'); if (data) { for (const key in data) { $a.attr('data-' + Espo.Utils.camelCaseToHyphen(key), data[key]); } } if (disabled) { $li.attr('disabled', 'disabled'); } if (title) { $a.attr('title', title); } return new _handlebars.default.SafeString($li.get(0).outerHTML); }); _handlebars.default.registerHelper('button', (name, options) => { const style = options.hash.style || 'default'; const scope = options.hash.scope || null; const label = options.hash.label || name; const labelTranslation = options.hash.labelTranslation; const link = options.hash.link; const iconHtml = options.hash.iconHtml; const iconClass = options.hash.iconClass; let html = options.hash.html || options.hash.text || (labelTranslation ? this.language.translatePath(labelTranslation) : this.language.translate(label, 'labels', scope)); if (!options.hash.html) { html = this.escapeString(html); } if (iconHtml) { html = iconHtml + ' ' + '' + html + ''; } else if (iconClass) { const iconHtml = $('').addClass(iconClass).get(0).outerHTML; html = iconHtml + ' ' + '' + html + ''; } const tag = link ? '' : ' {{/each}}
  • `; /** @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); let _init_config, _init_extra_config; /************************************************************************ * This file is part of EspoCRM. * * EspoCRM – Open Source CRM application. * Copyright (C) 2014-2025 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 { static #_ = [_init_config, _init_extra_config] = _applyDecs(this, [], [[(0, _di.inject)(_settings.default), 0, "config"]]).e; 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; } } 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); let _init_metadata, _init_extra_metadata; /************************************************************************ * This file is part of EspoCRM. * * EspoCRM – Open Source CRM application. * Copyright (C) 2014-2025 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 { static #_ = [_init_metadata, _init_extra_metadata] = _applyDecs(this, [], [[(0, _di.inject)(_metadata.default), 0, "metadata"]]).e; /** * @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}`); }); }); }); }); } } 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); let _init_metadata, _init_extra_metadata; /************************************************************************ * This file is part of EspoCRM. * * EspoCRM – Open Source CRM application. * Copyright (C) 2014-2025 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 { static #_ = [_init_metadata, _init_extra_metadata] = _applyDecs(this, [], [[(0, _di.inject)(_metadata.default), 0, "metadata"]]).e; /** * @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}`); } }); } } 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-2025 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-2025 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); 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-2025 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 { static #_ = [_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; /** * @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(); }); } } _exports.default = AttachmentInsertSourceFromHelper; }); define("ui", ["exports", "marked", "dompurify", "jquery"], function (_exports, _marked, _dompurify, _jquery) { "use strict"; Object.defineProperty(_exports, "__esModule", { value: true }); _exports.default = void 0; _dompurify = _interopRequireDefault(_dompurify); _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-2025 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 ui */ /** * Dialog parameters. * * @typedef {Object} module:ui.Dialog~Params * * @property {string} [className='dialog'] A class-name or multiple space separated. * @property {'static'|true|false} [backdrop='static'] A backdrop. * @property {boolean} [closeButton=true] A close button. * @property {boolean} [collapseButton=false] A collapse button. * @property {string|null} [header] A header HTML. * @property {string} [body] A body HTML. * @property {number|null} [width] A width. * @property {boolean} [removeOnClose=true] To remove on close. * @property {boolean} [draggable=false] Is draggable. * @property {function(): void} [onRemove] An on-remove callback. * @property {function(): void} [onClose] An on-close callback. * @property {function(): void} [onBackdropClick] An on-backdrop-click callback. * @property {string} [container='body'] A container selector. * @property {boolean} [keyboard=true] Enable a keyboard control. The `Esc` key closes a dialog. * @property {boolean} [footerAtTheTop=false] To display a footer at the top. * @property {module:ui.Dialog~Button[]} [buttonList] Buttons. * @property {Array} [dropdownItemList] Dropdown action items. * @property {boolean} [fullHeight] Deprecated. * @property {Number} [bodyDiffHeight] * @property {Number} [screenWidthXs] * @property {boolean} [maximizeButton] Is maximizable. * @property {function()} [onMaximize] On maximize handler. * @property {function()} [onMinimize] On minimize handler. * @property {string} [backdropClassName] A backdrop class name. As of v9.1.0. */ /** * A button or dropdown action item. * * @typedef {Object} module:ui.Dialog~Button * * @property {string} name A name. * @property {boolean} [pullLeft=false] Deprecated. Use the `position` property. * @property {'left'|'right'} [position='left'] A position. * @property {string} [html] HTML. * @property {string} [text] A text. * @property {boolean} [disabled=false] Disabled. * @property {boolean} [hidden=false] Hidden. * @property {'default'|'danger'|'success'|'warning'} [style='default'] A style. * @property {function(Espo.Ui.Dialog, JQueryEventObject): void} [onClick] An on-click callback. * @property {string} [className] An additional class name. * @property {string} [title] A title. */ /** * @type {Espo.Ui.Dialog[]} */ const shownDialogList = []; /** * @alias Espo.Ui.Dialog */ class Dialog { height; fitHeight; onRemove; onClose; onBackdropClick; buttons; screenWidthXs; backdropClassName; /** * @private * @type {boolean} */ maximizeButton = false; /** * @private * @type {HTMLAnchorElement} */ maximizeButtonElement; /** * @private * @type {HTMLAnchorElement} */ minimizeButtonElement; /** * @private * @type {function()} */ onMaximize; /** * @private * @type {function()} */ onMinimize; /** * @param {module:ui.Dialog~Params} options Options. */ constructor(options) { options = options || {}; /** @private */ this.className = 'dialog-confirm'; /** @private */ this.backdrop = 'static'; /** @private */ this.closeButton = true; /** @private */ this.collapseButton = false; /** @private */ this.header = null; /** @private */ this.body = ''; /** @private */ this.width = null; /** * @private * @type {module:ui.Dialog~Button[]} */ this.buttonList = []; /** * @private * @type {Array} */ this.dropdownItemList = []; /** @private */ this.removeOnClose = true; /** @private */ this.draggable = false; /** @private */ this.container = 'body'; /** @private */ this.options = options; /** @private */ this.keyboard = true; this.activeElement = document.activeElement; const params = ['className', 'backdrop', 'keyboard', 'closeButton', 'collapseButton', 'header', 'body', 'width', 'height', 'fitHeight', 'buttons', 'buttonList', 'dropdownItemList', 'removeOnClose', 'draggable', 'container', 'onRemove', 'onClose', 'onBackdropClick', 'maximizeButton', 'backdropClassName']; params.forEach(param => { if (param in options) { this[param] = options[param]; } }); if (options.onMaximize) { this.onMaximize = options.onMaximize; } if (options.onMinimize) { this.onMinimize = options.onMinimize; } /** @private */ this.onCloseIsCalled = false; if (this.buttons && this.buttons.length) { /** * @private * @type {module:ui.Dialog~Button[]} */ this.buttonList = this.buttons; } this.id = 'dialog-' + Math.floor(Math.random() * 100000); if (typeof this.backdrop === 'undefined') { /** @private */ this.backdrop = 'static'; } const $header = this.getHeader(); const $footer = this.getFooter(); const $body = (0, _jquery.default)('
    ').addClass('modal-body body').html(this.body); const $content = (0, _jquery.default)('
    ').addClass('modal-content'); if ($header) { $content.append($header); } if ($footer && this.options.footerAtTheTop) { $content.append($footer); } $content.append($body); if ($footer && !this.options.footerAtTheTop) { $content.append($footer); } const $dialog = (0, _jquery.default)('
    ').addClass('modal-dialog').append($content); const $container = (0, _jquery.default)(this.container); (0, _jquery.default)('
    ').attr('id', this.id).attr('class', this.className + ' modal').attr('role', 'dialog').attr('tabindex', '-1').append($dialog).appendTo($container); /** * An element. * * @type {JQuery} */ this.$el = (0, _jquery.default)('#' + this.id); /** * @private * @type {Element} */ this.el = this.$el.get(0); this.$el.find('header a.close').on('click', () => { //this.close(); }); this.initButtonEvents(); if (this.draggable) { this.$el.find('header').css('cursor', 'pointer'); // noinspection JSUnresolvedReference this.$el.draggable({ handle: 'header' }); } const modalContentEl = this.$el.find('.modal-content'); if (this.width) { modalContentEl.css('width', this.width); modalContentEl.css('margin-left', '-' + parseInt(this.width.replace('px', '')) / 5 + 'px'); } if (this.removeOnClose) { this.$el.on('hidden.bs.modal', e => { if (this.$el.get(0) === e.target) { if (!this.onCloseIsCalled) { this.close(); } if (this.skipRemove) { return; } this.remove(); } }); } const $window = (0, _jquery.default)(window); this.$el.on('shown.bs.modal', () => { (0, _jquery.default)('.modal-backdrop').not('.stacked').addClass('stacked'); const headerHeight = this.$el.find('.modal-header').outerHeight() || 0; const footerHeight = this.$el.find('.modal-footer').outerHeight() || 0; let diffHeight = headerHeight + footerHeight; if (!options.fullHeight) { diffHeight = diffHeight + options.bodyDiffHeight; } if (this.fitHeight || options.fullHeight) { const processResize = () => { const windowHeight = window.innerHeight; const windowWidth = $window.width(); if (!options.fullHeight && windowHeight < 512) { this.$el.find('div.modal-body').css({ maxHeight: 'none', overflow: 'auto', height: 'none' }); return; } const cssParams = { overflow: 'auto' }; if (options.fullHeight) { cssParams.height = windowHeight - diffHeight + 'px'; this.$el.css('paddingRight', 0); } else { if (windowWidth <= options.screenWidthXs) { cssParams.maxHeight = 'none'; } else { cssParams.maxHeight = windowHeight - diffHeight + 'px'; } } this.$el.find('div.modal-body').css(cssParams); }; $window.off('resize.modal-height'); $window.on('resize.modal-height', processResize); processResize(); } }); const $documentBody = (0, _jquery.default)(document.body); this.$el.on('hidden.bs.modal', () => { if ((0, _jquery.default)('.modal:visible').length > 0) { $documentBody.addClass('modal-open'); } }); } /** * Get a general container element. * * @return {HTMLDivElement} * @since 9.1.0 */ getElement() { return this.el; } /** * Update the header text. * * @param {string} text * @since 9.1.0 */ setHeaderText(text) { const element = this.el.querySelector('.modal-header .modal-title'); if (!element) { return; } element.textContent = text; } /** @private */ callOnClose() { if (this.onClose) { this.onClose(); } } /** @private */ callOnBackdropClick() { if (this.onBackdropClick) { this.onBackdropClick(); } } /** @private */ callOnRemove() { if (this.onRemove) { this.onRemove(); } } /** * Set action items. * * @param {module:ui.Dialog~Button[]} buttonList * @param {Array} dropdownItemList */ setActionItems(buttonList, dropdownItemList) { this.buttonList = buttonList; this.dropdownItemList = dropdownItemList; } /** * Init button events. */ initButtonEvents() { this.buttonList.forEach(o => { if (typeof o.onClick === 'function') { const $button = (0, _jquery.default)('#' + this.id + ' .modal-footer button[data-name="' + o.name + '"]'); $button.on('click', e => o.onClick(this, e)); } }); this.dropdownItemList.forEach(o => { if (o === false) { return; } if (typeof o.onClick === 'function') { const $button = (0, _jquery.default)('#' + this.id + ' .modal-footer a[data-name="' + o.name + '"]'); $button.on('click', e => o.onClick(this, e)); } }); } /** * @private * @return {JQuery|null} */ getHeader() { if (!this.header) { return null; } const $header = (0, _jquery.default)('
    ').addClass('modal-header').addClass(this.options.fixedHeaderHeight ? 'fixed-height' : '').append((0, _jquery.default)('

    ').addClass('modal-title').append((0, _jquery.default)('').addClass('modal-title-text').html(this.header))); if (this.collapseButton) { $header.prepend((0, _jquery.default)('').addClass('collapse-button').attr('role', 'button').attr('tabindex', '-1').attr('data-action', 'collapseModal').append((0, _jquery.default)('').addClass('fas fa-minus'))); } if (this.maximizeButton) { { const a = document.createElement('a'); a.classList.add('maximize-button'); a.role = 'button'; a.tabIndex = -1; a.setAttribute('data-action', 'maximizeModal'); const icon = document.createElement('span'); icon.classList.add('far', 'fa-window-maximize'); a.append(icon); $header.prepend(a); this.maximizeButtonElement = a; a.addEventListener('click', () => { this.maximizeButtonElement.classList.add('hidden'); this.minimizeButtonElement.classList.remove('hidden'); this.el.querySelector('.modal-dialog').classList.add('maximized'); if (this.onMaximize) { this.onMaximize(); } this.getElement().focus(); }); } { const a = document.createElement('a'); a.classList.add('minimize-button', 'hidden'); a.role = 'button'; a.tabIndex = -1; a.setAttribute('data-action', 'minimizeModal'); const icon = document.createElement('span'); icon.classList.add('far', 'fa-window-minimize'); a.append(icon); $header.prepend(a); this.minimizeButtonElement = a; a.addEventListener('click', () => { this.minimizeButtonElement.classList.add('hidden'); this.maximizeButtonElement.classList.remove('hidden'); this.el.querySelector('.modal-dialog').classList.remove('maximized'); if (this.onMinimize) { this.onMinimize(); } this.getElement().focus(); }); } } if (this.closeButton) { $header.prepend((0, _jquery.default)('').addClass('close').attr('data-dismiss', 'modal').attr('role', 'button').attr('tabindex', '-1').append((0, _jquery.default)('').attr('aria-hidden', 'true').html('×'))); } return $header; } /** * Get a footer. * * @return {JQuery|null} */ getFooter() { if (!this.buttonList.length && !this.dropdownItemList.length) { return null; } const $footer = (0, _jquery.default)('