mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-09-13 20:04:45 +08:00
955 lines
30 KiB
JavaScript
955 lines
30 KiB
JavaScript
/*
|
|
* luci-theme-kucat
|
|
* Copyright (C) 2022-2026 The Sirpdboy <herboy2008@gmail.com>
|
|
*
|
|
* Licensed to the public under the Apache License 2.0
|
|
*/
|
|
|
|
'use strict';
|
|
'require baseclass';
|
|
'require ui';
|
|
'require fs';
|
|
|
|
// 抑制控制台警告
|
|
(function() {
|
|
const originalConsoleWarn = console.warn;
|
|
const originalConsoleError = console.error;
|
|
const originalConsoleLog = console.log;
|
|
|
|
console.warn = function(...args) {
|
|
const message = args.join(' ');
|
|
const ignoredWarnings = [
|
|
'X-Frame-Options may only be set',
|
|
'Added non-passive event listener',
|
|
'Direct use XHR() is deprecated'
|
|
];
|
|
|
|
if (ignoredWarnings.some(w => message.includes(w))) {
|
|
return;
|
|
}
|
|
originalConsoleWarn.apply(console, args);
|
|
};
|
|
|
|
console.debug = function(...args) {
|
|
if (localStorage.getItem('debug') === 'true') {
|
|
originalConsoleLog.apply(console, ['[DEBUG]', ...args]);
|
|
}
|
|
};
|
|
})();
|
|
|
|
/**
|
|
* Lightweight animation utilities for menu interactions
|
|
*/
|
|
const KucatAnimations = {
|
|
durations: {
|
|
fast: 100,
|
|
normal: 200,
|
|
slow: 400
|
|
},
|
|
|
|
slideDown: function(element, duration = 'normal') {
|
|
if (!element) return;
|
|
|
|
const animDuration = typeof duration === 'string' ?
|
|
this.durations[duration] || this.durations.normal :
|
|
duration;
|
|
|
|
const originalDisplay = element.style.display;
|
|
const originalHeight = element.style.height;
|
|
|
|
element.style.display = 'block';
|
|
element.style.overflow = 'hidden';
|
|
element.style.height = '0px';
|
|
element.style.transition = `height ${animDuration}ms ease-out`;
|
|
|
|
requestAnimationFrame(() => {
|
|
element.style.height = element.scrollHeight + 'px';
|
|
});
|
|
|
|
setTimeout(() => {
|
|
element.style.height = originalHeight;
|
|
element.style.overflow = '';
|
|
element.style.transition = '';
|
|
if (originalDisplay === 'none') {
|
|
element.style.display = 'block';
|
|
}
|
|
}, animDuration);
|
|
},
|
|
|
|
slideUp: function(element, duration = 'normal') {
|
|
if (!element) return;
|
|
|
|
const animDuration = typeof duration === 'string' ?
|
|
this.durations[duration] || this.durations.normal :
|
|
duration;
|
|
|
|
const originalHeight = element.style.height;
|
|
|
|
element.style.overflow = 'hidden';
|
|
element.style.height = element.scrollHeight + 'px';
|
|
element.style.transition = `height ${animDuration}ms ease-out`;
|
|
|
|
requestAnimationFrame(() => {
|
|
element.style.height = '0px';
|
|
});
|
|
|
|
setTimeout(() => {
|
|
element.style.display = 'none';
|
|
element.style.height = originalHeight;
|
|
element.style.overflow = '';
|
|
element.style.transition = '';
|
|
}, animDuration);
|
|
}
|
|
};
|
|
|
|
return baseclass.extend({
|
|
// Menu icons mapping
|
|
menuIcons: {
|
|
'status': 'status',
|
|
'system': 'system',
|
|
'services': 'services',
|
|
'network': 'network',
|
|
'netwizard': 'netwizard',
|
|
'docker': 'docker',
|
|
'vpn': 'vpn',
|
|
'nas': 'nas',
|
|
'control': 'control'
|
|
},
|
|
defaultIcon: 'default',
|
|
|
|
// Menu categories - 将从配置加载
|
|
menuCategories: {
|
|
basic: []
|
|
},
|
|
|
|
currentCategory: 'basic',
|
|
menuRendered: false,
|
|
|
|
__init__: function() {
|
|
try {
|
|
this.loadSavedCategory();
|
|
// 先加载菜单配置,再加载菜单树
|
|
this.loadMenuConfig().then(() => {
|
|
ui.menu.load().then(L.bind(this.render, this));
|
|
});
|
|
} catch (e) {
|
|
console.debug('Init error:', e);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 从UCI配置加载basic菜单列表
|
|
*/
|
|
loadMenuConfig: function() {
|
|
return new Promise((resolve) => {
|
|
// 尝试从配置文件读取
|
|
fs.read('/etc/config/kucat').then((content) => {
|
|
var basicMenus = [];
|
|
var lines = content.split('\n');
|
|
lines.forEach(line => {
|
|
var match = line.match(/list item ['"](.+)['"]/);
|
|
if (match) {
|
|
basicMenus.push(match[1]);
|
|
}
|
|
});
|
|
|
|
if (basicMenus.length > 0) {
|
|
this.menuCategories.basic = basicMenus;
|
|
} else {
|
|
this.menuCategories.basic = this.getDefaultBasicMenus();
|
|
}
|
|
resolve();
|
|
}).catch(() => {
|
|
// 文件不存在,使用默认配置
|
|
this.menuCategories.basic = this.getDefaultBasicMenus();
|
|
resolve();
|
|
});
|
|
});
|
|
},
|
|
|
|
/**
|
|
* 获取默认basic菜单列表
|
|
*/
|
|
getDefaultBasicMenus: function() {
|
|
return [
|
|
'status/overview',
|
|
'status/realtime',
|
|
'netwizard',
|
|
'system/system',
|
|
'system/admin',
|
|
'system/ttyd',
|
|
'system/advancedplus',
|
|
'system/ota',
|
|
'system/kucat-config',
|
|
'services/AdGuardHome',
|
|
'control/eqosplus',
|
|
'control/timecontrol',
|
|
'control/watchdog',
|
|
'control/taskplan',
|
|
'network/firewall',
|
|
'network/netspeedtest',
|
|
'system/partexp'
|
|
];
|
|
},
|
|
|
|
/**
|
|
* Load saved category from localStorage
|
|
*/
|
|
loadSavedCategory: function() {
|
|
try {
|
|
const savedCategory = localStorage.getItem('luci-menu-category');
|
|
if (savedCategory === 'basic' || savedCategory === 'allmenu') {
|
|
this.currentCategory = savedCategory;
|
|
}
|
|
} catch (e) {
|
|
console.debug('Load category error:', e);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Render the complete interface
|
|
*/
|
|
render: function(tree) {
|
|
try {
|
|
var node = tree,
|
|
url = '';
|
|
|
|
// Render mode menu (top level)
|
|
this.renderModeMenu(tree);
|
|
|
|
// 先隐藏所有菜单项,避免闪现
|
|
this.hideAllMenus();
|
|
|
|
// Render tab menu for deep navigation
|
|
if (L.env.dispatchpath.length >= 3) {
|
|
for (var i = 0; i < 3 && node; i++) {
|
|
node = node.children[L.env.dispatchpath[i]];
|
|
url = url + (url ? '/' : '') + L.env.dispatchpath[i];
|
|
}
|
|
|
|
if (node) {
|
|
this.renderTabMenu(node, url);
|
|
}
|
|
}
|
|
|
|
// Initialize sidebar functionality
|
|
this.initSidebar();
|
|
|
|
// Hide loading indicator
|
|
this.hideLoading();
|
|
|
|
// Initialize responsive behavior
|
|
this.initResponsive();
|
|
|
|
// 添加切换按钮
|
|
setTimeout(() => {
|
|
try {
|
|
this.addCategorySwitchButton();
|
|
} catch (e) {
|
|
console.debug('Add category button error:', e);
|
|
}
|
|
}, 100);
|
|
|
|
this.menuRendered = true;
|
|
|
|
// 应用分类过滤
|
|
setTimeout(() => {
|
|
try {
|
|
this.applyCategoryFilter(this.currentCategory);
|
|
} catch (e) {
|
|
console.debug('Apply category filter error:', e);
|
|
}
|
|
}, 150);
|
|
|
|
} catch (e) {
|
|
console.debug('Render error:', e);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 隐藏所有菜单项,避免闪现
|
|
*/
|
|
hideAllMenus: function() {
|
|
try {
|
|
const existingStyle = document.getElementById('menu-hide-style');
|
|
if (existingStyle) {
|
|
existingStyle.remove();
|
|
}
|
|
|
|
const style = document.createElement('style');
|
|
style.id = 'menu-hide-style';
|
|
style.textContent = '#mainmenu .nav > li { display: none !important; }';
|
|
document.head.appendChild(style);
|
|
} catch (e) {
|
|
console.debug('Hide menus error:', e);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 显示菜单项
|
|
*/
|
|
showMenus: function() {
|
|
try {
|
|
const hideStyle = document.getElementById('menu-hide-style');
|
|
if (hideStyle) {
|
|
hideStyle.remove();
|
|
}
|
|
} catch (e) {
|
|
console.debug('Show menus error:', e);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 添加分类切换按钮
|
|
*/
|
|
addCategorySwitchButton: function() {
|
|
try {
|
|
const logoutContainer = document.querySelector('.logout-container') ||
|
|
document.querySelector('.pull-right') ||
|
|
document.querySelector('header .right') ||
|
|
document.querySelector('.main-right .pull-right');
|
|
|
|
if (logoutContainer) {
|
|
this.insertSwitchButton(logoutContainer);
|
|
}
|
|
} catch (e) {
|
|
console.debug('Add category button error:', e);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 插入切换按钮到指定容器
|
|
*/
|
|
insertSwitchButton: function(container) {
|
|
if (!container) return;
|
|
|
|
try {
|
|
if (container.querySelector('.category-switch')) {
|
|
return;
|
|
}
|
|
|
|
const switchBtn = E('a', {
|
|
'href': '#',
|
|
'class': 'category-switch',
|
|
'title': this.currentCategory === 'basic' ? _('Switch to Full Menus') : _('Switch to Custom Menus')
|
|
}, [
|
|
E('span', {
|
|
'class': 'switch-icon ' + (this.currentCategory === 'basic' ? 'icon-allmenu' : 'icon-basic')
|
|
})
|
|
]);
|
|
|
|
switchBtn.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
this.toggleCategory();
|
|
});
|
|
|
|
const logoutBtn = container.querySelector('a[href*="logout"], a[href*="sysauth"]');
|
|
|
|
if (logoutBtn) {
|
|
container.insertBefore(switchBtn, logoutBtn);
|
|
} else {
|
|
container.appendChild(switchBtn);
|
|
}
|
|
} catch (e) {
|
|
console.debug('Insert button error:', e);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 切换分类
|
|
*/
|
|
toggleCategory: function() {
|
|
try {
|
|
const newCategory = this.currentCategory === 'basic' ? 'allmenu' : 'basic';
|
|
this.switchCategory(newCategory);
|
|
|
|
const switchBtn = document.querySelector('.category-switch');
|
|
if (switchBtn) {
|
|
const iconSpan = switchBtn.querySelector('.switch-icon');
|
|
const textSpan = switchBtn.querySelector('.switch-text');
|
|
|
|
if (iconSpan) {
|
|
iconSpan.className = 'switch-icon ' + (newCategory === 'basic' ? 'icon-allmenu' : 'icon-basic');
|
|
}
|
|
|
|
switchBtn.title = newCategory === 'basic' ? _('Switch to Full Menus') : _('Switch to Custom Menus');
|
|
}
|
|
} catch (e) {
|
|
console.debug('Toggle category error:', e);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Switch between Basic and allmenu categories
|
|
*/
|
|
switchCategory: function(category) {
|
|
if (category === this.currentCategory) return;
|
|
|
|
try {
|
|
this.currentCategory = category;
|
|
localStorage.setItem('luci-menu-category', category);
|
|
|
|
this.applyCategoryFilter(category);
|
|
} catch (e) {
|
|
console.debug('Switch category error:', e);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 判断是否为basic菜单
|
|
*/
|
|
isBasicMenu: function(menuPath) {
|
|
try {
|
|
if (!menuPath || !this.menuCategories || !this.menuCategories.basic) return false;
|
|
|
|
// 检查完整路径是否在basic列表中
|
|
return this.menuCategories.basic.some(basicPath => {
|
|
return basicPath === menuPath;
|
|
});
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Apply category filter to menu items
|
|
*/
|
|
applyCategoryFilter: function(category) {
|
|
try {
|
|
this.showMenus();
|
|
|
|
|
|
// 收集所有菜单项的路径信息
|
|
const allMenuItems = document.querySelectorAll('#mainmenu .nav > li, #mainmenu .slide-menu li');
|
|
const menuPaths = new Map();
|
|
|
|
allMenuItems.forEach(item => {
|
|
try {
|
|
const link = item.querySelector('a');
|
|
if (!link) return;
|
|
|
|
const href = link.getAttribute('href');
|
|
if (!href) return;
|
|
|
|
// 从href提取路径
|
|
const pathMatch = href.match(/\/admin\/(.+)/);
|
|
if (pathMatch) {
|
|
const fullPath = pathMatch[1];
|
|
menuPaths.set(item, fullPath);
|
|
}
|
|
} catch (e) {}
|
|
});
|
|
|
|
if (category === 'basic') {
|
|
// Basic模式:只显示basic列表中的菜单
|
|
allMenuItems.forEach(item => {
|
|
try {
|
|
const fullPath = menuPaths.get(item);
|
|
if (!fullPath) {
|
|
item.style.display = 'none';
|
|
return;
|
|
}
|
|
|
|
const isBasic = this.isBasicMenu(fullPath);
|
|
item.style.display = isBasic ? 'block' : 'none';
|
|
|
|
} catch (e) {}
|
|
});
|
|
|
|
// 确保父菜单在子菜单可见时也显示
|
|
this.ensureParentMenusVisible();
|
|
|
|
} else {
|
|
// allmenu模式:显示所有菜单
|
|
allMenuItems.forEach(item => {
|
|
item.style.display = 'block';
|
|
});
|
|
}
|
|
|
|
// 确保当前页面对应的菜单项可见并展开
|
|
this.ensureCurrentMenuVisible();
|
|
|
|
} catch (e) {
|
|
console.debug('Apply category filter error:', e);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 确保父菜单在子菜单可见时也显示
|
|
*/
|
|
ensureParentMenusVisible: function() {
|
|
try {
|
|
const slideMenus = document.querySelectorAll('#mainmenu .slide-menu');
|
|
|
|
slideMenus.forEach(slideMenu => {
|
|
// 检查这个下拉菜单是否有任何可见的子项
|
|
const hasVisibleChildren = Array.from(slideMenu.children).some(child =>
|
|
child.style.display !== 'none'
|
|
);
|
|
|
|
if (hasVisibleChildren) {
|
|
// 如果有可见的子项,确保父菜单可见
|
|
const parentLi = slideMenu.closest('li.slide');
|
|
if (parentLi) {
|
|
parentLi.style.display = 'block';
|
|
}
|
|
}
|
|
});
|
|
} catch (e) {
|
|
console.debug('Ensure parent menus visible error:', e);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 确保当前页面对应的菜单项可见
|
|
*/
|
|
ensureCurrentMenuVisible: function() {
|
|
try {
|
|
const currentPath = window.location.pathname;
|
|
const menuItems = document.querySelectorAll('#mainmenu .nav > li, #mainmenu .slide-menu li');
|
|
|
|
menuItems.forEach(item => {
|
|
const link = item.querySelector('a');
|
|
if (!link) return;
|
|
|
|
const href = link.getAttribute('href');
|
|
if (href && currentPath.includes(href)) {
|
|
// 确保当前菜单可见
|
|
item.style.display = 'block';
|
|
|
|
// 如果是子菜单,确保所有父菜单也可见
|
|
let parent = item.parentElement?.closest('li.slide');
|
|
while (parent) {
|
|
parent.style.display = 'block';
|
|
parent = parent.parentElement?.closest('li.slide');
|
|
}
|
|
|
|
// 如果是下拉菜单,展开它
|
|
if (item.classList.contains('slide')) {
|
|
item.classList.add('active');
|
|
const slideMenu = item.querySelector('.slide-menu');
|
|
if (slideMenu) {
|
|
slideMenu.style.display = 'block';
|
|
}
|
|
}
|
|
}
|
|
});
|
|
} catch (e) {
|
|
console.debug('Ensure menu visible error:', e);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Extract menu name from link
|
|
*/
|
|
extractMenuName: function(link) {
|
|
try {
|
|
const href = link.getAttribute('href');
|
|
if (!href) return '';
|
|
|
|
const matches = href.match(/\/([^\/]+)$/);
|
|
return matches ? matches[1] : '';
|
|
} catch (e) {
|
|
return '';
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Get menu icon
|
|
*/
|
|
getMenuIcon: function(menuName) {
|
|
return this.menuIcons[menuName] || this.defaultIcon;
|
|
},
|
|
|
|
/**
|
|
* Generate menu icon class
|
|
*/
|
|
generateMenuIconClass: function(menuName) {
|
|
const iconName = this.getMenuIcon(menuName);
|
|
return `menu-icon-${iconName}`;
|
|
},
|
|
|
|
/**
|
|
* Initialize sidebar toggle functionality
|
|
*/
|
|
initSidebar: function() {
|
|
try {
|
|
var showSide = document.querySelector('.showSide');
|
|
var darkMask = document.querySelector('.darkMask');
|
|
var mainRight = document.querySelector('.main-right');
|
|
|
|
if (showSide) {
|
|
showSide.addEventListener('click', L.bind(this.toggleSidebar, this));
|
|
}
|
|
|
|
if (darkMask) {
|
|
darkMask.addEventListener('click', L.bind(this.toggleSidebar, this));
|
|
}
|
|
|
|
if (mainRight) {
|
|
mainRight.addEventListener('click', L.bind(this.handleMainClick, this));
|
|
}
|
|
} catch (e) {
|
|
console.debug('Init sidebar error:', e);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Initialize responsive behavior
|
|
*/
|
|
initResponsive: function() {
|
|
try {
|
|
if (window.innerWidth <= 920) {
|
|
this.closeSidebar();
|
|
}
|
|
|
|
let resizeTimeout;
|
|
window.addEventListener('resize', L.bind(function() {
|
|
clearTimeout(resizeTimeout);
|
|
resizeTimeout = setTimeout(() => {
|
|
try {
|
|
this.handleResize();
|
|
} catch (e) {
|
|
console.debug('Resize handler error:', e);
|
|
}
|
|
}, 100);
|
|
}, this));
|
|
} catch (e) {
|
|
console.debug('Init responsive error:', e);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Handle window resize events
|
|
*/
|
|
handleResize: function() {
|
|
try {
|
|
var width = window.innerWidth;
|
|
var mainLeft = document.querySelector('.main-left');
|
|
var darkMask = document.querySelector('.darkMask');
|
|
var switchBtn = document.querySelector('.category-switch');
|
|
|
|
if (width > 920) {
|
|
if (mainLeft) {
|
|
mainLeft.style.width = '';
|
|
mainLeft.style.visibility = 'visible';
|
|
}
|
|
if (darkMask) {
|
|
darkMask.style.visibility = 'hidden';
|
|
darkMask.style.opacity = '0';
|
|
}
|
|
if (switchBtn) {
|
|
switchBtn.style.display = 'inline-flex';
|
|
}
|
|
this.applyCategoryFilter(this.currentCategory);
|
|
} else {
|
|
this.closeSidebar();
|
|
|
|
}
|
|
} catch (e) {
|
|
console.debug('Handle resize error:', e);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Toggle sidebar visibility (mobile)
|
|
*/
|
|
toggleSidebar: function() {
|
|
try {
|
|
var mainLeft = document.querySelector('.main-left');
|
|
var darkMask = document.querySelector('.darkMask');
|
|
var mainRight = document.querySelector('.main-right');
|
|
|
|
if (!mainLeft || !darkMask) return;
|
|
|
|
var isOpen = mainLeft.style.width !== '0px' && mainLeft.style.width !== '0';
|
|
|
|
if (isOpen) {
|
|
this.closeSidebar();
|
|
} else {
|
|
this.openSidebar();
|
|
}
|
|
} catch (e) {
|
|
console.debug('Toggle sidebar error:', e);
|
|
}
|
|
},
|
|
|
|
handleMainClick: function(ev) {
|
|
try {
|
|
if (window.innerWidth <= 920 && document.body.classList.contains('sidebar-open')) {
|
|
if (!ev.target.closest('.showSide')) {
|
|
this.closeSidebar();
|
|
ev.stopPropagation();
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.debug('Main click error:', e);
|
|
}
|
|
},
|
|
|
|
openSidebar: function() {
|
|
try {
|
|
var mainLeft = document.querySelector('.main-left');
|
|
var darkMask = document.querySelector('.darkMask');
|
|
var mainRight = document.querySelector('.main-right');
|
|
|
|
if (mainLeft) {
|
|
mainLeft.style.width = '15rem';
|
|
mainLeft.style.visibility = 'visible';
|
|
}
|
|
if (darkMask) {
|
|
darkMask.style.visibility = 'visible';
|
|
darkMask.style.opacity = '1';
|
|
}
|
|
if (mainRight) {
|
|
mainRight.style.overflowY = 'hidden';
|
|
}
|
|
|
|
document.body.classList.add('sidebar-open');
|
|
} catch (e) {
|
|
console.debug('Open sidebar error:', e);
|
|
}
|
|
},
|
|
|
|
closeSidebar: function() {
|
|
try {
|
|
var mainLeft = document.querySelector('.main-left');
|
|
var darkMask = document.querySelector('.darkMask');
|
|
var mainRight = document.querySelector('.main-right');
|
|
|
|
if (mainLeft) {
|
|
mainLeft.style.width = '0';
|
|
mainLeft.style.visibility = 'hidden';
|
|
}
|
|
if (darkMask) {
|
|
darkMask.style.visibility = 'hidden';
|
|
darkMask.style.opacity = '0';
|
|
}
|
|
if (mainRight) {
|
|
mainRight.style.overflowY = 'auto';
|
|
}
|
|
|
|
document.body.classList.remove('sidebar-open');
|
|
} catch (e) {
|
|
console.debug('Close sidebar error:', e);
|
|
}
|
|
},
|
|
|
|
hideLoading: function() {
|
|
try {
|
|
var loading = document.querySelector('.main > .loading');
|
|
if (loading) {
|
|
loading.style.opacity = '0';
|
|
loading.style.visibility = 'hidden';
|
|
|
|
setTimeout(() => {
|
|
if (loading && loading.parentNode) {
|
|
loading.parentNode.removeChild(loading);
|
|
}
|
|
}, 300);
|
|
}
|
|
} catch (e) {
|
|
console.debug('Hide loading error:', e);
|
|
}
|
|
},
|
|
|
|
handleMenuExpand: function(ev) {
|
|
try {
|
|
var target = ev.target;
|
|
var slideItem = target.parentNode;
|
|
var slideMenu = target.nextElementSibling;
|
|
|
|
ev.preventDefault();
|
|
ev.stopPropagation();
|
|
|
|
if (!slideMenu || !slideMenu.classList.contains('slide-menu')) {
|
|
return;
|
|
}
|
|
|
|
var isActive = slideItem.classList.contains('active');
|
|
var allSlideItems = document.querySelectorAll('.main .main-left .nav > li.slide');
|
|
|
|
allSlideItems.forEach(function(item) {
|
|
if (item !== slideItem) {
|
|
var otherMenu = item.querySelector('.slide-menu');
|
|
if (otherMenu && otherMenu.style.display !== 'none') {
|
|
item.classList.remove('active');
|
|
item.querySelector('a.menu').classList.remove('active');
|
|
KucatAnimations.slideUp(otherMenu, 'fast');
|
|
}
|
|
}
|
|
});
|
|
|
|
if (isActive) {
|
|
slideItem.classList.remove('active');
|
|
target.classList.remove('active');
|
|
KucatAnimations.slideUp(slideMenu, 'fast');
|
|
} else {
|
|
slideItem.classList.add('active');
|
|
target.classList.add('active');
|
|
KucatAnimations.slideDown(slideMenu, 'fast');
|
|
}
|
|
|
|
target.blur();
|
|
} catch (e) {
|
|
console.debug('Menu expand error:', e);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Render main menu with category support
|
|
*/
|
|
renderMainMenu: function(tree, url, level) {
|
|
try {
|
|
var currentLevel = (level || 0) + 1;
|
|
var ul = E('ul', { 'class': level ? 'slide-menu' : 'nav' });
|
|
var children = ui.menu.getChildren(tree);
|
|
|
|
if (children.length === 0 || currentLevel > 2) {
|
|
return E([]);
|
|
}
|
|
|
|
for (var i = 0; i < children.length; i++) {
|
|
var child = children[i];
|
|
var isActive = this.isMenuItemActive(child, tree, currentLevel);
|
|
var submenu = this.renderMainMenu(child, url + '/' + child.name, currentLevel);
|
|
var hasChildren = submenu.children.length > 0;
|
|
|
|
var slideClass = hasChildren ? 'slide' : '';
|
|
var menuClass = hasChildren ? 'menu' : '';
|
|
|
|
if (isActive) {
|
|
slideClass += ' active';
|
|
menuClass += ' active';
|
|
ul.classList.add('active');
|
|
}
|
|
|
|
if (currentLevel === 1) {
|
|
const iconClass = this.generateMenuIconClass(child.name);
|
|
menuClass += ' ' + iconClass;
|
|
}
|
|
|
|
var menuItem = E('li', {
|
|
'class': slideClass.trim()
|
|
}, [
|
|
E('a', {
|
|
'href': L.url(url, child.name),
|
|
'click': (currentLevel === 1 && hasChildren) ?
|
|
ui.createHandlerFn(this, 'handleMenuExpand') : null,
|
|
'class': menuClass.trim(),
|
|
'data-title': child.title.replace(/ /g, '_'),
|
|
}, [_(child.title)]),
|
|
submenu
|
|
]);
|
|
|
|
ul.appendChild(menuItem);
|
|
}
|
|
|
|
if (currentLevel === 1) {
|
|
var container = document.querySelector('#mainmenu');
|
|
if (container) {
|
|
container.appendChild(ul);
|
|
container.style.display = '';
|
|
}
|
|
}
|
|
|
|
return ul;
|
|
} catch (e) {
|
|
console.debug('Render main menu error:', e);
|
|
return E([]);
|
|
}
|
|
},
|
|
|
|
isMenuItemActive: function(child, parent, level) {
|
|
try {
|
|
return (L.env.dispatchpath[level] === child.name) &&
|
|
(L.env.dispatchpath[level - 1] === parent.name);
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
},
|
|
|
|
renderModeMenu: function(tree) {
|
|
try {
|
|
var ul = document.querySelector('#modemenu');
|
|
var children = ui.menu.getChildren(tree);
|
|
|
|
if (!ul) return;
|
|
|
|
for (var i = 0; i < children.length; i++) {
|
|
var isActive = (L.env.requestpath.length ?
|
|
children[i].name == L.env.requestpath[0] : i == 0);
|
|
|
|
ul.appendChild(E('li', {}, [
|
|
E('a', {
|
|
'href': L.url(children[i].name),
|
|
'class': isActive ? 'active' : null
|
|
}, [_(children[i].title)])
|
|
]));
|
|
|
|
if (isActive) {
|
|
this.renderMainMenu(children[i], children[i].name);
|
|
}
|
|
|
|
if (i > 0 && i < children.length - 1) {
|
|
ul.appendChild(E('li', {'class': 'divider'}, [E('span')]));
|
|
}
|
|
}
|
|
|
|
if (children.length > 1) {
|
|
ul.parentElement.style.display = '';
|
|
}
|
|
} catch (e) {
|
|
console.debug('Render mode menu error:', e);
|
|
}
|
|
},
|
|
|
|
renderTabMenu: function(tree, url, level) {
|
|
try {
|
|
var container = document.querySelector('#tabmenu');
|
|
var currentLevel = (level || 0) + 1;
|
|
var ul = E('ul', { 'class': 'tabs' });
|
|
var children = ui.menu.getChildren(tree);
|
|
var activeNode = null;
|
|
|
|
if (children.length === 0 || !container) {
|
|
return E([]);
|
|
}
|
|
|
|
for (var i = 0; i < children.length; i++) {
|
|
var child = children[i];
|
|
var isActive = (L.env.dispatchpath[currentLevel + 2] === child.name);
|
|
var activeClass = isActive ? ' active' : '';
|
|
var className = 'tabmenu-item-%s %s'.format(child.name, activeClass);
|
|
|
|
ul.appendChild(E('li', {
|
|
'class': className.trim()
|
|
}, [
|
|
E('a', {
|
|
'href': L.url(url, child.name)
|
|
}, [_(child.title)])
|
|
]));
|
|
|
|
if (isActive) {
|
|
activeNode = child;
|
|
}
|
|
}
|
|
|
|
container.appendChild(ul);
|
|
container.style.display = '';
|
|
|
|
if (activeNode) {
|
|
container.appendChild(this.renderTabMenu(
|
|
activeNode,
|
|
url + '/' + activeNode.name,
|
|
currentLevel
|
|
));
|
|
}
|
|
|
|
return ul;
|
|
} catch (e) {
|
|
console.debug('Render tab menu error:', e);
|
|
return E([]);
|
|
}
|
|
}
|
|
}); |