| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681 |
- /* 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());
- }
- /**
- * 提取纯文本标签(去除 HTML 标签)
- * @param {string} html
- * @returns {string}
- */
- function extractLabelText(html)
- {
- return html.replace(/<[^>]*>/g, '').trim();
- }
- /**
- * 获取所有应用列表。
- * @returns {Array<{id: string, label: string, active: boolean}>}
- */
- function getApps()
- {
- const lastAppCode = getLastAppCode();
- return Array.from(allAppsItemsMap.values()).map(app => ({
- id: app.code,
- label: extractLabelText(window.appsItems.find(item => item.code === app.code)?.title),
- active: lastAppCode === app.code
- }));
- }
- /**
- * 获取所有可见的应用列表。
- * @returns {Array<{id: string, label: string, active: boolean}>}
- */
- function getVisibleApps()
- {
- const $mainNav = $('#menuMainNav');
- return getApps().filter(app => {
- const $menuItem = $mainNav.find(`li[data-app="${app.id}"]`);
- return $menuItem.length > 0 && !$menuItem.is('[data-hidden="1"]');
- });
- }
- /**
- * 获取所有已打开的应用列表。
- * @returns {Array<{id: string, label: string, active: boolean}>}
- */
- function getOpenedApps()
- {
- const lastAppCode = getLastAppCode();
- return Object.values(apps.openedMap).map(app => ({
- id: app.code,
- label: extractLabelText(window.appsItems.find(item => item.code === app.code)?.title),
- active: app.code === lastAppCode
- }));
- }
- /**
- * 触发 onChangeApp 回调函数。
- */
- function changeApp()
- {
- const callback = window.zinCallbacks.onChangeApp;
- if(typeof callback === 'function')
- {
- try { callback(getVisibleApps(), getOpenedApps()); } catch (e) { console.error('[ZIN] onChangeApp callback error:', e); }
- }
- }
- /**
- * 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);
- }
- let openedApp = apps.openedMap[code];
- if($.apps.workspace && $.apps.workspace !== code)
- {
- url = url || (openedApp ? openedApp.currentUrl : app.url);
- if(url) window.open(url, '_blank');
- return;
- }
- /* Create iframe for app */
- 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 */
- if(!openedApp.workspace)
- {
- 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');
- }
- $('body').attr('data-app', code);
- 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').parent().removeClass('active');
- $tabs.find('li[data-app="' + code + '"]>a').addClass('active').parent().addClass('active');
- }
- if(DEBUG) showLog(code, 'Open', getUrlID(url), openedApp, {options, forceReload, needLoad});
- triggerAppEvent(code, 'openapp', [openedApp, {load: needLoad}]);
- changeApp();
- const spaceType = openedApp.workspace ? openedApp.workspace.type : '';
- if(spaceType !== $('#menu').attr('data-space')) updateSpaceMenu(openedApp.workspace);
- 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;
- changeApp();
- }
- 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(['ai', 'zai', 'aiteammate', 'aitask'].includes(moduleName)) return 'aiapp';
- 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 && !target.endsWith('APP'))
- {
- 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 hasSpace = $('body').hasClass('has-space');
- const $menuItems = $mainNav.children(hasSpace ? '.is-space' : '.is-original');
- const itemHeight = $menuItems.filter((_, ele) => {
- const $ele = $(ele);
- return $ele.css('display') !== 'none' && !$ele.hasClass('divider');
- }).first().outerHeight();
- const maxHeight = $('#menu').outerHeight() - ($('body').hasClass('has-space') ? (($('#spaceHeading').outerHeight() || 0) + 8) : 0) - 24 - $('#menuToggleNav').outerHeight();
- const dividerHeight = 13;
- let showMoreMenu = false;
- let currentHeight = 0;
- let $firstHiddenItem = null;
- const moreBtnHeight = itemHeight + dividerHeight;
- $menuItems.each(function()
- {
- let $item = $(this);
- let isDivider = $item.hasClass('divider');
- let height = isDivider ? dividerHeight : itemHeight;
- if(showMoreMenu || (currentHeight + height) > maxHeight)
- {
- if(!$firstHiddenItem) $firstHiddenItem = $item;
- $item.addClass('hidden');
- if(!showMoreMenu)
- {
- showMoreMenu = true;
- $list.empty();
- }
- $list.append($item.clone().toggleClass('menu-item', !isDivider).removeClass('hidden'));
- }
- else
- {
- currentHeight += height;
- $item.removeClass('hidden');
- }
- });
- if(showMoreMenu && $firstHiddenItem && (currentHeight + moreBtnHeight) > maxHeight)
- {
- const $prevItem = $firstHiddenItem.prev();
- if($prevItem.length)
- {
- $prevItem.addClass('hidden');
- let isDivider = $prevItem.hasClass('divider');
- $list.prepend($prevItem.clone().toggleClass('menu-item', !isDivider).removeClass('hidden'));
- }
- }
- let moreMenuHeight = 16;
- let nextNoDivider = true;
- $list.children().each(function()
- {
- const $item = $(this);
- const isDivider = $item.hasClass('divider');
- if(isDivider && nextNoDivider)
- {
- $item.addClass('hidden');
- return;
- }
- nextNoDivider = isDivider;
- moreMenuHeight += isDivider ? dividerHeight : itemHeight;
- });
- nextNoDivider = true;
- let $lastItem = null;
- $menuItems.each(function()
- {
- const $item = $(this);
- if($item.hasClass('hidden')) return;
- const isDivider = $item.hasClass('divider');
- if(isDivider && nextNoDivider)
- {
- $item.addClass('hidden');
- return;
- }
- nextNoDivider = isDivider;
- $lastItem = $item;
- });
- if($lastItem && $lastItem.hasClass('divider')) $lastItem.addClass('hidden');
- $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)
- {
- if($.cookie.get('workspace'))
- {
- $('#menu').addClass('waiting-workspace');
- $(document).on('openapp', () =>
- {
- if($.cookie.get('workspace') === getLastAppCode()) return;
- $('#menu').removeClass('waiting-workspace');
- });
- }
- 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 is-original"></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 icon-app-empty ' + item.icon + '"></i><span class="text"></span>', false);
- $link.find('.text').toggleClass('font-brand', ['devops', 'bi', 'safe'].includes(item.code)).attr('title', item.text).text(item.text);
- apps.map[item.code] = item;
- $('<li class="hint-right is-original"></li>')
- .attr({'data-app': item.code, 'data-hint': item.text})
- .append($link)
- .appendTo($menuMainNav);
- const hasLongText = $link.find('.text').width() > ($link.outerWidth() - 24);
- $link.toggleClass('has-long-text', hasLongText).find('.text').attr('title', hasLongText ? item.text : null);
- 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);
- });
- const selectLangCallback = window.zinCallbacks.onSelectLang;
- if(typeof selectLangCallback === 'function')
- {
- try { selectLangCallback(window.config.clientLang); } catch (e) { console.error('[ZIN] onSelectLang callback error:', e); }
- }
- const selectVisionCallback = window.zinCallbacks.onSelectVision;
- if(typeof selectVisionCallback === 'function')
- {
- try { selectVisionCallback(window.getVisions()); } catch (e) { console.error('[ZIN] onSelectVision callback error:', e); }
- }
- refreshMenu();
- }
- /** Update apps menu. */
- function updateAppsMenu(includeAppsBar)
- {
- loadCurrentPage(
- {
- selector: (includeAppsBar ? '#menuMoreBtn>*,#appsToolbar>*,#visionSwitcher>*,' : '') + '#appsItemsData,#configJS,#searchItemsData,#userVisionData',
- complete: () => initAppsMenu(window.appsItems),
- });
- }
- 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);
- }
- });
- if(!$.apps.workspace) 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);
- 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');
- const workspace = $.apps.workspace;
- $nav.children($.apps.workspace ? '.is-space' : '.is-original').each(function(index, element)
- {
- const $elm = $(element);
- if($elm.hasClass('rsh-more')) return;
- const menuItem = {};
- menuItem.name = $elm.is('.divider') ? 'divider' : $elm.data(workspace ? 'name' : '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();
- let menu = 'nav';
- if($.apps.workspace)
- {
- const currentApp = getLastApp();
- if(currentApp.workspace.type === $.apps.workspace) menu = currentApp.workspace.menuGroup;
- }
- $.ajaxSubmit({url, data: {menu: menu, items: JSON.stringify(data)}});
- }
- /**
- * Restore menu nav to server.
- */
- function restoreMenuNavToServer()
- {
- let menu = 'nav';
- if($.apps.workspace)
- {
- const currentApp = getLastApp();
- if(currentApp.workspace.type === $.apps.workspace) menu = currentApp.workspace.menuGroup;
- }
- const url = $.createLink('custom', 'ajaxRestoreMenu');
- $.ajaxSubmit({url, data: {menu: menu}});
- 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)
- {
- const onClick = addMenuToMainNavCb($item);
- const items = [];
- const $li = $item.closest('li');
- if(canAddDivider($li)) items.push({
- 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;
- }
- function addMenuToSpaceNavCb($li, info)
- {
- return (name) => {
- if(name === 'divider')
- {
- $li.after('<li class="divider"></li>');
- refreshMenu();
- return
- }
- const fromName = $li.attr('data-name');
- const item = info.itemMap.get(name);
- if(!item) return;
- if(name === 'divider')
- {
- const index = info.items.findIndex(item => item.code === fromName);
- info.items.splice(index, 0, {type: 'divider'});
- $li.after('<li class="divider is-space"></li>');
- }
- else
- {
- const oldIndex = info.items.findIndex(item => item.code === name);
- info.items.splice(oldIndex, 1);
- const newIndex = info.items.findIndex(item => item.code === fromName);
- info.items.splice(newIndex, 0, item);
- item.hidden = false;
- updateSpaceMenu(info);
- }
- };
- }
- function generateAddSpaceNavItems($ele)
- {
- const currentApp = getLastApp();
- const info = currentApp.workspace;
- const $li = $ele.closest('li');
- if(!info || !$li.length) return [];
- const handleItemClick = (name) => {
- const fromName = $li.attr('data-name');
- if(name === 'divider')
- {
- const index = info.items.findIndex(item => item.code === fromName);
- info.items.splice(index, 0, {type: 'divider'});
- $li.after('<li class="divider is-space"></li>');
- }
- else
- {
- const item = info.itemMap.get(name);
- if(!item) return;
- const oldIndex = info.items.findIndex(item => item.code === name);
- info.items.splice(oldIndex, 1);
- const newIndex = info.items.findIndex(item => item.code === fromName);
- info.items.splice(newIndex + 1, 0, item);
- item.hidden = false;
- updateSpaceMenu(info);
- }
- saveMenuNavToServer();
- };
- const items = [];
- if(canAddDivider($li)) items.push({
- icon: 'icon-minus',
- text: langData.divider,
- onClick: () => handleItemClick('divider')
- });
- info.items.forEach((item) => {
- if(!item.hidden) return;
- items.push({
- icon: `icon-app-empty ${item.icon || `icon-${item.code}`}`,
- text: item.text,
- onClick: () => handleItemClick(item.code)
- });
- });
- return items;
- }
- function updateSpaceMenu(info)
- {
- const spaceType = info ? info.type : '';
- const currentApp = getLastApp();
- const currentCode = currentApp.code;
- const hasSpaceNav = spaceType === currentCode;
- const $menuMainNav = $('#menuMainNav');
- const $menu = $('#menu');
- $.apps.workspace = spaceType;
- currentApp.workspace = info;
- $menu.attr('data-space', spaceType);
- $('body').toggleClass('has-space', hasSpaceNav);
- $menuMainNav.children('.is-space').remove();
- if(!hasSpaceNav)
- {
- refreshMenu();
- if(info !== undefined) $menu.removeClass('waiting-workspace');
- return;
- }
- const $spaceHeading = $('#spaceHeading').toggleClass('has-dropmenu', !!info.dropmenu);
- if(info.dropmenu)
- {
- const $dropmenu = $('<div id="spaceDropmenu"></div>');
- $spaceHeading.find('.text').empty().append($dropmenu);
- const options = zui.evalValue(info.dropmenu, ['_element', $dropmenu[0]], ['_$dropmenu', $dropmenu]);
- zui.create('dropmenu', $dropmenu[0], $.extend({icon: info.icon || spaceType, display: zui.jsx`<span class="text" title=${info.name}>${info.name}</span>`}, options, {leadingAngle: false}));
- }
- else
- {
- if(info.name) $spaceHeading.find('.text').text(info.name).attr('title', info.name);
- }
- $spaceHeading.find('.icon').attr('class', `icon icon-${info.icon || spaceType}`);
- info.items.forEach(function(item)
- {
- item.code = item['data-id'];
- if(item.code === 'more') return;
- if(item === 'divider' || item.type === 'divider') return $menuMainNav.append('<li class="divider is-space"></li>');
- const $link= $('<a data-pos="menu"></a>')
- .attr('data-app', currentCode)
- .attr('href', item.url || '#')
- .attr('target', item.notApp ? '_blank' : undefined)
- .toggleClass('active', item.code === info.active)
- .addClass('rounded' + (item.notApp ? '' : ' open-in-app'));
- item.icon = item.icon || `icon-${item.code}`;
- $link.html('<i class="icon icon-app-empty ' + item.icon + '"></i><span class="text"></span>', false);
- $link.find('.text').toggleClass('font-brand', ['devops', 'bi', 'safe'].includes(item.code)).attr('title', item.text).text(item.text);
- $('<li class="hint-right is-space"></li>')
- .attr({'data-app': currentCode, 'data-hint': item.text, 'data-name': item.code})
- .attr('data-hidden', item.hidden ? 1 : null)
- .css('display', item.hidden ? 'none' : 'block')
- .append($link)
- .appendTo($menuMainNav);
- });
- refreshMenu();
- $menu.removeClass('waiting-workspace');
- }
- $(document).on('contextmenu', '#menuMainNav .divider', function(event)
- {
- const $divider = $(this);
- const $nav = $divider.closest('.nav');
- const isMoving = $nav.is('[z-use-sortable]');
- let 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();
- if(!$.apps.workspace) refreshMenu();
- saveMenuNavToServer();
- }
- });
- const toAddedItems = $.apps.workspace ? generateAddSpaceNavItems($divider) : generateAddMenuNavItems($divider);
- items.push(toAddedItems.length ? {text: langData.add, items: toAddedItems} : {text: langData.add, disabled: true});
- items.push({text: langData.restore, onClick: () => restoreMenuNavToServer()});
- items = items.filter(Boolean);
- if(!items.length) return;
- 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 inTabs = !!$btn.closest('#appTabs').length;
- const inWorkspace = $.apps.workspace === code;
- let items = [(inWorkspace && !inTabs) ? null : {text: langData.open, disabled: app && getLastAppCode() === code, onClick: () => showApp(code)}];
- if(app && (!inWorkspace || inTabs || $btn.hasClass('active'))) 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 : () => {
- const $li = $btn.closest('li');
- $li.hide().attr('data-hidden', '1');
- if($.apps.workspace)
- {
- const info = app.workspace;
- const name = $li.attr('data-name');
- const item = info.items.find(x => x.code === name);
- if(item) item.hidden = true;
- }
- else
- {
- closeApp(code);
- refreshMenu();
- changeApp();
- }
- saveMenuNavToServer();
- },
- disabled: hideDisabled,
- });
- const toAddedItems = inWorkspace ? generateAddSpaceNavItems($btn) : generateAddMenuNavItems($btn);
- if(toAddedItems.length)
- {
- items.push(toAddedItems.length ? {text: langData.add, items: toAddedItems} : {text: langData.add, disabled: true});
- }
- if(!inWorkspace && app && code !== 'my') items.push({text: langData.close, onClick: () => closeApp(code)});
- items.push({text: langData.restore, onClick: () => restoreMenuNavToServer()});
- }
- else
- {
- if(!inWorkspace && app && code !== 'my') items.push({text: langData.close, onClick: () => closeApp(code)});
- }
- items = items.filter(Boolean);
- if(!items.length) return;
- 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.filter(Boolean), 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,
- getApps: getApps,
- getVisibleApps: getVisibleApps,
- getOpenedApps: getOpenedApps,
- updateSpaceMenu: updateSpaceMenu,
- });
- 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;
- 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 w-96'
- });
- }
- }
- 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($ele) {
- return (name) => {
- const $li = $ele.closest('li');
- 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 is-original"></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().not('.hidden').is('.divider')) return false;
- if($item.is(':last-child')) return false;
- return true;
- }
|