v1.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. /**
  2. * @type {string[]}
  3. */
  4. const allNavbarItemNames = [];
  5. /**
  6. * @type {Map<string, any>}
  7. */
  8. const allNavbarItemMap = new Map();
  9. /**
  10. * Get current navbar items data.
  11. *
  12. * @returns {Array<{name: string; order: number;}>}
  13. */
  14. function getCurrentNavbarItems()
  15. {
  16. const items = [];
  17. const $nav = $('#navbar .nav');
  18. const overflowItems = [];
  19. $nav.children().each(function(index, element)
  20. {
  21. const $elm = $(element);
  22. if($elm.hasClass('rsh-more')) return;
  23. const $a = $elm.find('a');
  24. const menuItem = {};
  25. menuItem.name = $elm.is('.nav-divider') ? 'divider' : ($a.attr('data-id') || $a.attr('id'));
  26. if(typeof $elm.data('hidden') != 'undefined') menuItem.hidden = true;
  27. if($elm.hasClass('rsh-overflow-item')) overflowItems.push(menuItem);
  28. else items.push(menuItem);
  29. });
  30. return [...items, ...overflowItems].map((item, index) => ({...item, order: index * 5}));
  31. }
  32. /**
  33. * Generate navbar items to be added.
  34. * @param {Cash} $item
  35. * @param {(item: string) => void} onClick click handler of navbar item.
  36. * @returns {Array<{text: string; onClick: () => void;}>}
  37. */
  38. function generateAddNavbarItems($item, onClick)
  39. {
  40. const items = canAddDivider($item)
  41. ? [{
  42. text: langData.divider,
  43. onClick: () => {
  44. onClick('divider');
  45. saveNavbarToServer();
  46. }
  47. }]
  48. : [];
  49. const allNavbarItemIDSet = new Set(allNavbarItemMap.keys());
  50. const curNavbarItems = getCurrentNavbarItems();
  51. for(const {name} of curNavbarItems)
  52. {
  53. if(name === 'divider') continue;
  54. allNavbarItemIDSet.delete(name);
  55. }
  56. if(allNavbarItemIDSet.size === 0) return items;
  57. for(const name of allNavbarItemIDSet)
  58. {
  59. const item = allNavbarItemMap.get(name);
  60. items.push(
  61. {
  62. text: item.text,
  63. onClick: () => {
  64. onClick(name);
  65. saveNavbarToServer();
  66. }
  67. }
  68. );
  69. }
  70. return items;
  71. }
  72. /**
  73. * Checks whether the current navbar item can be hidden.
  74. *
  75. * @param {Cash} $item
  76. * @returns {boolean}
  77. */
  78. function canHideCurrentNavbar($item)
  79. {
  80. if($item.is('.nav-divider')) return true;
  81. if($item.is('.active')) return false;
  82. const app = $.apps.getLastApp();
  83. const appDefaultUrl = app.url;
  84. const itemUrl = $item.attr('href');
  85. if(itemUrl && itemUrl.includes(appDefaultUrl)) return false;
  86. return true;
  87. }
  88. function getMenuName()
  89. {
  90. const $nav = $('#navbar>.nav');
  91. return $nav.attr($nav.attr('data-workspace') ? 'data-main-navbar-group' : 'data-navbar-group');
  92. }
  93. /**
  94. * Save navbar to server.
  95. */
  96. function saveNavbarToServer(items, menu)
  97. {
  98. const url = $.createLink('custom', 'ajaxSetMenu');
  99. items = items || getCurrentNavbarItems();
  100. menu = menu ||getMenuName();
  101. $.ajaxSubmit({url, data: {menu, items: JSON.stringify(items)}});
  102. }
  103. /**
  104. * Restore navbar to server.
  105. */
  106. function restoreNavbarToServer(options = {})
  107. {
  108. const url = $.createLink('custom', 'ajaxRestoreMenu');
  109. const menu = getMenuName();
  110. $.ajaxSubmit({url, data: {menu}, ...options});
  111. }
  112. /**
  113. * Check whether current element can add a divider.
  114. * @param {Cash} $item
  115. * @returns {boolean}
  116. */
  117. function canAddDivider($item)
  118. {
  119. $item = $item.closest('li');
  120. if($item.is('.divider')) return false;
  121. if($item.next().is('.divider')) return false;
  122. if($item.is(':last-child')) return false;
  123. return true;
  124. }
  125. window.handleNavbarContextmenu = function(event, element)
  126. {
  127. const $item = $(element);
  128. const $nav = $('#navbar .nav');
  129. const isMoving = $nav.is('[z-use-sortable]');
  130. const hideDisabled = !canHideCurrentNavbar($item);
  131. const $li = $item.closest('li');
  132. const toAddedItems = generateAddNavbarItems($item, (name) => {
  133. if(name === 'divider') return $li.after('<li class="nav-divider item divider"></li>');
  134. const item = allNavbarItemMap.get(name);
  135. const $a = $('<a></a>')
  136. .attr('href', item.url)
  137. .attr('target', item.target)
  138. .attr('data-id', item['data-id'])
  139. .append(`<span class="text">${item.text}</span>`);
  140. if(item.class) $a.attr('class', item.class);
  141. const $navItem = $('<li class="nav-item item"></li>');
  142. $navItem.append($a);
  143. $li.after($navItem);
  144. saveNavbarToServer();
  145. });
  146. const items = [
  147. isMoving
  148. ? {
  149. text: langData.save,
  150. onClick: () => {
  151. $item.closest('.nav').zui().destroy();
  152. saveNavbarToServer();
  153. }
  154. }
  155. : {
  156. text: langData.sort,
  157. onClick: () => {
  158. const sortable = new zui.Sortable(
  159. '#navbar .nav',
  160. {
  161. animation: 150,
  162. ghostClass: 'bg-primary-pale',
  163. onSort: () => {
  164. saveNavbarToServer();
  165. }
  166. }
  167. );
  168. }
  169. },
  170. {
  171. text: langData.hide,
  172. disabled: hideDisabled,
  173. onClick: hideDisabled
  174. ? null
  175. : () => {
  176. $li.hasClass('nav-divider') ? $li.remove() : $li.hide().attr('data-hidden', '1');
  177. saveNavbarToServer();
  178. }
  179. },
  180. toAddedItems.length === 0
  181. ? {
  182. text: langData.add,
  183. disabled: true,
  184. }
  185. : {
  186. text: langData.add,
  187. items: toAddedItems,
  188. },
  189. {
  190. text: langData.restore,
  191. onClick: () => {
  192. restoreNavbarToServer({
  193. onSuccess() {
  194. loadCurrentPage('#navbar>*');
  195. }
  196. });
  197. }
  198. }
  199. ];
  200. if(window.customNavbarContextMenu) window.customNavbarContextMenu.hide();
  201. window.customNavbarContextMenu = zui.ContextMenu.show(
  202. {
  203. element: $item[0],
  204. placement: 'bottom-start',
  205. items: items,
  206. event: event,
  207. onClickItem: (info) => info.event.preventDefault(),
  208. onHide: () => window.customNavbarContextMenu = null,
  209. $optionsFromDataset: false,
  210. }
  211. );
  212. event.preventDefault();
  213. }
  214. window.initPageNavbar = function(navbarItems, workspace, activeMenu, spaceTag, menuOrders)
  215. {
  216. allNavbarItemNames.length = 0;
  217. allNavbarItemMap.clear();
  218. if(Array.isArray(menuOrders) && menuOrders.length)
  219. {
  220. const orderMap = new Map();
  221. for(const item of menuOrders) orderMap.set(item.name, item.order);
  222. if(!orderMap.has('more')) orderMap.set('more', 99999);
  223. let lastItem = null;
  224. navbarItems.forEach((item, index) => {
  225. item.order = orderMap.get(item.name || item.code || item['data-id']) || (lastItem ? lastItem.order + 1 : 0);
  226. lastItem = item;
  227. });
  228. navbarItems.sort((a, b) => a.order - b.order);
  229. }
  230. for(const item of navbarItems)
  231. {
  232. if(item.type === 'divider')
  233. {
  234. allNavbarItemNames.push('divider');
  235. continue;
  236. }
  237. const name = item['data-id'] || item.id;
  238. allNavbarItemNames.push(name);
  239. allNavbarItemMap.set(name, item);
  240. }
  241. updateWorkspaceUI(workspace ? {type: workspace, active: activeMenu, items: navbarItems, itemMap: allNavbarItemMap, saveToServer: saveNavbarToServer} : null);
  242. }