diff --git a/client/src/app.js b/client/src/app.js index 4955f9f660..35bf654d81 100644 --- a/client/src/app.js +++ b/client/src/app.js @@ -69,30 +69,30 @@ function ( /** Bull */Bull, Ui, Utils, - AclManager, - Cache, - Storage, - Settings, - Language, - Metadata, - FieldManager, - User, - Preferences, - ModelFactory, - CollectionFactory, - PreLoader, - BaseController, - Router, - DateTime, - LayoutManager, - ThemeManager, - SessionStorage, - ViewHelper, - WebSocketManager, + /** typeof module:acl-manager.Class */AclManager, + /** typeof module:cache.Class */Cache, + /** typeof module:storage.Class */Storage, + /** typeof module:models/settings.Class */Settings, + /** typeof module:language.Class */Language, + /** typeof module:metadata.Class */Metadata, + /** typeof module:field-manager.Class */FieldManager, + /** typeof module:models/user.Class */User, + /** typeof module:models/preferences.Class */Preferences, + /** typeof module:model-factory.Class */ModelFactory, + /** typeof module:collection-factory.Class */CollectionFactory, + /** typeof module:pre-loader.Class */PreLoader, + /** typeof module:controllers/base.Class */BaseController, + /** typeof module:router.Class */Router, + /** typeof module:date-time.Class */DateTime, + /** typeof module:layout-manager.Class */LayoutManager, + /** typeof module:theme-manager.Class */ThemeManager, + /** typeof module:session-storage.Class */SessionStorage, + /** typeof module:view-helper.Class */ViewHelper, + /** typeof module:web-socket-manager.Class */WebSocketManager, Ajax, - NumberUtil, - PageTitle, - BroadcastChannel + /** typeof module:number.Class */NumberUtil, + /** typeof module:page-title.Class */PageTitle, + /** typeof module:broadcast-channel.Class */BroadcastChannel ) { /** * A main application class. @@ -249,6 +249,8 @@ function ( auth: null, /** + * A base controller. + * * @private * @type {module:controllers/base.Class} */ @@ -286,6 +288,7 @@ function ( viewFactory: null, /** + * @type {Function} * @private */ viewLoader: null, @@ -712,8 +715,6 @@ function ( initView: function () { let helper = this.viewHelper = new ViewHelper(); - // @todo Use `helper.container`. - helper.layoutManager = new LayoutManager(this.cache, this.id); helper.settings = this.settings; helper.config = this.settings; @@ -723,19 +724,18 @@ function ( helper.modelFactory = this.modelFactory; helper.collectionFactory = this.collectionFactory; helper.storage = this.storage; + helper.sessionStorage = this.sessionStorage; helper.dateTime = this.dateTime; helper.language = this.language; helper.metadata = this.metadata; helper.fieldManager = this.fieldManager; helper.cache = this.cache; - helper.storage = this.storage; helper.themeManager = this.themeManager; - helper.sessionStorage = this.sessionStorage; - helper.basePath = this.basePath; - helper.appParams = this.appParams; helper.webSocketManager = this.webSocketManager; helper.numberUtil = this.numberUtil; helper.pageTitle = new PageTitle(this.settings); + helper.basePath = this.basePath; + helper.appParams = this.appParams; this.viewLoader = (viewName, callback) => { require(Utils.composeViewClassName(viewName), callback); diff --git a/client/src/controllers/base.js b/client/src/controllers/base.js index dfc76fe9fd..dd19e31365 100644 --- a/client/src/controllers/base.js +++ b/client/src/controllers/base.js @@ -26,10 +26,21 @@ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ -define('controllers/base', 'controller', function (Dep) { +define('controllers/base', ['controller'], function (Dep) { - return Dep.extend({ + /** + * A base controller. + * + * @class + * @name Class + * @extends module:controller.Class + * @memberOf module:controllers/base + */ + return Dep.extend(/** @lends module:controllers/base.Class# */{ + /** + * Log in. + */ login: function () { var viewName = this.getConfig().get('loginView') || 'views/login'; @@ -64,6 +75,9 @@ define('controllers/base', 'controller', function (Dep) { }); }, + /** + * @private + */ normalizeLoginData: function (userName, data) { return { auth: { @@ -79,10 +93,9 @@ define('controllers/base', 'controller', function (Dep) { }; }, - actionLogin: function () { - this.login(); - }, - + /** + * Log out. + */ logout: function () { var title = this.getConfig().get('applicationName') || 'EspoCRM'; $('head title').text(title); @@ -90,6 +103,21 @@ define('controllers/base', 'controller', function (Dep) { this.trigger('logout'); }, + /** + * Clear cache. + */ + clearCache: function () { + this.entire('views/clear-cache', { + cache: this.getCache() + }, view => { + view.render(); + }); + }, + + actionLogin: function () { + this.login(); + }, + actionLogout: function () { this.logout(); }, @@ -98,20 +126,18 @@ define('controllers/base', 'controller', function (Dep) { this.clearCache(); }, - clearCache: function () { - this.entire('views/clear-cache', { - cache: this.getCache() - }, (view) => { - view.render(); - }); - }, - + /** + * Error Not Found. + */ error404: function () { this.entire('views/base', {template: 'errors/404'}, (view) => { view.render(); }); }, + /** + * Error Forbidden. + */ error403: function () { this.entire('views/base', {template: 'errors/403'}, (view) => { view.render(); diff --git a/client/src/language.js b/client/src/language.js index 9c5ec3d640..300fcd7791 100644 --- a/client/src/language.js +++ b/client/src/language.js @@ -112,8 +112,8 @@ define('language', ['ajax'], function (Ajax) { * Translate a label. * * @param {string} name An item name. - * @param {string} [category='labels'] A category. - * @param {string} [scope='Global'] A scope. + * @param {string|null} [category='labels'] A category. + * @param {string|null} [scope='Global'] A scope. * @returns {string} */ translate: function (name, category, scope) { diff --git a/client/src/models/preferences.js b/client/src/models/preferences.js index 59577bba6c..c6e2804dbf 100644 --- a/client/src/models/preferences.js +++ b/client/src/models/preferences.js @@ -26,7 +26,7 @@ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ -define('models/preferences', 'model', function (Dep) { +define('models/preferences', ['model'], function (Dep) { return Dep.extend({ diff --git a/client/src/models/settings.js b/client/src/models/settings.js index 6c248f8d4e..5be6cc1941 100644 --- a/client/src/models/settings.js +++ b/client/src/models/settings.js @@ -26,12 +26,28 @@ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ -define('models/settings', 'model', function (Dep) { +define('models/settings', ['model'], function (Dep) { - return Dep.extend({ + /** + * A config. + * + * @class + * @name Class + * @extends module:model.Class + * @memberOf module:models/settings + */ + return Dep.extend(/** @lends module:models/settings.Class# */{ + /** + * @inheritDoc + */ name: 'Settings', + /** + * Load. + * + * @returns {Promise} + */ load: function () { return new Promise(resolve => { this.fetch() @@ -39,6 +55,12 @@ define('models/settings', 'model', function (Dep) { }); }, + /** + * Get a value by a path. + * + * @param {string} arr A path. + * @returns {*} Null if not set. + */ getByPath: function (arr) { if (!arr.length) { return null; diff --git a/client/src/search-manager.js b/client/src/search-manager.js index 65a0ddee11..216a00973c 100644 --- a/client/src/search-manager.js +++ b/client/src/search-manager.js @@ -28,14 +28,93 @@ define('search-manager', [], function () { - let SearchManager = function (collection, type, storage, dateTime, defaultData, emptyOnReset) { + /** + * Search data. + * + * @typedef {Object} module:search-manager~data + * + * @property {string} primary A primary filter. + * @property {Object.} bool Bool filters. + * @property {Object} advanced Advanced filters (field filters). Contains data needed for both + * the backend and frontend. + */ + + /** + * A where item. Sent to the backend. + * + * @typedef {Object} module:search-manager~whereItem + * + * @property {string} type A type. + * @property {string} [attribute] An attribute (field). + * @property {module:search-manager~whereItem[]|string|number|boolean|null} [value] A value. + * @property {boolean} [dateTime] Is a date-time item. + * @property {string} [timeZone] A time-zone (for date-time items). + */ + + /** + * A search manager. + * + * @class + * @name Class + * @memberOf module:search-manager + * + * @param {module:collection.Class} collection A collection. + * @param {string|null} type A type. Used for a storage key. + * @param {module:storage.Class|null} storage A storage. + * @param {module:date-time.Class|null} dateTime A date-time util. + * @param {module:search-manager~data|null} [defaultData=null] Default search data. + * @param {boolean} [emptyOnReset=false] To empty on reset. + */ + let SearchManager = function ( + collection, + type, + storage, + dateTime, + defaultData, + emptyOnReset + ) { + /** + * @private + * @type {module:collection.Class} + */ this.collection = collection; + + /** + * An entity type. + * + * @public + * @type {string} + */ this.scope = collection.name; + + /** + * @private + * @type {module:storage.Class|null} + */ this.storage = storage; + + /** + * @private + * @type {string} + */ this.type = type || 'list'; + + /** + * @private + * @type {module:date-time.Class|null} + */ this.dateTime = dateTime; + + /** + * @private + * @type {boolean} + */ this.emptyOnReset = emptyOnReset; + /** + * @private + * @type {Object} + */ this.emptyData = { textFilter: '', bool: {}, @@ -58,10 +137,11 @@ define('search-manager', [], function () { this.sanitizeData(); }; - _.extend(SearchManager.prototype, { - - data: null, + _.extend(SearchManager.prototype, /** @lends module:search-manager.Class# */{ + /** + * @private + */ sanitizeData: function () { if (!('advanced' in this.data)) { this.data.advanced = {}; @@ -76,6 +156,11 @@ define('search-manager', [], function () { } }, + /** + * Get a where clause. The where clause to be sent to the backend. + * + * @returns {module:search-manager~whereItem[]} + */ getWhere: function () { let where = []; @@ -131,6 +216,9 @@ define('search-manager', [], function () { return where; }, + /** + * @private + */ getWherePart: function (name, defs) { var attribute = name; @@ -182,6 +270,11 @@ define('search-manager', [], function () { }; }, + /** + * Load stored data. + * + * @returns {module:search-manager.Class} + */ loadStored: function () { this.data = this.storage.get(this.type + 'Search', this.scope) || @@ -193,28 +286,53 @@ define('search-manager', [], function () { return this; }, + /** + * Get data. + * + * @returns {module:search-manager~data} + */ get: function () { return this.data; }, + /** + * Set advanced filters. + * + * @param {Object} advanced Advanced filters. + */ setAdvanced: function (advanced) { this.data = Espo.Utils.clone(this.data); this.data.advanced = advanced; }, + /** + * Set bool filters. + * + * @param {Object.} bool Bool filters. + */ setBool: function (bool) { this.data = Espo.Utils.clone(this.data); this.data.bool = bool; }, + /** + * Set a primary filter. + * + * @param {string} primary A filter. + */ setPrimary: function (primary) { this.data = Espo.Utils.clone(this.data); this.data.primary = primary; }, + /** + * Set data. + * + * @param {module:search-manager~data} data Data. + */ set: function (data) { this.data = data; @@ -223,6 +341,9 @@ define('search-manager', [], function () { } }, + /** + * Empty data. + */ empty: function () { this.data = Espo.Utils.clone(this.emptyData); @@ -231,6 +352,9 @@ define('search-manager', [], function () { } }, + /** + * Reset. + */ reset: function () { if (this.emptyOnReset) { this.empty(); @@ -245,6 +369,9 @@ define('search-manager', [], function () { } }, + /** + * @private + */ getDateTimeWhere: function (type, field, value) { var where = { field: field diff --git a/client/src/session-storage.js b/client/src/session-storage.js index cd1a541016..fbdde30dfa 100644 --- a/client/src/session-storage.js +++ b/client/src/session-storage.js @@ -28,10 +28,26 @@ define('session-storage', ['storage'], function (Dep) { - return Dep.extend({ + /** + * A session storage. Cleared when a page session ends. + * + * @class + * @name Class + * @memberOf module:session-storage + */ + return Dep.extend(/** @lends module:session-storage.Class# */{ + /** + * @private + */ storageObject: sessionStorage, + /** + * Get a value. + * + * @param {string} name A name. + * @returns {*} Null if not set. + */ get: function (name) { try { var stored = this.storageObject.getItem(name); @@ -62,6 +78,12 @@ define('session-storage', ['storage'], function (Dep) { return null; }, + /** + * Set (store) a value. + * + * @param {string} name A name. + * @param {*} value A value. + */ set: function (name, value) { if (value instanceof Object || Array.isArray(value) || value === true || value === false) { value = '__JSON__:' + JSON.stringify(value); @@ -75,10 +97,21 @@ define('session-storage', ['storage'], function (Dep) { } }, + /** + * Has a value. + * + * @param {string} name A name. + * @returns {boolean} + */ has: function (name) { return this.storageObject.getItem(name) !== null; }, + /** + * Clear a value. + * + * @param {string} name A name. + */ clear: function (name) { for (let i in this.storageObject) { if (i === name) { diff --git a/client/src/storage.js b/client/src/storage.js index ea109d745b..1af88f3469 100644 --- a/client/src/storage.js +++ b/client/src/storage.js @@ -28,28 +28,66 @@ define('storage', [], function () { + /** + * A storage. Data is saved across browser sessions, has no expiration time. + * + * @class + * @name Class + * @memberOf module:storage + */ let Storage = function () {}; - _.extend(Storage.prototype, { + _.extend(Storage.prototype, /** @lends module:storage.Class# */{ + /** + * @protected + */ prefix: 'espo', + /** + * @protected + */ storageObject: localStorage, + /** + * @private + * @param {string} type + * @returns {string} + */ composeFullPrefix: function (type) { return this.prefix + '-' + type; }, + /** + * @private + * @param {string} type + * @param {string} name + * @returns {string} + */ composeKey: function (type, name) { return this.composeFullPrefix(type) + '-' + name; }, + /** + * @private + * @param {string} type + */ checkType: function (type) { - if (typeof type === 'undefined' && toString.call(type) !== '[object String]' || type === 'cache') { + 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: function (type, name) { this.checkType(type); @@ -58,6 +96,13 @@ define('storage', [], function () { 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: function (type, name) { this.checkType(type); @@ -100,6 +145,13 @@ define('storage', [], function () { return null; }, + /** + * Set (store) a value. + * + * @param {string} type A type (category). + * @param {string} name A name. + * @param {*} value A value. + */ set: function (type, name, value) { this.checkType(type); @@ -118,6 +170,12 @@ define('storage', [], function () { } }, + /** + * Clear a value. + * + * @param {string} type A type (category). + * @param {string} name A name. + */ clear: function (type, name) { let reText; diff --git a/client/src/theme-manager.js b/client/src/theme-manager.js index b1f7e33882..9864f49cca 100644 --- a/client/src/theme-manager.js +++ b/client/src/theme-manager.js @@ -28,20 +28,39 @@ define('theme-manager', [], function () { + /** + * A theme manager. + * + * @class + * @name Class + * @memberOf module:theme-manager + * + * @param {module:models/settings.Class} config A config. + * @param {module:models/preferences.Class} preferences Preferences. + * @param {module:metadata.Class} metadata Metadata. + */ let ThemeManager = function (config, preferences, metadata) { this.config = config; this.preferences = preferences; this.metadata = metadata; }; - _.extend(ThemeManager.prototype, { + _.extend(ThemeManager.prototype, /** module:theme-manager.Class# */{ + /** + * @private + */ defaultParams: { screenWidthXs: 768, dashboardCellHeight: 155, dashboardCellMargin: 19, }, + /** + * Get a theme name for the current user. + * + * @returns {string} + */ getName: function () { if (!this.config.get('userThemesDisabled')) { let name = this.preferences.get('theme'); @@ -54,6 +73,11 @@ define('theme-manager', [], function () { return this.config.get('theme'); }, + /** + * Get a theme name currently applied to the DOM. + * + * @returns {string|null} Null if not applied. + */ getAppliedName: function () { let name = window.getComputedStyle(document.body).getPropertyValue('--theme-name'); @@ -64,6 +88,11 @@ define('theme-manager', [], function () { return name.trim(); }, + /** + * Whether a current theme is applied to the DOM. + * + * @returns {boolean} + */ isApplied: function () { let appliedName = this.getAppliedName(); @@ -74,8 +103,14 @@ define('theme-manager', [], function () { return this.getName() === appliedName; }, + /** + * Get a stylesheet path for a current theme. + * + * @returns {string} + */ getStylesheet: function () { - let link = this.metadata.get(['themes', this.getName(), 'stylesheet']) || 'client/css/espo/espo.css'; + let link = this.metadata.get(['themes', this.getName(), 'stylesheet']) || + 'client/css/espo/espo.css'; if (this.config.get('cacheTimestamp')) { link += '?r=' + this.config.get('cacheTimestamp').toString(); @@ -84,6 +119,11 @@ define('theme-manager', [], function () { return link; }, + /** + * Get an iframe stylesheet path for a current theme. + * + * @returns {string} + */ getIframeStylesheet: function () { let link = this.metadata.get(['themes', this.getName(), 'stylesheetIframe']) || 'client/css/espo/espo-iframe.css'; @@ -95,6 +135,11 @@ define('theme-manager', [], function () { return link; }, + /** + * Get an iframe-fallback stylesheet path for a current theme. + * + * @returns {string} + */ getIframeFallbackStylesheet: function () { let link = this.metadata.get(['themes', this.getName(), 'stylesheetIframeFallback']) || 'client/css/espo/espo-iframe.css'; @@ -106,10 +151,22 @@ define('theme-manager', [], function () { return link; }, + /** + * Get a theme parameter. + * + * @param {string} name A parameter name. + * @returns {*} Null if not set. + */ getParam: function (name) { - return this.metadata.get(['themes', this.getName(), name]) || this.defaultParams[name] || null; + return this.metadata.get(['themes', this.getName(), name]) || + this.defaultParams[name] || null; }, + /** + * Whether a current theme is different from a system default theme. + * + * @returns {boolean} + */ isUserTheme: function () { if (!this.config.get('userThemesDisabled')) { let name = this.preferences.get('theme'); @@ -122,8 +179,7 @@ define('theme-manager', [], function () { } return false; - } - + }, }); return ThemeManager; diff --git a/client/src/view-helper.js b/client/src/view-helper.js index 95078b53a5..259d9d7b14 100644 --- a/client/src/view-helper.js +++ b/client/src/view-helper.js @@ -26,19 +26,28 @@ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ -define('view-helper', ['lib!marked', 'lib!dompurify'], function (marked, DOMPurify) { +define('view-helper', ['lib!marked', 'lib!dompurify', 'lib!handlebars'], +function (marked, DOMPurify, /** typeof Handlebars */Handlebars) { /** - * @class Espo.ViewHelper + * A view helper. + * + * @class + * @name Class + * @memberOf module:view-helper */ let ViewHelper = function () { this._registerHandlebarsHelpers(); + /** + * @private + */ this.mdBeforeList = [ { regex: /\&\#x60;\&\#x60;\&\#x60;\n?([\s\S]*?)\&\#x60;\&\#x60;\&\#x60;/g, value: function (s, string) { - return '
' +string.replace(/\*/g, '*').replace(/\~/g, '~') + '
'; + return '
' +string.replace(/\*/g, '*').replace(/\~/g, '~') +
+                        '
'; } }, { @@ -78,97 +87,159 @@ define('view-helper', ['lib!marked', 'lib!dompurify'], function (marked, DOMPuri }); }; - _.extend(ViewHelper.prototype, /** @lends Espo.ViewHelper */{ + _.extend(ViewHelper.prototype, /** @lends module:view-helper.Class# */{ + /** + * A layout manager. + * + * @type {module:layout-manager.Class} + */ layoutManager: null, /** - * @property {Espo.Modles.Settings} settings + * A config. + * + * @type {module:models/settings.Class} */ settings: null, /** - * @property {Espo.Modles.Settings} settings + * A config. + * + * @type {module:models/settings.Class} + */ + config: null, + + /** + * A current user. + * + * @type {module:models/user.Class} */ user: null, + /** + * A preferences. + * + * @type {module:models/preferences.Class} + */ preferences: null, /** - * @property {Espo.Language} language + * An ACL manager. + * + * @type {module:acl-manager.Class} + */ + acl: null, + + /** + * A model factory. + * + * @type {module:model-factory.Class} + */ + modelFactory: null, + + /** + * A collection factory. + * + * @type {module:collection-factory.Class} + */ + collectionFactory: null, + + /** + * A storage. + * + * @type {module:storage.Class} + */ + storage: null, + + /** + * A session storage. + * + * @type {module:session-storage.Class} + */ + sessionStorage: null, + + /** + * A date-time util. + * + * @type {module:date-time.Class} + */ + dateTime: null, + + /** + * A language. + * + * @type {module:language.Class} */ language: null, /** - * @param {string} name - * @returns {*} + * A metadata. + * + * @type {module:metadata.Class} */ - getAppParam: function (name) { - return (this.appParams || {})[name]; - }, + metadata: null, /** - * @param {string} text - * @returns {string} + * A field-manager util. + * + * @type {module:field-manager.Class} */ - stripTags: function (text) { - text = text || ''; - - if (typeof text === 'string' || text instanceof String) { - return text.replace(/<\/?[^>]+(>|$)/g, ''); - } - - return text; - }, + fieldManager: null, /** - * @param {string} text - * @returns {string} + * A cache. + * + * @type {module:cache.Class} */ - escapeString: function (text) { - return Handlebars.Utils.escapeExpression(text); - }, + cache: null, /** - * @param {string} id - * @param {'small'|'medium'|'large'} size - * @param {int} width - * @param {string} additionalClassName - * @returns {string} + * A theme manager. + * + * @type {module:theme-manager.Class} */ - getAvatarHtml: function (id, size, width, additionalClassName) { - if (this.config.get('avatarsDisabled')) { - return ''; - } + themeManager: null, - var t; + /** + * A web-socket manager + * + * @type {module:web-socket-manager.Class} + */ + webSocketManager: null, - var cache = this.cache; + /** + * A number util. + * + * @type {module:number.Class} + */ + numberUtil: null, - if (cache) { - t = cache.get('app', 'timestamp'); - } - else { - t = Date.now(); - } + /** + * A page-title util. + * + * @type {module:page-title.Class} + */ + pageTitle: null, - var basePath = this.basePath || ''; - var size = size || 'small'; + /** + * A base path. + * + * @type {string} + */ + basePath: '', - var width = (width || 16).toString(); - - var className = 'avatar'; - - if (additionalClassName) { - className += ' ' + additionalClassName; - } - - return ''; - }, + /** + * Application parameters. + * + * @type {Object} + */ + appParams: null, + /** + * @private + */ _registerHandlebarsHelpers: function () { - Handlebars.registerHelper('img', img => { return new Handlebars.SafeString(""); }); @@ -268,7 +339,7 @@ define('view-helper', ['lib!marked', 'lib!dompurify'], function (marked, DOMPuri options.hash.text || this.language.translate(label, 'labels', scope); - className = options.hash.className || ''; + let className = options.hash.className || ''; if (className) { className = ' ' + className; @@ -297,7 +368,7 @@ define('view-helper', ['lib!marked', 'lib!dompurify'], function (marked, DOMPuri }); Handlebars.registerHelper('complexText', (text, options) => { - return this.transfromMarkdownText(text, options.hash); + return this.transformMarkdownText(text, options.hash); }); Handlebars.registerHelper('translateOption', (name, options) => { @@ -400,19 +471,100 @@ define('view-helper', ['lib!marked', 'lib!dompurify'], function (marked, DOMPuri }, /** - * @param {string} text - * @returns {string} + * Get an application parameter. + * + * @param {string} name + * @returns {*} */ - transfromMarkdownInlineText: function (text) { - return this.transfromMarkdownText(text, {inline: true}); + getAppParam: function (name) { + return (this.appParams || {})[name]; }, /** + * Strip tags. + * + * @deprecated * @param {string} text - * @param {{inline: boolean, linksInNewTab: boolean}} options * @returns {string} */ - transfromMarkdownText: function (text, options) { + stripTags: function (text) { + text = text || ''; + + if (typeof text === 'string' || text instanceof String) { + return text.replace(/<\/?[^>]+(>|$)/g, ''); + } + + return text; + }, + + /** + * Escape a string. + * + * @param {string} text A string. + * @returns {string} + */ + escapeString: function (text) { + return Handlebars.Utils.escapeExpression(text); + }, + + /** + * Get a user avatar HTML. + * + * @param {string} id A user ID. + * @param {'small'|'medium'|'large'} [size='small'] A size. + * @param {int} [width=16] + * @param {string} [additionalClassName] An additional class-name. + * @returns {string} + */ + getAvatarHtml: function (id, size, width, additionalClassName) { + if (this.config.get('avatarsDisabled')) { + return ''; + } + + var t; + + var cache = this.cache; + + if (cache) { + t = cache.get('app', 'timestamp'); + } + else { + t = Date.now(); + } + + var basePath = this.basePath || ''; + var size = size || 'small'; + + var width = (width || 16).toString(); + + var className = 'avatar'; + + if (additionalClassName) { + className += ' ' + additionalClassName; + } + + return ''; + }, + + /** + * A Markdown text to HTML (one-line). + * + * @param {string} text A text. + * @returns {string} HTML. + */ + transformMarkdownInlineText: function (text) { + return this.transformMarkdownText(text, {inline: true}); + }, + + /** + * A Markdown text to HTML. + * + * @param {string} text A text. + * @param {{inline?: boolean, linksInNewTab?: boolean}} [options] Options. + * @returns {string} HTML. + */ + transformMarkdownText: function (text, options) { text = text || ''; text = Handlebars.Utils.escapeExpression(text).replace(/>+/g, '>'); @@ -445,9 +597,11 @@ define('view-helper', ['lib!marked', 'lib!dompurify'], function (marked, DOMPuri }, /** - * @param {string} scope - * @param {boolean} noWhiteSpace - * @param {string} additionalClassName + * Get a color-icon HTML for a scope. + * + * @param {string} scope A scope. + * @param {boolean} [noWhiteSpace=false] No white space. + * @param {string} [additionalClassName] An additional class-name. * @returns {string} */ getScopeColorIconHtml: function (scope, noWhiteSpace, additionalClassName) { @@ -481,8 +635,10 @@ define('view-helper', ['lib!marked', 'lib!dompurify'], function (marked, DOMPuri }, /** - * @param {type} text - * @param {Object} options + * Sanitize HTML. + * + * @param {type} text HTML. + * @param {Object} [options] Options. * @returns {string} */ sanitizeHtml: function (text, options) { @@ -490,7 +646,9 @@ define('view-helper', ['lib!marked', 'lib!dompurify'], function (marked, DOMPuri }, /** - * @param {string} value + * Moderately sanitize HTML. + * + * @param {string} value HTML. * @returns {string} */ moderateSanitizeHtml: function (value) { @@ -527,7 +685,9 @@ define('view-helper', ['lib!marked', 'lib!dompurify'], function (marked, DOMPuri }, /** - * @param {string} html + * Strip event handlers in HTML. + * + * @param {string} html HTML. * @returns {string} */ stripEventHandlersInHtml: function (html) { @@ -566,11 +726,11 @@ define('view-helper', ['lib!marked', 'lib!dompurify'], function (marked, DOMPuri continue; } - if (!lastQuote && html[j - 1] === "=" && (html[j] === "'" || html[j] === '"')){ + if (!lastQuote && html[j - 1] === "=" && (html[j] === "'" || html[j] === '"')) { lastQuote = html[j]; } - if (!lastQuote && html[j - 2] === " " && html[j - 1] === "o" && html[j] === "n"){ + if (!lastQuote && html[j - 2] === " " && html[j - 1] === "o" && html[j] === "n") { strip = j - 2; } @@ -585,7 +745,9 @@ define('view-helper', ['lib!marked', 'lib!dompurify'], function (marked, DOMPuri }, /** - * @param {$} $el + * Calculate a content container height. + * + * @param {$} $el Element. * @returns {number} */ calculateContentContainerHeight: function ($el) { @@ -615,9 +777,11 @@ define('view-helper', ['lib!marked', 'lib!dompurify'], function (marked, DOMPuri }, /** - * @param {Espo.View} view - * @param {string} type - * @param {string} scope + * Process view-setup-handlers. + * + * @param {module:view} view A view. + * @param {string} type A view-setup-handler type. + * @param {string} [scope] A scope. */ processSetupHandlers: function (view, type, scope) { scope = scope || view.scope; @@ -655,6 +819,22 @@ define('view-helper', ['lib!marked', 'lib!dompurify'], function (marked, DOMPuri view.wait(promise); } }, + + /** + * @deprecated Use `transformMarkdownText`. + * @internal Used in extensions. + */ + transfromMarkdownText: function (text, options) { + this.transformMarkdownText(text, options); + }, + + /** + * @deprecated Use `transformMarkdownInlineText`. + * @internal Used in extensions. + */ + transfromMarkdownInlineText: function (text) { + return this.transformMarkdownInlineText(text); + }, }); return ViewHelper; diff --git a/client/src/view.js b/client/src/view.js index 9842bed7bc..ac5ebc5fa1 100644 --- a/client/src/view.js +++ b/client/src/view.js @@ -113,16 +113,16 @@ define('view', [], function () { }, /** - * Get the view-helper. + * Get a view-helper. * - * @returns {module:view-helper} + * @returns {module:view-helper.Class} */ getHelper: function () { return this._helper; }, /** - * Get the current user. + * Get a current user. * * @returns {module:models/user.Class} */ @@ -477,7 +477,7 @@ define('view', [], function () { } if (message) { - message = this.getHelper().transfromMarkdownText(message, {linksInNewTab: true}).toString(); + message = this.getHelper().transformMarkdownText(message, {linksInNewTab: true}).toString(); } let confirmText = o.confirmText || this.translate('Yes'); diff --git a/client/src/views/admin/complex-expression/modals/add-function.js b/client/src/views/admin/complex-expression/modals/add-function.js index 463cbe0796..99780daa36 100644 --- a/client/src/views/admin/complex-expression/modals/add-function.js +++ b/client/src/views/admin/complex-expression/modals/add-function.js @@ -45,7 +45,7 @@ define('views/admin/complex-expression/modals/add-function', ['views/modal', 'mo data: function () { var text = this.translate('formulaFunctions', 'messages', 'Admin') .replace('{documentationUrl}', this.documentationUrl); - text = this.getHelper().transfromMarkdownText(text, {linksInNewTab: true}).toString(); + text = this.getHelper().transformMarkdownText(text, {linksInNewTab: true}).toString(); return { functionDataList: this.functionDataList, diff --git a/client/src/views/admin/field-manager/modals/add-field.js b/client/src/views/admin/field-manager/modals/add-field.js index 8652eda8b2..7dc772c313 100644 --- a/client/src/views/admin/field-manager/modals/add-field.js +++ b/client/src/views/admin/field-manager/modals/add-field.js @@ -83,10 +83,11 @@ define('views/admin/field-manager/modals/add-field', 'views/modal', function (De if (text === type) { $el.addClass('hidden'); + return; } - var text = this.getHelper().transfromMarkdownText(text, {linksInNewTab: true}).toString(); + text = this.getHelper().transformMarkdownText(text, {linksInNewTab: true}).toString(); Espo.Ui.popover($el, { content: text, diff --git a/client/src/views/admin/formula/modals/add-function.js b/client/src/views/admin/formula/modals/add-function.js index b8c3a0e648..101dcb0381 100644 --- a/client/src/views/admin/formula/modals/add-function.js +++ b/client/src/views/admin/formula/modals/add-function.js @@ -45,7 +45,7 @@ define('views/admin/formula/modals/add-function', ['views/modal', 'model'], func data: function () { var text = this.translate('formulaFunctions', 'messages', 'Admin') .replace('{documentationUrl}', this.documentationUrl); - text = this.getHelper().transfromMarkdownText(text, {linksInNewTab: true}).toString(); + text = this.getHelper().transformMarkdownText(text, {linksInNewTab: true}).toString(); return { functionDataList: this.functionDataList, diff --git a/client/src/views/fields/base.js b/client/src/views/fields/base.js index c01f8fd829..9b34973e15 100644 --- a/client/src/views/fields/base.js +++ b/client/src/views/fields/base.js @@ -34,49 +34,58 @@ define('views/fields/base', ['view'], function (Dep) { * @todo Document all options. * @todo Document events. * - * @class Espo.Views.Fields.Base - * @extends Espo.View + * @class + * @name Class + * @extends module:view.Class + * @memberOf module:views/fields/base */ - return Dep.extend(/** @lends Espo.Views.Fields.Base */ { + return Dep.extend(/** @lends module:views/fields/base.Class# */ { /** * A field type. + * * @type {string} */ type: 'base', /** * List mode template. + * * @type {string} */ listTemplate: 'fields/base/list', /** * List-link mode template. + * * @type {string} */ listLinkTemplate: 'fields/base/list-link', /** * Detail mode template. + * * @type {string} */ detailTemplate: 'fields/base/detail', /** * Edit mode template. + * * @type {string} */ editTemplate: 'fields/base/edit', /** * Search mode template. + * * @type {string} */ searchTemplate: 'fields/base/search', /** * A validation list. There should be a `validate{Name}` method for each item. + * * @type {Array.} */ validations: ['required'], @@ -93,68 +102,89 @@ define('views/fields/base', ['view'], function (Dep) { /** * A field name. + * * @type {string} */ name: null, /** + * Definitions. + * * @type {Object} */ defs: null, /** * Field params. + * * @type {Object} */ params: null, /** * A mode. + * * @type {string} */ mode: null, /** * Search params. + * * @type {Object|null} */ searchParams: null, + /** + * @private + */ _timeout: null, /** * Inline edit disabled. + * * @type {boolean} */ inlineEditDisabled: false, /** * Field is disabled. + * * @type {boolean} */ disabled: false, /** * Field is read-only. + * * @type {boolean} */ readOnly: false, + /** + * @type {string[]|null} + */ attributeList: null, /** * Attribute values before edit. + * * @type {Object} */ initialAttributes: null, VALIDATION_POPOVER_TIMEOUT: 3000, + /** + * @type [Function] + * @internal + */ validateCallback: null, /** - * A record helper. - * @type {Espo.ViewRecordHelper} + * A record-view helper. + * + * @type {module:record-view-helper.Class} */ recordHelper: null, @@ -713,7 +743,8 @@ define('views/fields/base', ['view'], function (Dep) { } tooltipText = tooltipText || this.translate(this.name, 'tooltips', this.model.name) || ''; - tooltipText = this.getHelper().transfromMarkdownText(tooltipText, {linksInNewTab: true}).toString(); + tooltipText = this.getHelper() + .transformMarkdownText(tooltipText, {linksInNewTab: true}).toString(); let hidePopover = () => { $('body').off('click.popover-' + this.cid); diff --git a/client/src/views/list.js b/client/src/views/list.js index 6db7248a40..bba511477b 100644 --- a/client/src/views/list.js +++ b/client/src/views/list.js @@ -26,7 +26,8 @@ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ -define('views/list', ['views/main', 'search-manager'], function (Dep, SearchManager) { +define('views/list', ['views/main', 'search-manager'], +function (Dep, /** typeof module:search-manager.Class */SearchManager) { /** * A list view page. @@ -409,7 +410,6 @@ define('views/list', ['views/main', 'search-manager'], function (Dep, SearchMana ); searchManager.scope = this.scope; - searchManager.loadStored(); collection.where = searchManager.getWhere(); diff --git a/client/src/views/stream/panel.js b/client/src/views/stream/panel.js index c850ac8038..56d7363182 100644 --- a/client/src/views/stream/panel.js +++ b/client/src/views/stream/panel.js @@ -482,10 +482,10 @@ define('views/stream/panel', ['views/record/panels/relationship', 'lib!Textcompl var $a = this.$el.find('.buttons-panel a.stream-post-info'); - var message = this.getHelper().transfromMarkdownInlineText( + var message = this.getHelper().transformMarkdownInlineText( this.translate('infoMention', 'messages', 'Stream') ) + '

' + - this.getHelper().transfromMarkdownInlineText( + this.getHelper().transformMarkdownInlineText( this.translate('infoSyntax', 'messages', 'Stream') + ':' ) + '
'; diff --git a/client/src/views/user/record/edit.js b/client/src/views/user/record/edit.js index fa8e3f9b86..614f4b32c3 100644 --- a/client/src/views/user/record/edit.js +++ b/client/src/views/user/record/edit.js @@ -270,7 +270,7 @@ define('views/user/record/edit', ['views/record/edit', 'views/user/record/detail var msg = this.translate('setupSmtpBefore', 'messages', 'User').replace('{url}', '#Admin/outboundEmails'); - msg = this.getHelper().transfromMarkdownInlineText(msg); + msg = this.getHelper().transformMarkdownInlineText(msg); return msg; },