metadata navbar menu
This commit is contained in:
@@ -18,5 +18,46 @@
|
||||
"order": 15,
|
||||
"disabled": false
|
||||
}
|
||||
},
|
||||
"menuItems": {
|
||||
"admin": {
|
||||
"order": 0,
|
||||
"groupIndex": 1,
|
||||
"link": "#Admin",
|
||||
"labelTranslation": "Global.labels.Administration",
|
||||
"accessDataList": [
|
||||
{
|
||||
"isAdminOnly": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"preferences": {
|
||||
"order": 1,
|
||||
"groupIndex": 1,
|
||||
"link": "#Preferences",
|
||||
"labelTranslation": "Global.labels.Preferences"
|
||||
},
|
||||
"lastViewed": {
|
||||
"order": 0,
|
||||
"groupIndex": 5,
|
||||
"link": "#LastViewed",
|
||||
"labelTranslation": "Global.scopeNamesPlural.LastViewed",
|
||||
"configCheck": "!actionHistoryDisabled",
|
||||
"handler": "handlers/navbar-menu",
|
||||
"actionFunction": "lastViewed"
|
||||
},
|
||||
"about": {
|
||||
"order": 0,
|
||||
"groupIndex": 10,
|
||||
"link": "#LastViewed",
|
||||
"labelTranslation": "Global.labels.About"
|
||||
},
|
||||
"logout": {
|
||||
"order": 1,
|
||||
"groupIndex": 10,
|
||||
"labelTranslation": "Global.labels.Log Out",
|
||||
"handler": "handlers/navbar-menu",
|
||||
"actionFunction": "logout"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,11 +195,11 @@
|
||||
{{#each menuDataList}}
|
||||
{{#unless divider}}
|
||||
<li><a
|
||||
{{#if link}}href="{{link}}"{{else}}role="button"{{/if}}
|
||||
tabindex="0"
|
||||
class="nav-link{{#if action}} action{{/if}}"{{#if action}}
|
||||
data-action="{{action}}"{{/if}}
|
||||
>{{#if html}}{{{html}}}{{else}}{{label}}{{/if}}</a></li>
|
||||
{{#if name}}data-name="{{name}}"{{/if}}
|
||||
{{#if link}}href="{{link}}"{{else}}role="button"{{/if}}
|
||||
tabindex="0"
|
||||
class="nav-link{{#if handler}} action{{/if}}"
|
||||
>{{#if html}}{{{html}}}{{else}}{{label}}{{/if}}</a></li>
|
||||
{{else}}
|
||||
<li class="divider"></li>
|
||||
{{/unless}}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/************************************************************************
|
||||
* 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.
|
||||
************************************************************************/
|
||||
|
||||
import ActionHandler from 'action-handler';
|
||||
|
||||
class NavbarMenuHandler extends ActionHandler {
|
||||
|
||||
logout() {
|
||||
this.view.getRouter().logout();
|
||||
}
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
lastViewed() {
|
||||
Espo.Ui.notify(' ... ');
|
||||
|
||||
this.view.createView('dialog', 'views/modals/last-viewed', {}, view => {
|
||||
view.render();
|
||||
Espo.Ui.notify(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default NavbarMenuHandler;
|
||||
@@ -64,10 +64,6 @@ class NavbarSiteView extends View {
|
||||
this.switchSideMenu();
|
||||
},
|
||||
/** @this NavbarSiteView */
|
||||
'click a.action': function (e) {
|
||||
Espo.Utils.handleAction(this, e.originalEvent, e.currentTarget);
|
||||
},
|
||||
/** @this NavbarSiteView */
|
||||
'click [data-action="toggleCollapsable"]': function () {
|
||||
this.toggleCollapsable();
|
||||
},
|
||||
@@ -91,7 +87,7 @@ class NavbarSiteView extends View {
|
||||
tabDefsList1: this.tabDefsList.filter(item => !item.isInMore),
|
||||
tabDefsList2: this.tabDefsList.filter(item => item.isInMore),
|
||||
title: this.options.title,
|
||||
menuDataList: this.getMenuDataList(),
|
||||
menuDataList: this.menuDataList,
|
||||
userId: this.getUser().id,
|
||||
logoSrc: this.getLogoSrc(),
|
||||
itemDataList: this.getItemDataList(),
|
||||
@@ -362,6 +358,24 @@ class NavbarSiteView extends View {
|
||||
}
|
||||
|
||||
setup() {
|
||||
this.addHandler('click', 'a.action', (/** MouseEvent */event, target) => {
|
||||
let actionData;
|
||||
const name = target.dataset.name;
|
||||
|
||||
if (name) {
|
||||
const item = this.menuDataList.find(it => it.name === name);
|
||||
|
||||
if (item.handler && item.actionFunction) {
|
||||
actionData = {
|
||||
handler: item.handler,
|
||||
actionFunction: item.actionFunction,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Espo.Utils.handleAction(this, event, target, actionData);
|
||||
});
|
||||
|
||||
this.getRouter().on('routed', (e) => {
|
||||
if (e.controller) {
|
||||
this.selectTab(e.controller);
|
||||
@@ -424,6 +438,8 @@ class NavbarSiteView extends View {
|
||||
|
||||
this.$body.removeClass('has-navbar');
|
||||
});
|
||||
|
||||
this.setupMenu();
|
||||
}
|
||||
|
||||
getItemDataList() {
|
||||
@@ -1292,82 +1308,96 @@ class NavbarSiteView extends View {
|
||||
/**
|
||||
* @typedef {Object} MenuDataItem
|
||||
* @property {string} [link]
|
||||
* @property {string} [name]
|
||||
* @property {string} [html]
|
||||
* @property {string} [handler]
|
||||
* @property {string} [actionFunction]
|
||||
* @property {true} [divider]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @return {MenuDataItem[]}
|
||||
*/
|
||||
getMenuDataList() {
|
||||
|
||||
setupMenu() {
|
||||
let avatarHtml = this.getHelper().getAvatarHtml(this.getUser().id, 'small', 20, 'avatar-link');
|
||||
|
||||
if (avatarHtml) {
|
||||
avatarHtml += ' ';
|
||||
}
|
||||
|
||||
/** @type {MenuDataItem[]}*/
|
||||
let list = [
|
||||
/** @type {MenuDataItem[]} */
|
||||
this.menuDataList = [
|
||||
{
|
||||
link: '#User/view/' + this.getUser().id,
|
||||
link: `#User/view/${this.getUser().id}`,
|
||||
html: avatarHtml + this.getHelper().escapeString(this.getUser().get('name')),
|
||||
},
|
||||
{divider: true}
|
||||
];
|
||||
|
||||
if (this.getUser().isAdmin()) {
|
||||
list.push({
|
||||
link: '#Admin',
|
||||
label: this.getLanguage().translate('Administration'),
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @type {Record<string, {
|
||||
* order?: number,
|
||||
* groupIndex?: number,
|
||||
* link?: string,
|
||||
* labelTranslation?: string,
|
||||
* configCheck?: string,
|
||||
* disabled:? boolean,
|
||||
* handler?: string,
|
||||
* actionFunction?: string,
|
||||
* accessDataList?: module:utils~AccessDefs[],
|
||||
* }>} items
|
||||
*/
|
||||
const items = this.getMetadata().get('app.clientNavbar.menuItems') || {};
|
||||
|
||||
list.push({
|
||||
link: '#Preferences',
|
||||
label: this.getLanguage().translate('Preferences'),
|
||||
const nameList = Object.keys(items).sort((n1, n2) => {
|
||||
const o1 = items[n1].order;
|
||||
const o2 = items[n2].order;
|
||||
|
||||
const g1 = items[n1].groupIndex;
|
||||
const g2 = items[n2].groupIndex;
|
||||
|
||||
if (g2 === g1) {
|
||||
return o1 - o2;
|
||||
}
|
||||
|
||||
return g1 - g2;
|
||||
});
|
||||
|
||||
if (!this.getConfig().get('actionHistoryDisabled')) {
|
||||
list.push({divider: true});
|
||||
let currentGroup = 0;
|
||||
|
||||
list.push({
|
||||
action: 'showLastViewed',
|
||||
link: '#LastViewed',
|
||||
label: this.getLanguage().translate('LastViewed', 'scopeNamesPlural'),
|
||||
for (const name of nameList) {
|
||||
const item = items[name];
|
||||
|
||||
if (item.groupIndex !== currentGroup) {
|
||||
currentGroup = item.groupIndex;
|
||||
|
||||
this.menuDataList.push({divider: true});
|
||||
}
|
||||
|
||||
if (item.disabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
item.configCheck &&
|
||||
!Espo.Utils.checkActionAvailability(this.getHelper(), item)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
item.accessDataList &&
|
||||
!Espo.Utils.checkAccessDataList(item.accessDataList, this.getAcl(), this.getUser())
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.menuDataList.push({
|
||||
name: name,
|
||||
link: item.link,
|
||||
label: this.getLanguage().translatePath(item.labelTranslation),
|
||||
handler: item.handler,
|
||||
actionFunction: item.actionFunction,
|
||||
});
|
||||
}
|
||||
|
||||
list = list.concat([
|
||||
{
|
||||
divider: true
|
||||
},
|
||||
{
|
||||
link: '#About',
|
||||
label: this.getLanguage().translate('About')
|
||||
},
|
||||
{
|
||||
action: 'logout',
|
||||
label: this.getLanguage().translate('Log Out')
|
||||
},
|
||||
]);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
actionLogout() {
|
||||
this.getRouter().logout();
|
||||
}
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
actionShowLastViewed() {
|
||||
Espo.Ui.notify(' ... ');
|
||||
|
||||
this.createView('dialog', 'views/modals/last-viewed', {}, (view) => {
|
||||
view.render();
|
||||
|
||||
Espo.Ui.notify(false);
|
||||
});
|
||||
}
|
||||
|
||||
showMoreTabs() {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"items": {
|
||||
"description": "Navbar item definitions",
|
||||
"description": "Navbar item definitions.",
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"description": "An item.",
|
||||
@@ -30,6 +30,51 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"menuItems": {
|
||||
"description": "Menu item definitions.",
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"description": "An item.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"labelTranslation": {
|
||||
"type": "string",
|
||||
"description": "A label translation path."
|
||||
},
|
||||
"link": {
|
||||
"type": "string",
|
||||
"description": "A link."
|
||||
},
|
||||
"order": {
|
||||
"type": "integer",
|
||||
"description": "An order position."
|
||||
},
|
||||
"groupIndex": {
|
||||
"type": "integer",
|
||||
"description": "A group index. Groups are separated by a divider."
|
||||
},
|
||||
"disabled": {
|
||||
"type": "boolean",
|
||||
"description": "An item will be hidden."
|
||||
},
|
||||
"handler": {
|
||||
"type": "string",
|
||||
"description": "A handler class."
|
||||
},
|
||||
"actionFunction": {
|
||||
"type": "string",
|
||||
"description": "An action function in the handler."
|
||||
},
|
||||
"configCheck": {
|
||||
"type": "string",
|
||||
"description": "A config path to check. Path items are separated by a dot. If a config value is not empty, then the action is allowed. The `!` prefix reverses the check."
|
||||
},
|
||||
"accessDataList": {
|
||||
"$ref": "../clientDefs.json#/definitions/accessDataList"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user