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-2026 EspoCRM, Inc. * Website: https://www.espocrm.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License version 3, * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ 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("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-2026 EspoCRM, Inc. * Website: https://www.espocrm.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License version 3, * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ /** @module 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/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); var _staticBlock; let _init_dateTime, _init_extra_dateTime; /************************************************************************ * This file is part of EspoCRM. * * EspoCRM – Open Source CRM application. * Copyright (C) 2014-2026 EspoCRM, Inc. * Website: https://www.espocrm.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License version 3, * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } function _applyDecs(e, t, n, r, o, i) { var a, c, u, s, f, l, p, d = Symbol.metadata || Symbol.for("Symbol.metadata"), m = Object.defineProperty, h = Object.create, y = [h(null), h(null)], v = t.length; function g(t, n, r) { return function (o, i) { n && (i = o, o = e); for (var a = 0; a < t.length; a++) i = t[a].apply(o, r ? [i] : []); return r ? i : o; }; } function b(e, t, n, r) { if ("function" != typeof e && (r || void 0 !== e)) throw new TypeError(t + " must " + (n || "be") + " a function" + (r ? "" : " or undefined")); return e; } function applyDec(e, t, n, r, o, i, u, s, f, l, p) { function d(e) { if (!p(e)) throw new TypeError("Attempted to access private element on non-instance"); } var h = [].concat(t[0]), v = t[3], w = !u, D = 1 === o, S = 3 === o, j = 4 === o, E = 2 === o; function I(t, n, r) { return function (o, i) { return n && (i = o, o = e), r && r(o), P[t].call(o, i); }; } if (!w) { var P = {}, k = [], F = S ? "get" : j || D ? "set" : "value"; if (f ? (l || D ? P = { get: _setFunctionName(function () { return v(this); }, r, "get"), set: function (e) { t[4](this, e); } } : P[F] = v, l || _setFunctionName(P[F], r, E ? "" : F)) : l || (P = Object.getOwnPropertyDescriptor(e, r)), !l && !f) { if ((c = y[+s][r]) && 7 !== (c ^ o)) throw Error("Decorating two elements with the same name (" + P[F].name + ") is not supported yet"); y[+s][r] = o < 3 ? 1 : o; } } for (var N = e, O = h.length - 1; O >= 0; O -= n ? 2 : 1) { var T = b(h[O], "A decorator", "be", !0), z = n ? h[O - 1] : void 0, A = {}, H = { kind: ["field", "accessor", "method", "getter", "setter", "class"][o], name: r, metadata: a, addInitializer: function (e, t) { if (e.v) throw new TypeError("attempted to call addInitializer after decoration was finished"); b(t, "An initializer", "be", !0), i.push(t); }.bind(null, A) }; if (w) c = T.call(z, N, H), A.v = 1, b(c, "class decorators", "return") && (N = c);else if (H.static = s, H.private = f, c = H.access = { has: f ? p.bind() : function (e) { return r in e; } }, j || (c.get = f ? E ? function (e) { return d(e), P.value; } : I("get", 0, d) : function (e) { return e[r]; }), E || S || (c.set = f ? I("set", 0, d) : function (e, t) { e[r] = t; }), N = T.call(z, D ? { get: P.get, set: P.set } : P[F], H), A.v = 1, D) { if ("object" == typeof N && N) (c = b(N.get, "accessor.get")) && (P.get = c), (c = b(N.set, "accessor.set")) && (P.set = c), (c = b(N.init, "accessor.init")) && k.unshift(c);else if (void 0 !== N) throw new TypeError("accessor decorators must return an object with get, set, or init properties or undefined"); } else b(N, (l ? "field" : "method") + " decorators", "return") && (l ? k.unshift(N) : P[F] = N); } return o < 2 && u.push(g(k, s, 1), g(i, s, 0)), l || w || (f ? D ? u.splice(-1, 0, I("get", s), I("set", s)) : u.push(E ? P[F] : b.call.bind(P[F])) : m(e, r, P)), N; } function w(e) { return m(e, d, { configurable: !0, enumerable: !0, value: a }); } return void 0 !== i && (a = i[d]), a = h(null == a ? null : a), f = [], l = function (e) { e && f.push(g(e)); }, p = function (t, r) { for (var i = 0; i < n.length; i++) { var a = n[i], c = a[1], l = 7 & c; if ((8 & c) == t && !l == r) { var p = a[2], d = !!a[3], m = 16 & c; applyDec(t ? e : e.prototype, a, m, d ? "#" + p : _toPropertyKey(p), l, l < 2 ? [] : t ? s = s || [] : u = u || [], f, !!t, d, r, t && d ? function (t) { return _checkInRHS(t) === e; } : o); } } }, p(8, 0), p(0, 0), p(8, 1), p(0, 1), l(u), l(s), c = f, v || w(e), { e: c, get c() { var n = []; return v && [w(e = applyDec(e, [t], r, e.name, 5, n)), g(n, 1)]; } }; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } function _setFunctionName(e, t, n) { "symbol" == typeof t && (t = (t = t.description) ? "[" + t + "]" : ""); try { Object.defineProperty(e, "name", { configurable: !0, value: n ? n + " " + t : t }); } catch (e) {} return e; } function _checkInRHS(e) { if (Object(e) !== e) throw TypeError("right-hand side of 'in' should be an object, got " + (null !== e ? typeof e : "null")); return e; } 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 { 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; } static #_ = _staticBlock = () => [_init_dateTime, _init_extra_dateTime] = _applyDecs(this, [], [[(0, _di.inject)(_dateTime.default), 0, "dateTime"]]).e; } _exports.default = DefaultValueProvider; _staticBlock(); }); 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-2026 EspoCRM, Inc. * Website: https://www.espocrm.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License version 3, * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ /** @module 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. // Does not prevent if handled from a nested view. @todo 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("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-2026 EspoCRM, Inc. * Website: https://www.espocrm.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License version 3, * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ /** @module 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)('