1440 lines
45 KiB
JavaScript
1440 lines
45 KiB
JavaScript
/* Init variables */
|
|
const apps =
|
|
{
|
|
/** @type {Record<string, ZentaoApp>} */
|
|
map: {},
|
|
/** @type {Record<string, ZentaoOpenedApp>} */
|
|
openedMap: {},
|
|
defaultCode: '',
|
|
zIndex: 10,
|
|
frameContent: null,
|
|
theme: null,
|
|
oldPages: new Set(oldPages)
|
|
};
|
|
|
|
const DEBUG = config.debug;
|
|
|
|
function getUrlID(url)
|
|
{
|
|
const info = $.parseLink(url);
|
|
return info.moduleName ? `${info.moduleName}-${info.methodName}` : '';
|
|
}
|
|
|
|
function showLog(code, name, moreTitles, trace, moreInfos)
|
|
{
|
|
const titles = ['%c HOME '];
|
|
const titleColors = ['color:#fff;font-weight:bold;background:#009688'];
|
|
if(code)
|
|
{
|
|
titles.push(`%c ${code} `);
|
|
titleColors.push('background:rgba(0,150,136,0.2);color:#009688;');
|
|
}
|
|
if(name)
|
|
{
|
|
titles.push(`%c ${name} `);
|
|
titleColors.push('color:#009688;font-weight:bold;');
|
|
}
|
|
if(!Array.isArray(moreTitles)) moreTitles = [moreTitles];
|
|
if(typeof moreTitles[0] === 'string' && (moreTitles[0].startsWith('success:') || moreTitles[0].startsWith('error:')))
|
|
{
|
|
const message = moreTitles.shift();
|
|
const [type, content] = message.split(':', 2);
|
|
titles.push(`%c ${content} `);
|
|
titleColors.push(`color:${type === 'error' ? '#f56c6c' : '#67c23a'};`);
|
|
}
|
|
if(trace || moreInfos)
|
|
{
|
|
console.groupCollapsed(titles.join(''), ...titleColors, ...moreTitles);
|
|
if(trace) console.trace(trace);
|
|
if(moreInfos)
|
|
{
|
|
if($.isPlainObject(moreInfos)) Object.keys(moreInfos).forEach((infoName) => console.log(infoName, moreInfos[infoName]));
|
|
else console.log(moreInfos);
|
|
}
|
|
console.groupEnd();
|
|
}
|
|
else
|
|
{
|
|
console.log(titles.join(''), ...titleColors, ...moreTitles);
|
|
}
|
|
}
|
|
|
|
function triggerAppEvent(code, event, args, options)
|
|
{
|
|
const app = apps.openedMap[code];
|
|
if(!app) return;
|
|
|
|
event = event.includes('.') ? event : `${event}.apps`;
|
|
if(DEBUG && (!options || options.silent !== true)) showLog(code, 'Event', event, {args});
|
|
if(!Array.isArray(args)) args = [args];
|
|
if(app.$app) app.$app.trigger(event, args);
|
|
try
|
|
{
|
|
const iframeWindow = app.iframe && app.iframe.contentWindow;
|
|
if(iframeWindow) return iframeWindow.$(iframeWindow.document).trigger(event, args);
|
|
}
|
|
catch(e){}
|
|
}
|
|
|
|
function isOldPage(url)
|
|
{
|
|
if(typeof url !== 'object') url = $.parseLink(url);
|
|
return apps.oldPages.has(url.moduleName.toLowerCase()) || apps.oldPages.has(`${url.moduleName}-${url.methodName}`.toLowerCase());
|
|
}
|
|
|
|
/**
|
|
* Open app
|
|
* @param {string} url
|
|
* @param {string|object} [code]
|
|
* @param {boolean|object} [options]
|
|
* @returns {ZentaoOpenedApp|undefined}
|
|
*/
|
|
function openApp(url, code, options)
|
|
{
|
|
if(!apps.frameContent)
|
|
{
|
|
setTimeout(() => {openApp(url, code, options);}, 100);
|
|
return;
|
|
}
|
|
const loadOptions = {};
|
|
if(typeof code === 'object') $.extend(loadOptions, code);
|
|
else if(code) loadOptions.code = code;
|
|
if(typeof options === 'boolean') loadOptions.forceReload = options;
|
|
else if(typeof options === 'object') $.extend(loadOptions, options);
|
|
options = loadOptions;
|
|
code = options.code;
|
|
let forceReload = options.forceReload !== false;
|
|
delete options.forceReload;
|
|
delete options.code;
|
|
|
|
if(!code)
|
|
{
|
|
if(apps.map[url])
|
|
{
|
|
code = url;
|
|
url = '';
|
|
}
|
|
else if(url)
|
|
{
|
|
url = url.replace('http:', window.location.protocol).replace('https:', window.location.protocol);
|
|
code = getAppCode(url);
|
|
}
|
|
if(!code) return openApp('my');
|
|
}
|
|
|
|
const app = apps.map[code];
|
|
if(!app)
|
|
{
|
|
if(DEBUG) showLog(code, 'APP NOT FOUND!', url, options);
|
|
return zui.Modal.alert(appNotFound);
|
|
}
|
|
|
|
/* Create iframe for app */
|
|
let openedApp = apps.openedMap[code];
|
|
if(!openedApp)
|
|
{
|
|
if(!url) url = app.url;
|
|
openedApp = $.extend({opened: true, url: url, zIndex: ++apps.zIndex, currentUrl: url}, app);
|
|
forceReload = false;
|
|
apps.openedMap[code] = openedApp;
|
|
|
|
const $iframe =
|
|
$([
|
|
'<iframe',
|
|
'id="appIframe-' + code + '"',
|
|
'name="app-' + code + '"',
|
|
'class="fade"',
|
|
'allowfullscreen="true"',
|
|
app.external ? 'src="' + url + '"' : '',
|
|
'frameborder="no"',
|
|
'allowtransparency="true"',
|
|
'scrolling="auto"',
|
|
'/>'
|
|
].join(' '));
|
|
const iframe = $iframe[0];
|
|
openedApp.iframe = iframe;
|
|
openedApp.$app = $('<div class="app-container loading" id="app-' + code + '"></div>')
|
|
.append($iframe)
|
|
.appendTo('#apps');
|
|
|
|
const finishLoad = () =>
|
|
{
|
|
$iframe.removeClass('loading').addClass('in');
|
|
setTimeout(() => {openedApp.$app.removeClass('loading');}, 300);
|
|
};
|
|
if(app.external)
|
|
{
|
|
$iframe.on('ready.app', () =>{openApp(url, code, options);});
|
|
}
|
|
else
|
|
{
|
|
const writeToDoc = () => {
|
|
iframe.contentDocument.open();
|
|
const html = apps.frameContent.replace('window.defaultAppUrl = ""', `window.defaultAppUrl = "${url}"`);
|
|
iframe.contentDocument.write(html);
|
|
iframe.contentDocument.close();
|
|
};
|
|
if(!iframe.contentDocument.body.children.length) setTimeout(() => writeToDoc(), 500);
|
|
}
|
|
iframe.onload = iframe.onreadystatechange = function(e)
|
|
{
|
|
try
|
|
{
|
|
iframe.contentWindow.$(iframe.contentDocument).one('pageload.app pagecaheload.app', finishLoad);
|
|
setTimeout(finishLoad, 10000);
|
|
}
|
|
catch(e){finishLoad()}
|
|
triggerAppEvent(openedApp.code, 'loadapp', [openedApp, e]);
|
|
};
|
|
openedApp.$app.show().css('z-index', openedApp.zIndex);
|
|
}
|
|
if(!url) url = openedApp.currentUrl;
|
|
|
|
/* Set tab cookie */
|
|
$.cookie.set('tab', code, {expires: config.cookieLife, path: config.webRoot});
|
|
|
|
/* Highlight on left menu */
|
|
const $menuNav = $('#menuMainNav,#menuMoreNav');
|
|
const $lastItem = $menuNav.find('li>a.active');
|
|
if($lastItem.data('app') !== code)
|
|
{
|
|
$lastItem.removeClass('active');
|
|
$menuNav.find('li[data-app="' + code + '"]>a').addClass('active');
|
|
openedApp.$app.trigger('showapp', openedApp);
|
|
}
|
|
|
|
/* Show and load app */
|
|
const isSameUrl = $.parseLink(openedApp.currentUrl).url === $.parseLink(url).url;
|
|
const needLoad = !isSameUrl || forceReload !== false;
|
|
if(needLoad)
|
|
{
|
|
const openFromHidden = openedApp.zIndex < apps.zIndex;
|
|
reloadApp(code, url, openFromHidden ? $.extend({loadingIndicatorDelay: '0s'}, options) : options);
|
|
openedApp.$app.toggleClass('open-from-hidden', openFromHidden);
|
|
}
|
|
else
|
|
{
|
|
updateApp(code, url, openedApp.currentTitle, 'show');
|
|
}
|
|
openedApp.zIndex = ++apps.zIndex;
|
|
openedApp.$app.show().css('z-index', openedApp.zIndex);
|
|
openedApp.getPageInfo = () => {
|
|
const getPageInfo = openedApp.iframe.contentWindow.getPageInfo;
|
|
return getPageInfo ? getPageInfo() : null;
|
|
};
|
|
openedApp.getPerfData = () => {
|
|
const getPerfData = openedApp.iframe.contentWindow.getPerfData;
|
|
return getPerfData ? getPerfData() : null;
|
|
};
|
|
|
|
/* Update on app tabs bar */
|
|
const $tabs = $('#appTabs');
|
|
let $tabItem = $('#appTab-' + code);
|
|
if(!$tabItem.length)
|
|
{
|
|
if (app.text === undefined) return false;
|
|
const $link= $('<a data-pos="tab"></a>')
|
|
.attr('data-app', code)
|
|
.addClass('show-in-app')
|
|
.append($('<span class="text"></span>').text(app.text));
|
|
$tabItem = $('<li class="nav-item"></li>')
|
|
.attr({'data-app': code, id: 'appTab-' + code})
|
|
.append($link)
|
|
.appendTo($tabs);
|
|
openedApp.$bar = $tabItem;
|
|
}
|
|
const $lastTab = $tabs.find('li>a.active');
|
|
if($lastTab.data('app') !== code)
|
|
{
|
|
$lastTab.removeClass('active');
|
|
$tabs.find('li[data-app="' + code + '"]>a').addClass('active');
|
|
}
|
|
|
|
if(DEBUG) showLog(code, 'Open', getUrlID(url), openedApp, {options, forceReload, needLoad});
|
|
triggerAppEvent(code, 'openapp', [openedApp, {load: needLoad}]);
|
|
|
|
return openedApp;
|
|
}
|
|
|
|
/**
|
|
* Show app
|
|
* @param {string} code
|
|
*/
|
|
function showApp(code)
|
|
{
|
|
return openApp('', code, false);
|
|
}
|
|
|
|
/**
|
|
* Reload app
|
|
* @param {string} code
|
|
* @param {string} url
|
|
* @param {object} options
|
|
*/
|
|
function reloadApp(code, url, options)
|
|
{
|
|
const app = apps.openedMap[code];
|
|
if(!app) return;
|
|
|
|
if(url === true) url = app.url;
|
|
else if(!url) url = app.currentUrl;
|
|
|
|
const iframe = app.iframe;
|
|
try
|
|
{
|
|
if(app.external) iframe.src = url;
|
|
else if(iframe.contentWindow.loadPage) iframe.contentWindow.loadPage(url, $.extend({updateHeading: true}, options));
|
|
else if(DEBUG) showLog(code, 'Reload', 'error:Cannot load page when iframe is not ready.', {options});
|
|
else console.error('[APPS]', 'Reload failed: Cannot load page when iframe is not ready.');
|
|
}
|
|
catch(error)
|
|
{
|
|
iframe.src = url;
|
|
}
|
|
|
|
app.currentUrl = url;
|
|
}
|
|
|
|
function updateApp(code, url, title, type)
|
|
{
|
|
const app = apps.openedMap[code];
|
|
if(!app || app.external) return;
|
|
|
|
const state = typeof code === 'object' ? code : {code: code, url: url, title: title, type: type};
|
|
const prevState = window.history.state;
|
|
const urlInfo = $.parseLink(state.url);
|
|
|
|
state.prev = prevState;
|
|
state.index = prevState ? prevState.index + 1 : 1;
|
|
state.path = urlInfo.methodName ? `${urlInfo.moduleName}-${urlInfo.methodName}` : '';
|
|
|
|
if(title)
|
|
{
|
|
document.title = title;
|
|
app.currentTitle = title;
|
|
}
|
|
|
|
if(prevState && prevState.code === code && prevState.url === url) return prevState;
|
|
|
|
app.currentUrl = url;
|
|
const urlParts = url.split('#');
|
|
const hash = urlParts.length > 1 ? urlParts[1] : '';
|
|
const browserUrl = getAppCode(urlParts[0]) !== code ? `${urlParts[0]}#${hash.length ? `${hash}&` : ''}app=${code}` : url;
|
|
window.history.pushState(state, title, browserUrl);
|
|
zui.store.session.set('lastOpenApp', {code, url});
|
|
triggerAppEvent(code, 'updateapp', [code, url, title, type]);
|
|
if(DEBUG) showLog(code, 'Update', title || getUrlID(url), state, {url, title, type});
|
|
return state;
|
|
}
|
|
|
|
/**
|
|
* Get last opened app
|
|
* @returns {object} The opened app info object
|
|
*/
|
|
function getLastApp()
|
|
{
|
|
let lastShowIndex = 0;
|
|
let lastApp = null;
|
|
$.each(apps.openedMap, function(_code, app)
|
|
{
|
|
if(lastShowIndex < app.zIndex && !app.closed)
|
|
{
|
|
lastShowIndex = app.zIndex;
|
|
lastApp = app;
|
|
}
|
|
});
|
|
return lastApp;
|
|
}
|
|
|
|
/** Get last opened app code. */
|
|
function getLastAppCode()
|
|
{
|
|
const lastApp = getLastApp();
|
|
return lastApp ? lastApp.code : null;
|
|
}
|
|
|
|
/**
|
|
* Close app
|
|
* @param {string} code
|
|
* @returns {ZentaoOpenedApp|undefined|false}
|
|
*/
|
|
function closeApp(code)
|
|
{
|
|
code = code || getLastAppCode();
|
|
const app = apps.openedMap[code];
|
|
if(!app) return;
|
|
|
|
const iframe = app.iframe;
|
|
if(iframe)
|
|
{
|
|
if(iframe && iframe.contentDocument && iframe.contentWindow && iframe.contentWindow.onCloseApp)
|
|
{
|
|
var result = iframe.contentWindow.onCloseApp();
|
|
if(result === false) return false;
|
|
}
|
|
}
|
|
|
|
$('#appTabs a.active[data-app="' + code + '"]').parent().remove();
|
|
|
|
app.closed = true;
|
|
app.$app.trigger('hideapp', app);
|
|
app.$app.remove();
|
|
app.$bar.remove();
|
|
|
|
hideApp(code);
|
|
delete apps.openedMap[code];
|
|
|
|
triggerAppEvent(code, 'closeapp', app);
|
|
return app;
|
|
}
|
|
|
|
/**
|
|
* Hide app
|
|
* @param {string} code
|
|
* @returns {ZentaoOpenedApp|undefined}
|
|
*/
|
|
function hideApp(code)
|
|
{
|
|
code = code || getLastAppCode();
|
|
const app = apps.openedMap[code];
|
|
if(!app) return;
|
|
|
|
$('#menuNav a.active[data-app="' + code + '"]').removeClass('active');
|
|
|
|
if(!app.closed) triggerAppEvent(code, 'hideapp', app);
|
|
|
|
app.$app.hide();
|
|
|
|
/* Active last app */
|
|
const lastApp = getLastApp(true) || getLastApp();
|
|
showApp(lastApp ? lastApp.code : apps.defaultCode);
|
|
return app;
|
|
}
|
|
|
|
/**
|
|
* Get app code from url
|
|
* @param {String} urlOrModuleName Url string
|
|
* @return {String}
|
|
*/
|
|
function getAppCode(urlOrModuleName, defaultCode)
|
|
{
|
|
var code = navGroup[urlOrModuleName];
|
|
if(code) return code;
|
|
|
|
var link = $.parseLink(urlOrModuleName);
|
|
if(!link.moduleName || link.isOnlyBody || (link.moduleName === 'index' && link.methodName === 'index')) return '';
|
|
|
|
if(link.hash && link.hash.indexOf('app=') === 0) return link.hash.substr(4);
|
|
|
|
/* Handling special situations */
|
|
var moduleName = link.moduleName;
|
|
var methodName = link.methodName;
|
|
var moduleMethodLower = (moduleName + '-' + methodName).toLowerCase();
|
|
|
|
if(moduleMethodLower === 'index-index') return 'my';
|
|
if(moduleMethodLower === 'search-buildindex') return 'admin';
|
|
|
|
if(moduleName === 'tutorial' && methodName === 'wizard')
|
|
{
|
|
moduleName = link.vars[0][1];
|
|
methodName = link.vars[1][1];
|
|
}
|
|
|
|
var methodLowerCase = methodName.toLowerCase();
|
|
if(moduleName === 'doc')
|
|
{
|
|
if(link.prj) return 'project';
|
|
|
|
if((link.params.from || link.params.$3) == 'product')
|
|
{
|
|
if(['objectlibs', 'showfiles', 'browse', 'view', 'edit', 'delete', 'create'].includes(methodLowerCase)) return 'product';
|
|
}
|
|
return 'doc';
|
|
}
|
|
if(['caselib', 'testreport', 'testsuite', 'testtask', 'testcase', 'bug', 'qa'].includes(moduleName))
|
|
{
|
|
return link.prj ? 'project' : 'qa';
|
|
}
|
|
if(moduleName === 'report')
|
|
{
|
|
if(['usereport', 'editreport', 'deletereport', 'custom'].includes(methodLowerCase) && link.params.from) return 'system';
|
|
if(methodName == 'projectsummary') return 'project';
|
|
return 'bi';
|
|
}
|
|
if(moduleName === 'story' && vision === 'lite') return 'project'
|
|
if(moduleName === 'testcase' && methodLowerCase === 'zerocase')
|
|
{
|
|
return link.params.from == 'project' ? 'project' : 'qa';
|
|
}
|
|
if(moduleName === 'execution' && methodLowerCase === 'all')
|
|
{
|
|
return (link.params.from || link.params.$3) == 'project' ? 'project' : 'execution';
|
|
}
|
|
if(moduleName === 'issue' || moduleName === 'risk' || moduleName === 'opportunity' || moduleName === 'pssp' || moduleName === 'auditplan' || moduleName === 'meeting' || moduleName === 'nc')
|
|
{
|
|
if(link.params.$2 == 'my' || link.params.from == 'my') return 'my';
|
|
if(link.params.$2 == 'project' || link.params.from == 'project') return 'project';
|
|
if(link.params.$2 == 'execution' || link.params.from == 'execution') return 'execution';
|
|
}
|
|
if(moduleName === 'product')
|
|
{
|
|
if(methodLowerCase === 'create' && (link.params.programID || link.params.$1)) return 'program';
|
|
if(methodLowerCase === 'edit' && (link.params.programID || link.params.$4)) return 'program';
|
|
if(methodLowerCase === 'batchedit') return 'program';
|
|
var moduleGroup = link.params.moduleGroup ? link.params.moduleGroup : link.params.$2;
|
|
if(methodLowerCase === 'showerrornone' && moduleGroup) return moduleGroup;
|
|
}
|
|
if(moduleName === 'stakeholder')
|
|
{
|
|
if(methodLowerCase === 'create' && (link.params.programID || link.params.$1)) return 'program';
|
|
}
|
|
if(moduleName === 'user')
|
|
{
|
|
if(['todo', 'todocalendar', 'effortcalendar', 'effort', 'task', 'todo', 'story', 'bug', 'testtask', 'testcase', 'execution', 'dynamic', 'profile', 'view', 'issue', 'risk'].includes(methodLowerCase)) return 'system';
|
|
}
|
|
if(moduleName === 'my')
|
|
{
|
|
if(['team'].includes(methodLowerCase)) return 'system';
|
|
}
|
|
if(moduleName === 'company') if(methodLowerCase == 'browse') return 'admin';
|
|
if(moduleName === 'opportunity' || moduleName === 'risk' || moduleName == 'trainplan') if(methodLowerCase == 'view') return 'project';
|
|
if(moduleName === 'tree')
|
|
{
|
|
if(methodLowerCase === 'browse')
|
|
{
|
|
var viewType = link.params.view || link.params.$2;
|
|
if(['bug', 'case', 'caselib'].includes(viewType)) return link.params.$5 === 'project' ? 'project' : 'qa';
|
|
|
|
if(viewType === 'doc' && (link.params.from === 'product' || link.params.$5 == 'product')) return 'product';
|
|
if(viewType === 'doc' && (link.params.from === 'project' || link.params.$5 == 'project')) return 'project';
|
|
if(viewType === 'doc') return 'doc';
|
|
if(viewType === 'story') return 'product';
|
|
if(viewType === 'host') return 'admin';
|
|
if(viewType === 'ticket') return 'feedback';
|
|
}
|
|
if(methodLowerCase === 'browsetask') return 'execution';
|
|
if(methodLowerCase === 'browsegroup') return 'bi';
|
|
}
|
|
if(moduleName === 'ai')
|
|
{
|
|
/* Match admin ai pages. */
|
|
if(['adminindex', 'prompts', 'promptview', 'conversations', 'models', 'modelcreate', 'modelview', 'modeledit', 'editmodel', 'promptassignrole', 'promptselectdatasource', 'promptsetpurpose', 'promptsettargetform', 'promptfinalize', 'promptedit', 'miniprograms', 'createminiprogram', 'editminiprogram', 'configuredminiprogram', 'editminiprogramcategory', 'miniprogramview', 'assistants', 'assistantcreate', 'assistantview', 'assistantedit'].includes(methodLowerCase)) return 'admin';
|
|
}
|
|
|
|
code = navGroup[moduleName] || moduleName || urlOrModuleName;
|
|
return apps.map[code] ? code : defaultCode;
|
|
}
|
|
|
|
/**
|
|
* Search history and go back to specified path.
|
|
*
|
|
* @param {string} target Back target, can be app name or module-method path.
|
|
* @param {string} url Fallback url.
|
|
* @param {object} startState Start state.
|
|
* @returns {void}
|
|
*/
|
|
function goBack(target, url, startState)
|
|
{
|
|
const currentState = window.history.state;
|
|
const preState = currentState && currentState.prev;
|
|
if(DEBUG) showLog(null, 'Go back', target, null, {url, startState, currentState, preState});
|
|
if(target && startState && currentState && preState)
|
|
{
|
|
startState = startState.prev || (currentState && currentState.prev);
|
|
if($.apps.openedMap[target])
|
|
{
|
|
let state = startState;
|
|
while(state && state.code !== target) state = state.prev;
|
|
if(state && state.code === target)
|
|
{
|
|
if(state.index === preState.index)
|
|
{
|
|
if(url) return openApp(url, target, false);
|
|
return window.history.back();
|
|
}
|
|
return openApp(state.url, state.code, false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const pathSet = new Set(target.toLowerCase().split(','));
|
|
let state = startState;
|
|
while(state && state.path && !pathSet.has(state.path.toLowerCase())) state = state.prev;
|
|
if(state && pathSet.has(state.path.toLowerCase()))
|
|
{
|
|
return openApp(state.url, state.code, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(url) return openApp(url);
|
|
if(target)
|
|
{
|
|
if($.apps.openedMap[target]) return openApp(target);
|
|
if(target.includes('-'))
|
|
{
|
|
const parts = target.split(',').shift().split('-');
|
|
return openApp($.createLink(parts[0], parts[1]));
|
|
}
|
|
}
|
|
|
|
window.history.back();
|
|
}
|
|
|
|
/**
|
|
* Logout current user.
|
|
*
|
|
* @param {string} url
|
|
*/
|
|
function logout(url)
|
|
{
|
|
if(!url) url = getLastApp().currentUrl.replace(/^https?:\/\/[^/]+/, '');
|
|
$.get($.createLink('user', 'logout', 'referer=' + btoa(url)), function(data)
|
|
{
|
|
let load = $.createLink('user', 'login');
|
|
try
|
|
{
|
|
data = JSON.parse(data);
|
|
if(data.load != 'login') load = data.load;
|
|
}
|
|
catch (error) {}
|
|
location.href = load;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Toggle left menu
|
|
* @param {boolean} [toggle]
|
|
* @returns {boolean}
|
|
*/
|
|
function toggleMenu(toggle)
|
|
{
|
|
var $body = $('body');
|
|
if (toggle === undefined) toggle = $body.hasClass('hide-menu');
|
|
$body.toggleClass('hide-menu', !toggle).toggleClass('show-menu', !!toggle);
|
|
|
|
$.cookie.set('hideMenu', String(!toggle), {expires: config.cookieLife, path: config.webRoot});
|
|
return toggle;
|
|
}
|
|
|
|
/**
|
|
* Refresh more menu in #menuNav
|
|
* @return {void}
|
|
*/
|
|
function refreshMenu()
|
|
{
|
|
const $mainNav = $('#menuMainNav');
|
|
const $list = $('#menuMoreList');
|
|
const $menuNav = $('#menuNav');
|
|
const $menuItems = $mainNav.children('li');
|
|
const itemHeight = $menuItems.first().outerHeight();
|
|
const maxHeight = $menuNav.outerHeight() - 12;
|
|
const dividerHeight = 13;
|
|
let showMoreMenu = false;
|
|
let currentHeight = itemHeight;
|
|
let moreMenuHeight = 12;
|
|
|
|
$menuItems.each(function()
|
|
{
|
|
var $item = $(this);
|
|
var isDivider = $item.hasClass('divider');
|
|
var height = isDivider ? dividerHeight : itemHeight;
|
|
currentHeight += height;
|
|
|
|
if(currentHeight > maxHeight)
|
|
{
|
|
$item.addClass('hidden');
|
|
if(!showMoreMenu)
|
|
{
|
|
showMoreMenu = true;
|
|
$list.empty();
|
|
|
|
var $prevItem = $item.prev();
|
|
if($prevItem.hasClass('divider')) $prevItem.addClass('hidden');
|
|
|
|
if(isDivider) return;
|
|
}
|
|
moreMenuHeight += isDivider ? dividerHeight : itemHeight;
|
|
$list.append($item.clone().toggleClass('menu-item', !isDivider).removeClass('hidden'));
|
|
}
|
|
else
|
|
{
|
|
$item.removeClass('hidden');
|
|
}
|
|
});
|
|
|
|
/* The magic number "111" is the space between dropdown trigger btn and the bottom of screen */
|
|
let listStyle = {maxHeight: 'initial', top: moreMenuHeight > 111 ? 111 - moreMenuHeight : ''};
|
|
if($list[0] && $list[0].getBoundingClientRect)
|
|
{
|
|
const btnBounding = $list.prev('a')[0].getBoundingClientRect();
|
|
if(btnBounding.height)
|
|
{
|
|
const winHeight = $(window).height();
|
|
if(winHeight < moreMenuHeight)
|
|
{
|
|
listStyle.maxHeight = winHeight;
|
|
listStyle.overflow = 'auto';
|
|
listStyle.top = 5 - btnBounding.top;
|
|
}
|
|
else if(moreMenuHeight > (winHeight - btnBounding.top))
|
|
{
|
|
listStyle.top = winHeight - btnBounding.top - moreMenuHeight + 5;
|
|
}
|
|
}
|
|
}
|
|
$list.css(listStyle);
|
|
$menuNav.toggleClass('show-more-nav', showMoreMenu);
|
|
|
|
if(showMoreMenu && !$list.data('listened-click'))
|
|
{
|
|
$list.data('listened-click', true).on('click', function()
|
|
{
|
|
$list.addClass('hidden');
|
|
setTimeout(function(){$list.removeClass('hidden')}, 200);
|
|
});
|
|
}
|
|
}
|
|
|
|
/** Init apps menu list. */
|
|
function initAppsMenu(items)
|
|
{
|
|
apps.map.help =
|
|
{
|
|
code: 'help',
|
|
icon: 'icon-help',
|
|
url: manualUrl || $helpLink.attr('href'),
|
|
external: true,
|
|
text: manualText || $helpLink.text(),
|
|
appUrl: config.webRoot + '#app=help'
|
|
};
|
|
|
|
const $menuMainNav = $('#menuMainNav').empty();
|
|
(items || appsItems).forEach(function(item)
|
|
{
|
|
const oldItem = apps.map[item.code];
|
|
if(item === 'divider') return $menuMainNav.append('<li class="divider"></li>');
|
|
if(oldItem !== item && oldItem) item = $.extend({}, oldItem, item, {active: oldItem.active});
|
|
item.external = item.external || item.url && item.url.includes('://');
|
|
|
|
const $link= $('<a data-pos="menu"></a>')
|
|
.attr('data-app', item.notApp ? undefined : item.code)
|
|
.attr('href', item.url || '#')
|
|
.attr('target', item.notApp ? '_blank' : undefined)
|
|
.addClass('rounded' + (item.notApp ? '' : ' show-in-app'))
|
|
.html(item.title, false);
|
|
|
|
item.icon = item.icon || ($link.find('.icon').attr('class') || '').replace('icon ', '');
|
|
item.text = $link.text().trim();
|
|
$link.html('<i class="icon ' + item.icon + '"></i><span class="text">' + item.text + '</span>', false);
|
|
if(['devops', 'bi', 'safe'].includes(item.code)) $link.find('.text').addClass('font-brand');
|
|
apps.map[item.code] = item;
|
|
|
|
$('<li class="hint-right"></li>')
|
|
.attr({'data-app': item.code, 'data-hint': item.text})
|
|
.append($link)
|
|
.appendTo($menuMainNav);
|
|
|
|
if(!apps.defaultCode) apps.defaultCode = item.code;
|
|
});
|
|
|
|
/* 隐藏的App依然可以通过输入URL的形式打开。 */
|
|
allAppsItems.forEach(function(item)
|
|
{
|
|
if(item.code && !apps.map[item.code]) apps.map[item.code] = item;
|
|
});
|
|
|
|
const lastApp = getLastApp();
|
|
if(lastApp) $menuMainNav.find('li[data-app="' + lastApp.code + '"]>a').addClass('active');
|
|
|
|
apps.map.search =
|
|
{
|
|
opened: false,
|
|
code: 'search',
|
|
group: 'search',
|
|
icon: 'icon-search',
|
|
methodName: 'index',
|
|
moduleName: 'search',
|
|
text: langData.search,
|
|
title: '<i class="icon icon-search"></i> ' + langData.search,
|
|
url: '/index.php?m=search&f=index',
|
|
vars: ''
|
|
};
|
|
|
|
$('#appTabs').find('.nav-item').each(function()
|
|
{
|
|
const $item = $(this);
|
|
const code = $item.data('app');
|
|
const app = apps.map[code];
|
|
if(!app) return;
|
|
$item.find('.text').text(app.text);
|
|
});
|
|
}
|
|
|
|
/** Update apps menu. */
|
|
function updateAppsMenu(includeAppsBar)
|
|
{
|
|
loadCurrentPage(
|
|
{
|
|
selector: (includeAppsBar ? '#menuMoreBtn>*,#appsToolbar>*,#visionSwitcher>*,' : '') + 'appsItems()',
|
|
onRender: function(info)
|
|
{
|
|
if(info.name === 'appsItems')
|
|
{
|
|
initAppsMenu(info.data);
|
|
refreshMenu();
|
|
return true;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function changeAppsLang(lang)
|
|
{
|
|
$('html').attr('lang', lang);
|
|
zui.i18n.setCode(lang);
|
|
$.each(apps.openedMap, function(_code, app)
|
|
{
|
|
if(app.iframe && app.iframe.contentWindow && app.iframe.contentWindow.changeAppLang)
|
|
{
|
|
app.iframe.contentWindow.changeAppLang(lang);
|
|
}
|
|
});
|
|
updateAppsMenu(true);
|
|
}
|
|
|
|
function changeAppsTheme(theme)
|
|
{
|
|
changeAppTheme(theme);
|
|
$.each(apps.openedMap, function(_code, app)
|
|
{
|
|
if(app.iframe && app.iframe.contentWindow && app.iframe.contentWindow.changeAppTheme)
|
|
{
|
|
app.iframe.contentWindow.changeAppTheme(theme);
|
|
}
|
|
});
|
|
apps.theme = theme;
|
|
$.get($.createLink('index', 'app'), html =>
|
|
{
|
|
apps.frameContent = html;
|
|
apps.theme = null;
|
|
});
|
|
}
|
|
|
|
function updateUserToolbar()
|
|
{
|
|
$.each(apps.openedMap, function(_code, app)
|
|
{
|
|
if(app.iframe && app.iframe.contentWindow && app.iframe.contentWindow.loadPage)
|
|
{
|
|
app.iframe.contentWindow.loadPage({selector: '#toolbar', partial: true, target: '#toolbar'});
|
|
}
|
|
});
|
|
}
|
|
|
|
initAppsMenu();
|
|
/* Refresh more menu on window resize */
|
|
$(window).on('resize', refreshMenu);
|
|
refreshMenu();
|
|
setTimeout(refreshMenu, 500);
|
|
|
|
/* Bind event for menut-toggle */
|
|
$(document).on('click', '.menu-toggle', () => toggleMenu());
|
|
toggleMenu(!$('body').hasClass('hide-menu'));
|
|
|
|
/**
|
|
* Get current menu nav data.
|
|
*
|
|
* @returns {Array<{name: string; order: number;}>}
|
|
*/
|
|
function getMenuNavData()
|
|
{
|
|
const data = [];
|
|
const $nav = $('#menuMainNav');
|
|
$nav.children().each(function(index, element) {
|
|
const $elm = $(element);
|
|
const menuItem = {};
|
|
menuItem.name = $elm.is('.divider') ? 'divider' : $elm.data('app');
|
|
menuItem.order = index * 5;
|
|
if(typeof $elm.data('hidden') != 'undefined') menuItem.hidden = true;
|
|
|
|
data.push(menuItem);
|
|
});
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* Save menu nav to server.
|
|
*/
|
|
function saveMenuNavToServer()
|
|
{
|
|
const url = $.createLink('custom', 'ajaxSetMenu');
|
|
const data = getMenuNavData();
|
|
$.ajaxSubmit({url, data: {menu: 'nav', items: JSON.stringify(data)}});
|
|
}
|
|
|
|
/**
|
|
* Restore menu nav to server.
|
|
*/
|
|
function restoreMenuNavToServer()
|
|
{
|
|
const url = $.createLink('custom', 'ajaxRestoreMenu');
|
|
$.ajaxSubmit({url, data: {menu: 'nav'}});
|
|
top.location.reload();
|
|
}
|
|
|
|
/**
|
|
* Generate menu nav items to be added.
|
|
*
|
|
* @param {Cash} $item
|
|
* @param {(item: string) => void} onClick click handler of menu item.
|
|
* @returns {Array<{icon: string; text: string; onClick: () => void;}>}
|
|
*/
|
|
function generateAddMenuNavItems($item, onClick)
|
|
{
|
|
const items = canAddDivider($item)
|
|
? [
|
|
{
|
|
icon: 'icon-minus',
|
|
text: langData.divider,
|
|
onClick: () => {
|
|
onClick('divider');
|
|
saveMenuNavToServer();
|
|
}
|
|
}
|
|
]
|
|
: [];
|
|
|
|
const data = getMenuNavData();
|
|
const allAppCodeSet = new Set(allAppsItemsMap.keys());
|
|
for(const {name} of data)
|
|
{
|
|
if(name === 'divider') continue;
|
|
allAppCodeSet.delete(name);
|
|
}
|
|
|
|
if(allAppCodeSet.size === 0) return items;
|
|
for(const name of allAppCodeSet)
|
|
{
|
|
const [icon, title] = getAppItemIconAndTitle(name);
|
|
items.push(
|
|
{
|
|
icon,
|
|
text: title,
|
|
onClick: () => {
|
|
onClick(name);
|
|
saveMenuNavToServer();
|
|
}
|
|
}
|
|
);
|
|
}
|
|
return items;
|
|
}
|
|
|
|
$(document).on('contextmenu', '#menuMainNav .divider', function(event)
|
|
{
|
|
const $divider = $(this);
|
|
const $nav = $divider.closest('.nav');
|
|
const isMoving = $nav.is('[z-use-sortable]');
|
|
const items = [];
|
|
if(isMoving)
|
|
{
|
|
items.push(
|
|
{
|
|
text: langData.save,
|
|
onClick: () => {
|
|
$divider.closest('.nav').zui().destroy();
|
|
saveMenuNavToServer();
|
|
}
|
|
}
|
|
);
|
|
}
|
|
else
|
|
{
|
|
items.push(
|
|
{
|
|
text: langData.sort,
|
|
onClick: () => {
|
|
const sortable = new zui.Sortable(
|
|
'#menuMainNav',
|
|
{
|
|
animation: 150,
|
|
ghostClass: 'bg-primary-pale',
|
|
onSort: () => {
|
|
saveMenuNavToServer();
|
|
}
|
|
}
|
|
);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
items.push(
|
|
{
|
|
text: langData.hide,
|
|
onClick: () => {
|
|
const $li = $divider.closest('li');
|
|
$li.remove();
|
|
refreshMenu();
|
|
saveMenuNavToServer();
|
|
}
|
|
}
|
|
);
|
|
const toAddedItems = generateAddMenuNavItems($divider, addMenuToMainNavCb($divider));
|
|
items.push(
|
|
toAddedItems.length === 0
|
|
? {
|
|
text: langData.add,
|
|
disabled: true,
|
|
}
|
|
: {
|
|
text: langData.add,
|
|
items: toAddedItems,
|
|
}
|
|
);
|
|
items.push(
|
|
{
|
|
text: langData.restore,
|
|
onClick: () => {
|
|
restoreMenuNavToServer();
|
|
}
|
|
}
|
|
);
|
|
|
|
if(apps.openedMenu) apps.openedMenu.hide();
|
|
apps.openedMenu = zui.ContextMenu.show(
|
|
{
|
|
element: $divider[0],
|
|
placement: 'right-start',
|
|
items: items,
|
|
event: event,
|
|
onClickItem: (info) => info.event.preventDefault(),
|
|
onHide: () => apps.openedMenu = null,
|
|
}
|
|
);
|
|
event.preventDefault();
|
|
});
|
|
|
|
/* Bind events for app trigger */
|
|
$(document).on('click', '.open-in-app,.show-in-app', function(e)
|
|
{
|
|
const $link = $(this);
|
|
if($link.is('[data-modal],[data-toggle],.iframe,.not-in-app')) return;
|
|
const url = $link.attr('href') || $link.data('url');
|
|
if(url && url.includes('onlybody=yes')) return;
|
|
const code = $link.data('app');
|
|
if($.apps.openedMap[code]) showApp(code);
|
|
else openApp(url, $link.data('app'), !$link.hasClass('show-in-app'));
|
|
e.preventDefault();
|
|
}).on('contextmenu', '.open-in-app,.show-in-app', function(event)
|
|
{
|
|
if(isTutorialMode) return;
|
|
|
|
const $btn = $(this);
|
|
const code = $btn.data('app');
|
|
if(!code) return;
|
|
|
|
const app = apps.openedMap[code];
|
|
const items = [{text: langData.open, disabled: app && getLastAppCode() === code, onClick: () => showApp(code)}];
|
|
|
|
if(app) items.push({text: langData.reload, onClick: () => reloadApp(code)});
|
|
|
|
if($btn.closest('#menuMainNav').length !== 0)
|
|
{
|
|
const $nav = $btn.closest('.nav');
|
|
const isMoving = $nav.is('[z-use-sortable]');
|
|
if(isMoving)
|
|
{
|
|
items.push(
|
|
{
|
|
text: langData.save,
|
|
onClick: () => {
|
|
$btn.closest('.nav').zui().destroy();
|
|
saveMenuNavToServer();
|
|
}
|
|
}
|
|
);
|
|
}
|
|
else
|
|
{
|
|
items.push(
|
|
{
|
|
text: langData.sort,
|
|
onClick: () => {
|
|
const sortable = new zui.Sortable(
|
|
'#menuMainNav',
|
|
{
|
|
animation: 150,
|
|
ghostClass: 'bg-primary-pale',
|
|
onSort: () => {
|
|
saveMenuNavToServer();
|
|
}
|
|
}
|
|
);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
const hideDisabled = code === 'my' || $btn.is('.active');
|
|
items.push(
|
|
{
|
|
text: langData.hide,
|
|
onClick: hideDisabled
|
|
? null
|
|
: () => {
|
|
closeApp(code);
|
|
const $li = $btn.closest('li');
|
|
$li.hide().attr('data-hidden', '1');
|
|
refreshMenu();
|
|
saveMenuNavToServer();
|
|
},
|
|
disabled: hideDisabled,
|
|
}
|
|
);
|
|
const toAddedItems = generateAddMenuNavItems($btn, addMenuToMainNavCb($btn.closest('li')));
|
|
items.push(
|
|
toAddedItems.length === 0
|
|
? {
|
|
text: langData.add,
|
|
disabled: true,
|
|
}
|
|
: {
|
|
text: langData.add,
|
|
items: toAddedItems,
|
|
}
|
|
);
|
|
|
|
if(app && code !== 'my') items.push({text: langData.close, onClick: () => closeApp(code)});
|
|
|
|
items.push(
|
|
{
|
|
text: langData.restore,
|
|
onClick: () => {
|
|
restoreMenuNavToServer();
|
|
}
|
|
}
|
|
);
|
|
} else
|
|
{
|
|
if(app && code !== 'my') items.push({text: langData.close, onClick: () => closeApp(code)});
|
|
}
|
|
|
|
if(apps.openedMenu) apps.openedMenu.hide();
|
|
apps.openedMenu = zui.ContextMenu.show({element: $btn[0], placement: $btn.closest('#appTabs').length ? 'top-start' : 'right-start', items: items, event: event, onClickItem: function(info){info.event.preventDefault();}, onHide: () => {apps.openedMenu = null}});
|
|
event.preventDefault();
|
|
});
|
|
|
|
$(window).on('popstate', function(event)
|
|
{
|
|
const state = event.state;
|
|
if(DEBUG) showLog(state ? state.code : null, 'Popstate', state ? state.url : null, state);
|
|
if(state) openApp(state.url, state.code, false);
|
|
});
|
|
|
|
$.get($.createLink('index', 'app'), html =>
|
|
{
|
|
apps.frameContent = html;
|
|
|
|
/* Open default app */
|
|
let url = defaultOpen || '';
|
|
let code = '';
|
|
if(location.hash.indexOf('#app=') === 0)
|
|
{
|
|
const params = $.parseUrlParams(location.hash.substring(1));
|
|
code = params.app;
|
|
}
|
|
else if(url.includes(' '))
|
|
{
|
|
const parts = url.split(' ');
|
|
url = parts[0];
|
|
code = parts[1];
|
|
}
|
|
else if(url && $.apps[url])
|
|
{
|
|
code = url;
|
|
url = '';
|
|
}
|
|
if(!code && defaultOpen)
|
|
{
|
|
const lastOpenApp = zui.store.session.get('lastOpenApp');
|
|
if(lastOpenApp && (lastOpenApp.url === url || lastOpenApp.url.endsWith(url) || $.parseLink(lastOpenApp.url).url === $.parseLink(url).url)) code = lastOpenApp.code;
|
|
}
|
|
if(!url)
|
|
{
|
|
if(!code) url = apps.defaultCode;
|
|
else {url = code; code = '';}
|
|
}
|
|
openApp(url, code);
|
|
});
|
|
|
|
$.apps = $.extend(apps,
|
|
{
|
|
openedApps: apps.openedMap,
|
|
openApp: openApp,
|
|
open: openApp,
|
|
reloadApp: reloadApp,
|
|
showApp: showApp,
|
|
updateApp: updateApp,
|
|
updateUrl: function(appCode, url, title) {return updateApp(appCode, url, title)},
|
|
getLastApp: getLastApp,
|
|
goBack: goBack,
|
|
logout: logout,
|
|
isOldPage: isOldPage,
|
|
getAppCode: getAppCode,
|
|
updateAppsMenu: updateAppsMenu,
|
|
changeAppsLang: changeAppsLang,
|
|
changeAppsTheme: changeAppsTheme,
|
|
updateUserToolbar: updateUserToolbar,
|
|
closeApp: closeApp,
|
|
toggleMenu: toggleMenu,
|
|
triggerAppEvent: triggerAppEvent,
|
|
});
|
|
|
|
window.notifyMessage = function(data)
|
|
{
|
|
if(!window.Notification) return;
|
|
|
|
var notify = null;
|
|
var message = data;
|
|
if(typeof data.text == 'string') message = data.text;
|
|
if(Notification.permission == "granted")
|
|
{
|
|
notify = new Notification("", {body:message, tag:'zentao', data:data});
|
|
}
|
|
else if(Notification.permission != "denied")
|
|
{
|
|
Notification.requestPermission().then(function(permission)
|
|
{
|
|
notify = new Notification("", {body:message, tag:'zentao', data:data});
|
|
});
|
|
}
|
|
|
|
if(!notify) return;
|
|
|
|
notify.onclick = function()
|
|
{
|
|
window.focus();
|
|
if(typeof notify.data.url == 'string' && notify.data.url) window.location.href = notify.data.url;
|
|
notify.close();
|
|
}
|
|
setTimeout(function(){notify.close();}, 3000);
|
|
}
|
|
|
|
window.browserNotify = function()
|
|
{
|
|
let windowBlur = false;
|
|
let preCount = 0;
|
|
setInterval(function()
|
|
{
|
|
if(window.Notification && Notification.permission == 'default') Notification.requestPermission();
|
|
if(window.Notification && Notification.permission == 'granted')
|
|
{
|
|
window.onblur = function(){windowBlur = true;}
|
|
window.onfocus = function(){windowBlur = false;}
|
|
$('iframe').each(function()
|
|
{
|
|
let iframeID = $(this).attr('id');
|
|
let $this = document.getElementById(iframeID);
|
|
if(document.all)
|
|
{
|
|
$this.onblur = function(){windowBlur = true;}
|
|
$this.onfocus = function(){windowBlur = false;}
|
|
}
|
|
else
|
|
{
|
|
$this.contentWindow.onblur = function(){windowBlur = true;}
|
|
$this.contentWindow.onfocus = function(){windowBlur = false;}
|
|
}
|
|
});
|
|
}
|
|
|
|
$.get($.createLink('message', 'ajaxGetMessage', "windowBlur=" + (windowBlur ? '1' : '0')), function(data)
|
|
{
|
|
if(!data) return;
|
|
if(typeof data == 'string') data = JSON.parse(data);
|
|
for(i in data.messages)
|
|
{
|
|
let message = data.messages[i];
|
|
if(windowBlur)
|
|
{
|
|
if(typeof message.text == 'string') notifyMessage(message);
|
|
}
|
|
else
|
|
{
|
|
if(!$(message).hasClass('browser-message-content')) continue;
|
|
zui.Messager.show(
|
|
{
|
|
content: {html: message},
|
|
placement: 'bottom-right',
|
|
time: 0,
|
|
icon: 'envelope-o',
|
|
className: 'bg-secondary-50 text-secondary-600 messager-notice'
|
|
});
|
|
}
|
|
}
|
|
|
|
let unreadCount = parseInt(data.newCount);
|
|
let showCount = data.showCount != '0';
|
|
let dotStyle = 'padding: 2px;';
|
|
let rightStyle = showCount ? 'right: -10px;' : 'right: -2px;';
|
|
if(!showCount) dotStyle += 'width: 5px; height: 5px;';
|
|
if(unreadCount < 10 && showCount) rightStyle = 'right: -5px;';
|
|
if(unreadCount > 99) unreadCount = '99+';
|
|
|
|
dotStyle += showCount ? 'top: -3px; aspect-ratio: 0;' : 'top: -2px; aspect-ratio: 1 / 1;';
|
|
dotStyle += rightStyle;
|
|
|
|
let dotHtml = '<span class="label danger label-dot absolute' + (showCount ? ' rounded-sm' : '') + '" style="' + dotStyle + '">' + (showCount ? unreadCount : '') + '</span>';
|
|
$('#apps .app-container').each(function()
|
|
{
|
|
let $iframeMessageBar = $(this).find('iframe').contents().find('#messageBar');
|
|
if($iframeMessageBar.length > 0)
|
|
{
|
|
$iframeMessageBar.find('.label-dot.danger').remove();
|
|
if(unreadCount) $iframeMessageBar.append(dotHtml);
|
|
}
|
|
|
|
let $oldPage = $(this).find('iframe').contents().find('#oldPage');
|
|
if($oldPage.length > 0)
|
|
{
|
|
$iframeMessageBar = $oldPage.find('iframe').contents().find('#messageBar');
|
|
if($iframeMessageBar.length == 0) return;
|
|
|
|
$iframeMessageBar.find('.label-dot.danger').remove();
|
|
if(unreadCount) $iframeMessageBar.append(dotHtml);
|
|
}
|
|
});
|
|
});
|
|
}, pollTime * 1000);
|
|
};
|
|
|
|
window.clickMessage = function(obj)
|
|
{
|
|
let $obj = $(obj);
|
|
let url = $obj.attr('data-url').replace(/\?onlybody=yes/g, '').replace(/\&onlybody=yes/g, '');
|
|
let messageID = $obj.parent().attr('data-id');
|
|
openApp(url);
|
|
$.get($.createLink('message', 'ajaxMarkRead', 'id=' + messageID));
|
|
$(obj).closest('.alert.messager').find('.alert-close').trigger('click');
|
|
}
|
|
|
|
window.ping = function()
|
|
{
|
|
setInterval(function(){$.get($.createLink('misc', 'ping'));}, pollTime * 1000);
|
|
}
|
|
|
|
window.startCron = function(restart)
|
|
{
|
|
if(typeof(restart) == 'undefined') restart = 0;
|
|
$.ajax({type:"GET", timeout:100, url:$.createLink('cron', 'ajaxExec', 'restart=' + restart)});
|
|
}
|
|
|
|
turnon ? browserNotify() : ping();
|
|
if(runnable) startCron();
|
|
if(scoreNotice) zui.Messager.show({ content: {html: scoreNotice}, placement: 'bottom-right', time: 0, icon: 'diamond', className: 'bg-secondary-50 text-secondary-600 score-notice'});
|
|
|
|
/* Handle clicking outside. */
|
|
$(document).on('click', e =>
|
|
{
|
|
if($(e.target).attr('id') != 'bizLink' && !$(e.target).parents('#bizLink').length)
|
|
{
|
|
$('#upgradeContent').hide();
|
|
$('#bizLink').removeClass('active');
|
|
}
|
|
});
|
|
|
|
const allAppsItemsMap = new Map();
|
|
$(document).ready(
|
|
function()
|
|
{
|
|
for(const item of allAppsItems)
|
|
{
|
|
if(item === 'divider') continue;
|
|
|
|
allAppsItemsMap.set(item.code, item);
|
|
}
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Get icon and title of app item.
|
|
*
|
|
* @param {string} name app name
|
|
* @returns {[string, string]}
|
|
*/
|
|
function getAppItemIconAndTitle(name)
|
|
{
|
|
if(!allAppsItemsMap.has(name)) return[];
|
|
const item = allAppsItemsMap.get(name);
|
|
const str = item.title;
|
|
const regex = /class=["']icon (\S*)["']\>\<\/i\>\s(\S*)/;
|
|
const matches = str.match(regex);
|
|
|
|
if(matches)
|
|
{
|
|
const icon = matches[1];
|
|
const text = matches[2].trim();
|
|
return [icon, text];
|
|
}
|
|
return [];
|
|
}
|
|
|
|
/**
|
|
* Add menu item to #mainNav callback, used by generateAddMenuNavItems.
|
|
*
|
|
* @param {Cash} $li menu item li
|
|
* @returns {(name: string) => void}
|
|
*/
|
|
function addMenuToMainNavCb($li) {
|
|
return (name) => {
|
|
if(name === 'divider')
|
|
{
|
|
$li.after('<li class="divider"></li>');
|
|
refreshMenu();
|
|
return
|
|
}
|
|
|
|
let item = allAppsItemsMap.get(name);
|
|
const oldItem = apps.map[item.code];
|
|
if(oldItem !== item && oldItem) item = $.extend({}, oldItem, item, {active: false});
|
|
item.external = item.external || item.url && item.url.includes('://');
|
|
|
|
const $link= $('<a data-pos="menu"></a>')
|
|
.attr('data-app', item.notApp ? undefined : item.code)
|
|
.attr('href', item.url || '#')
|
|
.attr('target', item.notApp ? '_blank' : undefined)
|
|
.addClass('rounded' + (item.notApp ? '' : ' show-in-app'))
|
|
.html(item.title, false);
|
|
|
|
item.icon = item.icon || ($link.find('.icon').attr('class') || '').replace('icon ', '');
|
|
item.text = $link.text().trim();
|
|
$link.html('<i class="icon ' + item.icon + '"></i><span class="text">' + item.text + '</span>', false);
|
|
if(['devops', 'bi', 'safe'].includes(item.code)) $link.find('.text').addClass('font-brand');
|
|
apps.map[item.code] = item;
|
|
|
|
$('<li class="hint-right"></li>')
|
|
.attr({'data-app': item.code, 'data-hint': item.text})
|
|
.append($link)
|
|
.insertAfter($li);
|
|
|
|
refreshMenu();
|
|
if(!apps.defaultCode) apps.defaultCode = item.code;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Check whether current element can add a divider.
|
|
* @param {Cash} $item
|
|
* @returns {boolean}
|
|
*/
|
|
function canAddDivider($item)
|
|
{
|
|
$item = $item.closest('li');
|
|
if($item.is('.divider')) return false;
|
|
if($item.next().is('.divider')) return false;
|
|
if($item.is(':last-child')) return false;
|
|
return true;
|
|
}
|