tabs ref
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2024 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* 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.
|
||||
************************************************************************/
|
||||
|
||||
export default class TabsHelper {
|
||||
|
||||
/**
|
||||
* @param {import('models/settings').default} config
|
||||
* @param {import('models/preferences').default} preferences
|
||||
* @param {import('models/user').default} user
|
||||
* @param {import('acl-manager').default} acl
|
||||
* @param {import('metadata').default} metadata
|
||||
*/
|
||||
constructor(config, preferences, user, acl, metadata) {
|
||||
/** @private */
|
||||
this.config = config;
|
||||
/** @private */
|
||||
this.preferences = preferences;
|
||||
/** @private */
|
||||
this.user = user;
|
||||
/** @private */
|
||||
this.acl = acl;
|
||||
/** @private */
|
||||
this.metadata = metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the tab list.
|
||||
*
|
||||
* @return {(Object|string)[]}
|
||||
*/
|
||||
getTabList() {
|
||||
let tabList = this.preferences.get('useCustomTabList') && !this.preferences.get('addCustomTabs') ?
|
||||
this.preferences.get('tabList') :
|
||||
this.config.get('tabList');
|
||||
|
||||
if (this.preferences.get('useCustomTabList') && this.preferences.get('addCustomTabs')) {
|
||||
tabList = [
|
||||
...tabList,
|
||||
...(this.preferences.get('tabList') || []),
|
||||
];
|
||||
}
|
||||
|
||||
return Espo.Utils.cloneDeep(tabList) || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Is a tab a divider.
|
||||
*
|
||||
* @param {string|{type?: string}} item
|
||||
*/
|
||||
isTabDivider(item) {
|
||||
return typeof item === 'object' && item.type === 'divider';
|
||||
}
|
||||
|
||||
/**
|
||||
* Is a tab more-delimiter.
|
||||
*
|
||||
* @param {string|{type?: string}} item
|
||||
*/
|
||||
isTabMoreDelimiter(item) {
|
||||
return item === '_delimiter_' || item === '_delimiter-ext_';
|
||||
}
|
||||
|
||||
/**
|
||||
* Is a tab a URL.
|
||||
*
|
||||
* @param {string|{type?: string}} item
|
||||
*/
|
||||
isTabUrl(item) {
|
||||
return typeof item === 'object' && item.type === 'url';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record|string} item
|
||||
* @return {boolean}
|
||||
*/
|
||||
checkTabAccess(item) {
|
||||
if (this.isTabUrl(item)) {
|
||||
if (item.onlyAdmin && !this.user.isAdmin()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!item.aclScope) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.acl.check(item.aclScope);
|
||||
}
|
||||
|
||||
if (item === 'Home' || this.isTabMoreDelimiter(item)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @type {Record<string, {disabled?: boolean, acl?: boolean, tabAclPermission?: string}>} */
|
||||
const scopes = this.metadata.get('scopes') || {};
|
||||
|
||||
if (!scopes[item]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const defs = scopes[item] || {};
|
||||
|
||||
if (defs.disabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (defs.acl) {
|
||||
return this.acl.check(item);
|
||||
}
|
||||
|
||||
if (defs.tabAclPermission) {
|
||||
const level = this.acl.getPermissionLevel(defs.tabAclPermission);
|
||||
|
||||
return level && level !== 'no';
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -28,11 +28,16 @@
|
||||
|
||||
import View from 'view';
|
||||
import $ from 'jquery';
|
||||
import TabsHelper from 'helpers/site/tabs';
|
||||
|
||||
class NavbarSiteView extends View {
|
||||
|
||||
template = 'site/navbar'
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {string|false|null}
|
||||
*/
|
||||
currentTab = null
|
||||
|
||||
events = {
|
||||
@@ -337,18 +342,7 @@ class NavbarSiteView extends View {
|
||||
* @return {(Object|string)[]}
|
||||
*/
|
||||
getTabList() {
|
||||
let tabList = this.getPreferences().get('useCustomTabList') && !this.getPreferences().get('addCustomTabs') ?
|
||||
this.getPreferences().get('tabList') :
|
||||
this.getConfig().get('tabList');
|
||||
|
||||
if (this.getPreferences().get('useCustomTabList') && this.getPreferences().get('addCustomTabs')) {
|
||||
tabList = [
|
||||
...tabList,
|
||||
...(this.getPreferences().get('tabList') || []),
|
||||
];
|
||||
}
|
||||
|
||||
tabList = Espo.Utils.cloneDeep(tabList || []);
|
||||
const tabList = this.tabsHelper.getTabList();
|
||||
|
||||
if (this.isSide()) {
|
||||
tabList.unshift('Home');
|
||||
@@ -386,6 +380,15 @@ class NavbarSiteView extends View {
|
||||
this.selectTab(false);
|
||||
});
|
||||
|
||||
/** @private */
|
||||
this.tabsHelper = new TabsHelper(
|
||||
this.getConfig(),
|
||||
this.getPreferences(),
|
||||
this.getUser(),
|
||||
this.getAcl(),
|
||||
this.getMetadata()
|
||||
);
|
||||
|
||||
const itemDefs = this.getMetadata().get('app.clientNavbar.items') || {};
|
||||
|
||||
/** @type {string[]} */
|
||||
@@ -500,52 +503,8 @@ class NavbarSiteView extends View {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record|string} item
|
||||
* @return {boolean}
|
||||
* @private
|
||||
*/
|
||||
filterTabItem(item) {
|
||||
if (typeof item === 'object' && item.type === 'url') {
|
||||
if (item.onlyAdmin && !this.getUser().isAdmin()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!item.aclScope) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.getAcl().check(item.aclScope);
|
||||
}
|
||||
|
||||
if (['Home', '_delimiter_', '_delimiter-ext_'].includes(item)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const scopes = this.getMetadata().get('scopes') || {};
|
||||
|
||||
if (!scopes[item]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const defs = /** @type {{disabled?: boolean, acl?: boolean, tabAclPermission?: string}} */
|
||||
scopes[item] || {};
|
||||
|
||||
if (defs.disabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (defs.acl) {
|
||||
return this.getAcl().check(item);
|
||||
}
|
||||
|
||||
if (defs.tabAclPermission) {
|
||||
const level = this.getAcl().getPermissionLevel(defs.tabAclPermission);
|
||||
|
||||
return level && level !== 'no';
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
setupGlobalSearch() {
|
||||
let isAvailable = false;
|
||||
|
||||
@@ -567,6 +526,9 @@ class NavbarSiteView extends View {
|
||||
this.itemList = this.itemList.filter(it => it !== 'globalSearch');
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
adjustTop() {
|
||||
const smallScreenWidth = this.getThemeManager().getParam('screenWidthXs');
|
||||
const navbarHeight = this.getNavbarHeight();
|
||||
@@ -852,6 +814,9 @@ class NavbarSiteView extends View {
|
||||
this.$body.css('minHeight', minHeight + 'px');
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
adjustBodyMinHeightTop() {
|
||||
let minHeight = this.getNavbarHeight();
|
||||
|
||||
@@ -1026,19 +991,10 @@ class NavbarSiteView extends View {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setupTabDefsList() {
|
||||
function isMoreDelimiter(item) {
|
||||
return item === '_delimiter_' || item === '_delimiter-ext_';
|
||||
}
|
||||
|
||||
function isDivider(item) {
|
||||
return typeof item === 'object' && item.type === 'divider';
|
||||
}
|
||||
|
||||
function isUrl(item) {
|
||||
return typeof item === 'object' && item.type === 'url';
|
||||
}
|
||||
|
||||
/** @type {{url: string, name: string}[]} */
|
||||
this.urlList = [];
|
||||
|
||||
@@ -1050,10 +1006,10 @@ class NavbarSiteView extends View {
|
||||
}
|
||||
|
||||
if (typeof item !== 'object') {
|
||||
return this.filterTabItem(item);
|
||||
return this.tabsHelper.checkTabAccess(item);
|
||||
}
|
||||
|
||||
if (isDivider(item)) {
|
||||
if (this.tabsHelper.isTabDivider(item)) {
|
||||
if (!this.isSide()) {
|
||||
return false;
|
||||
}
|
||||
@@ -1065,21 +1021,21 @@ class NavbarSiteView extends View {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isUrl(item)) {
|
||||
return this.filterTabItem(item);
|
||||
if (this.tabsHelper.isTabUrl(item)) {
|
||||
return this.tabsHelper.checkTabAccess(item);
|
||||
}
|
||||
|
||||
/** @type {(Record|string)[]} */
|
||||
let itemList = (item.itemList || []).filter((item) => {
|
||||
if (isDivider(item)) {
|
||||
let itemList = (item.itemList || []).filter(item => {
|
||||
if (this.tabsHelper.isTabDivider(item)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.filterTabItem(item);
|
||||
return this.tabsHelper.checkTabAccess(item);
|
||||
});
|
||||
|
||||
itemList = itemList.filter((item, i) => {
|
||||
if (!isDivider(item)) {
|
||||
if (!this.tabsHelper.isTabDivider(item)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1089,7 +1045,7 @@ class NavbarSiteView extends View {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isDivider(nextItem)) {
|
||||
if (this.tabsHelper.isTabDivider(nextItem)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1097,7 +1053,7 @@ class NavbarSiteView extends View {
|
||||
});
|
||||
|
||||
itemList = itemList.filter((item, i) => {
|
||||
if (!isDivider(item)) {
|
||||
if (!this.tabsHelper.isTabDivider(item)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1119,11 +1075,11 @@ class NavbarSiteView extends View {
|
||||
const nextItem = this.tabList[i + 1];
|
||||
const prevItem = this.tabList[i - 1];
|
||||
|
||||
if (isMoreDelimiter(item)) {
|
||||
if (this.tabsHelper.isTabMoreDelimiter(item)) {
|
||||
moreIsMet = true;
|
||||
}
|
||||
|
||||
if (!isDivider(item)) {
|
||||
if (!this.tabsHelper.isTabDivider(item)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1131,11 +1087,11 @@ class NavbarSiteView extends View {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isDivider(nextItem)) {
|
||||
if (this.tabsHelper.isTabDivider(nextItem)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isDivider(prevItem) && isMoreDelimiter(nextItem) && moreIsMet) {
|
||||
if (this.tabsHelper.isTabDivider(prevItem) && this.tabsHelper.isTabMoreDelimiter(nextItem) && moreIsMet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1161,7 +1117,7 @@ class NavbarSiteView extends View {
|
||||
};
|
||||
|
||||
this.tabList.forEach((tab, i) => {
|
||||
if (isMoreDelimiter(tab)) {
|
||||
if (this.tabsHelper.isTabMoreDelimiter(tab)) {
|
||||
if (!vars.moreIsMet) {
|
||||
vars.moreIsMet = true;
|
||||
|
||||
@@ -1192,6 +1148,29 @@ class NavbarSiteView extends View {
|
||||
this.tabDefsList = tabDefsList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {{
|
||||
* colorsDisabled: boolean,
|
||||
* tabIconsDisabled: boolean,
|
||||
* }} params
|
||||
* @param {Object|string} tab
|
||||
* @param {number} i
|
||||
* @param {Object} vars
|
||||
* @return {{
|
||||
* isAfterShowMore: boolean,
|
||||
* isDivider: boolean,
|
||||
* color: null,
|
||||
* link: string,
|
||||
* name: string,
|
||||
* isInMore: boolean,
|
||||
* shortLabel: string,
|
||||
* label: string,
|
||||
* isGroup: boolean,
|
||||
* aClassName: string,
|
||||
* iconClass: null
|
||||
* }}
|
||||
*/
|
||||
prepareTabItemDefs(params, tab, i, vars) {
|
||||
let label;
|
||||
let link;
|
||||
@@ -1215,8 +1194,7 @@ class NavbarSiteView extends View {
|
||||
if (tab === 'Home') {
|
||||
label = this.getLanguage().translate(tab);
|
||||
link = '#';
|
||||
}
|
||||
else if (typeof tab === 'object' && tab.type === 'divider') {
|
||||
} else if (typeof tab === 'object' && tab.type === 'divider') {
|
||||
isDivider = true;
|
||||
label = tab.text;
|
||||
aClassName = 'nav-divider-text';
|
||||
@@ -1225,8 +1203,7 @@ class NavbarSiteView extends View {
|
||||
if (label) {
|
||||
label = translateLabel(label);
|
||||
}
|
||||
}
|
||||
else if (typeof tab === 'object' && tab.type === 'url') {
|
||||
} else if (typeof tab === 'object' && tab.type === 'url') {
|
||||
isUrl = true;
|
||||
label = tab.text || '#';
|
||||
name = 'url-' + i;
|
||||
@@ -1239,8 +1216,7 @@ class NavbarSiteView extends View {
|
||||
}
|
||||
|
||||
this.urlList.push({name: name, url: link});
|
||||
}
|
||||
else if (typeof tab === 'object') {
|
||||
} else if (typeof tab === 'object') {
|
||||
isGroup = true;
|
||||
|
||||
label = tab.text || '';
|
||||
@@ -1256,8 +1232,7 @@ class NavbarSiteView extends View {
|
||||
if (label) {
|
||||
label = translateLabel(label);
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
label = this.getLanguage().translate(tab, 'scopeNamesPlural');
|
||||
link = '#' + tab;
|
||||
}
|
||||
@@ -1315,7 +1290,9 @@ class NavbarSiteView extends View {
|
||||
* @property {true} [divider]
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setupMenu() {
|
||||
let avatarHtml = this.getHelper().getAvatarHtml(this.getUser().id, 'small', 20, 'avatar-link');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user