zentaomax.ui.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. /**
  2. * 重写文档应用的配置选项方法。
  3. * Override the method to set the doc app options.
  4. */
  5. window._setDocAppOptions = window.setDocAppOptions; // Save the original method.
  6. window.setDocAppOptions = function(_, options) // Override the method.
  7. {
  8. options = window._setDocAppOptions(_, options);
  9. currentUser = options.currentUser;
  10. return $.extend(options,
  11. {
  12. viewModeUrl : getViewModeUrl,
  13. customRenders : $.extend(docAppCustomRenders, customRenders),
  14. getDocViewSidebarTabs: getDocViewSidebarTabs
  15. });
  16. };
  17. const customRenders =
  18. {
  19. 'app-nav': function(items)
  20. {
  21. const docApp = getDocApp();
  22. if(docApp && docApp.mode == 'view')
  23. {
  24. const homeUrl = $.createLink('weekly', 'browse', `projectID=${projectID}&browseType=all`);
  25. const homeText = getLang('reportList');
  26. const homeButton = zui.renderCustomContent({className: 'homeButton', content: {html: `<a class='btn ghost text-primary square size-md ml-1' href=${homeUrl}><i class="icon-home mr-0.5"></i>${homeText}</a>`}});
  27. items[0] = ['space', [homeButton]];
  28. }
  29. const moduleIndex = items.findIndex(item => item[0].includes('-angle'));
  30. if(moduleIndex > 0) items.splice(moduleIndex, 1);
  31. return items;
  32. }
  33. }
  34. $.extend(window.docAppActions,
  35. {
  36. doc: function(info)
  37. {
  38. const lang = getLang();
  39. const doc = info.data;
  40. const docApp = this;
  41. const canEditDoc = hasPriv('edit');
  42. if(doc.content && !doc.hasContent)
  43. {
  44. async function initHtmlByRawContent(rawContent)
  45. {
  46. await zui.Editor.loadModule();
  47. const html = await zui.Editor.convertToHtml(rawContent);
  48. $.post($.createLink('weekly', 'ajaxSetContent', `reportID=${doc.id}&version=${doc.version}`), {content: html})
  49. };
  50. initHtmlByRawContent(doc.content);
  51. }
  52. const moreItems = [];
  53. if(hasPriv('delete')) moreItems.push({icon: 'trash', text: lang.delete, command: `deleteDoc/${doc.id}`});
  54. if(hasPriv('exportReport')) moreItems.push({icon: 'export', text: lang.export, command: `exportWord/${doc.id}/${doc.title}`});
  55. const isToolbar = info.ui === 'toolbar';
  56. const isInModal = $(this.element).closest('.modal').length;
  57. const actions = isInModal ? [] : [
  58. isToolbar ? {hint: docApp.fullscreen ? lang.exitFullscreen : lang.enterFullscreen, icon: docApp.fullscreen ? 'fullscreen-exit' : 'fullscreen', command: 'toggleFullscreen'} : null,
  59. canEditDoc ? {icon: 'edit', class: doc.editable ? null : 'disabled', type: doc.editable ? 'ghost text-primary' : 'ghost text-gray', hint: doc.editable ? lang.edit : lang.needEditable, rounded: 'lg', command: doc.editable ? 'startEditDoc' : null} : null,
  60. (isToolbar && docApp.props.showDocOutline !== false) ? {hint: lang.docOutline, icon: 'list-box', command: 'toggleViewSideTab/outline'} : null,
  61. (isToolbar && docApp.props.showDocHistory !== false) ? {hint: lang.history, icon: 'file-log', command: 'toggleViewSideTab/history'} : null,
  62. moreItems.length ? {icon: 'icon-ellipsis-v', type: 'dropdown', rounded: 'lg', placement: 'bottom-end', caret: false, items: moreItems} : null,
  63. ];
  64. return actions;
  65. },
  66. /**
  67. * 定义文档编辑时的操作按钮。
  68. * Define the actions on toolbar of the doc editing page.
  69. */
  70. 'doc-edit': function(info)
  71. {
  72. const doc = info.data;
  73. if(!doc) return;
  74. const lang = getLang();
  75. const isToolbar = info.ui === 'toolbar';
  76. const docApp = this;
  77. return [
  78. isToolbar ? {hint: docApp.fullscreen ? lang.exitFullscreen : lang.enterFullscreen, icon: docApp.fullscreen ? 'fullscreen-exit' : 'fullscreen', command: 'toggleFullscreen'} : null,
  79. (isToolbar && docApp.props.showDocOutline !== false) ? {hint: lang.docOutline, icon: 'list-box', command: 'toggleViewSideTab/outline'} : null,
  80. (isToolbar && docApp.props.showDocHistory !== false) ? {hint: lang.history, icon: 'file-log', command: 'toggleViewSideTab/history'} : null,
  81. (isToolbar && docApp.props.quickEditMenu) ? {hint: lang.insertSystemData, icon: 'controls', command: 'toggleViewSideTab/quick-edit-menu'} : null,
  82. {text: lang.refreshData, type: 'ghost', command: `refreshData/${doc.project}`, icon: 'refresh'},
  83. {text: lang.settings, size: 'md', type: 'ghost', command: `showSettingModal/${doc.id}`, icon: 'cog-outline'},
  84. doc.status === 'draft' ? {text: lang.saveDraft, size: 'md', className: 'btn-wide', type: 'secondary', command: `saveDoc/draft`} : null,
  85. {text: lang.release, size: 'md', className: 'btn-wide', type: 'primary', command: `saveDoc`},
  86. {text: lang.cancel, size: 'md', className: 'btn-wide', type: 'primary-outline', command: 'exitEditDoc'}
  87. ];
  88. }
  89. });
  90. $.extend(window.docAppCommands,
  91. {
  92. showSettingModal: function(_, args)
  93. {
  94. const docApp = getDocApp();
  95. const docID = args[0] || docApp.docID;
  96. const url = $.createLink('weekly', 'ajaxSetBasic', `docID=${docID || 0}`);
  97. zui.Modal.open({url: url});
  98. },
  99. refreshData: function(_, args)
  100. {
  101. zui.Modal.confirm(getLang('confirmRefresh')).then(result =>
  102. {
  103. if(!result) return;
  104. refreshReportData(args[0]);
  105. });
  106. },
  107. exportWord: function(_, args)
  108. {
  109. const docApp = getDocApp();
  110. const docID = args[0] || docApp.docID;
  111. let title = args[1] || 'undefined';
  112. const form = new FormData();
  113. $('.zentao-chart').each(function()
  114. {
  115. const $this = $(this);
  116. const blockID = $this.data('id');
  117. const $chartBox = $this.find('.chartBox');
  118. if($chartBox.length == 0)
  119. {
  120. form.append(`charts[${blockID}][data]`, $this.find('.canvas').text());
  121. return;
  122. }
  123. let chartType = '';
  124. if($chartBox.hasClass('table')) chartType = 'table';
  125. if($chartBox.hasClass('chart')) chartType = 'image';
  126. if($chartBox.hasClass('gantt')) chartType = 'gantt';
  127. form.append(`charts[${blockID}][type]`, chartType);
  128. if(chartType == 'table') form.append(`charts[${blockID}][data]`, $chartBox.find('table').length ? $chartBox.find('table').prop('outerHTML') : $chartBox.text());
  129. if(chartType == 'image') form.append(`charts[${blockID}][data]`, $chartBox.find('canvas').length ? $chartBox.find('canvas').get(0).toDataURL('image/png') : $chartBox.text());
  130. if(chartType == 'gantt') form.append(`charts[${blockID}][data]`, location.origin + $chartBox.data('link'));
  131. });
  132. const url = $.createLink('weekly', `exportReport`, `docID=${docID}`);
  133. $.post(url, form, function(blob)
  134. {
  135. const urlObject = URL.createObjectURL(blob);
  136. const link = document.createElement('a');
  137. link.href = urlObject;
  138. if(!isNaN(title) && title !== '') title = 'doc_' + title;
  139. link.download = decodeURIComponent(title.replace(/"/g, '') + '.doc');
  140. document.body.appendChild(link);
  141. link.click();
  142. document.body.removeChild(link);
  143. URL.revokeObjectURL(urlObject);
  144. });
  145. },
  146. deleteDoc: function(_, args)
  147. {
  148. const docApp = getDocApp();
  149. const docID = args[0] || docApp.docID;
  150. $.ajaxSubmit(
  151. {
  152. confirm: getLang('confirmDelete'),
  153. url: $.createLink('weekly', 'delete', `docID=${docID}`),
  154. load: false,
  155. onSuccess: function()
  156. {
  157. getDocApp().delete('doc', docID);
  158. loadPage($.createLink('weekly', 'browse', `projectID=${projectID}`));
  159. }
  160. });
  161. },
  162. startEditDoc: function(_, [doc], options)
  163. {
  164. const docApp = getDocApp();
  165. loadPage($.createLink('weekly', 'edit', `reportID=${docApp.docID}`));
  166. },
  167. exitEditDoc: function(_, [doc], options)
  168. {
  169. const docApp = getDocApp();
  170. loadPage($.createLink('weekly', 'view', `reportID=${docApp.docID}`));
  171. }
  172. })
  173. /**
  174. * 获取查看视图的 URL,用于更新浏览器地址栏。
  175. * Get the view mode URL for updating the browser address bar.
  176. */
  177. function getViewModeUrl(options)
  178. {
  179. if(options.mode == 'view') return $.createLink('weekly', 'view', `reportID=${options.docID}`);
  180. if(options.mode == 'edit') return $.createLink('weekly', 'edit', `reportID=${options.docID}`);
  181. return $.createLink('weekly', 'browse', `projectID=${options.project}`);
  182. };
  183. /*
  184. * 插入禅道列表。
  185. * Insert the Zentao list.
  186. */
  187. window.insertZentaoMeasurement = function(type, props, isNewBlock)
  188. {
  189. if(!isNewBlock) return getDocApp().editor.$.updateHolderText({name: `${type}_${props.blockID}`, text: `${props.text}`, hint: props.hint});
  190. getDocApp().editor.$.insertHolderText(`${type}_${props.blockID}`, {text: `${props.text}`, hint: props.hint}, props);
  191. };
  192. /*
  193. * 插入执行度量数据。
  194. * Insert the execution list.
  195. */
  196. window.insertExecutionMeasurement = function(e, info)
  197. {
  198. $.getJSON($.createLink('weekly', 'ajaxGetMeasurement', `type=${info.item['data-type']}&projectID=${info.item['data-project']}`), function(count)
  199. {
  200. getDocApp().editor.$.insertHolderText(info.item['data-type'], {text: `${count}`, hint: info.item.hint});
  201. })
  202. };
  203. /*
  204. * 点击占位符。
  205. * Click the holder.
  206. */
  207. window.clickHolder = function(info)
  208. {
  209. const docApp = getDocApp();
  210. if(docApp.mode != 'edit') return;
  211. if(!info.holder.data) return;
  212. if(!info.holder.data.blockID) return;
  213. const url = $.createLink('reporttemplate', 'ajaxBuildZentaoMeasurementConfig', `type=${info.holder.data.type}&blockID=${info.holder.data.blockID}&projectID=${projectID}`);
  214. zui.Modal.open({url: url});
  215. };
  216. /* 从编辑器删除图表。*/
  217. window.deleteZentaoChart = function(blockID)
  218. {
  219. const $block = $('#docApp').find(`.zentao-chart[data-id="${blockID}"]`);
  220. if(!$block.length) return false;
  221. getDocApp().editor.$.deleteBlock($block[0]);
  222. };
  223. window.getDocViewSidebarTabs = function(doc, info)
  224. {
  225. const docApp = getDocApp();
  226. const tabs = [
  227. {key: 'outline', icon: 'list-box', title: getLang('docOutline')},
  228. {key: 'history', icon: 'file-log', title: getLang('history')}
  229. ]
  230. /* 定义快捷编辑菜单标签页。 */
  231. const quickEditMenu = docApp ? docApp.props.quickEditMenu : '';
  232. if(quickEditMenu && quickEditMenu.length && docApp.mode == 'edit')
  233. {
  234. tabs.push({
  235. key: 'quick-edit-menu',
  236. icon: 'controls',
  237. render: function(doc)
  238. {
  239. const navHtml = [];
  240. const contentHtml = [];
  241. if(!docApp.quickEditMenuActiveTab) docApp.quickEditMenuActiveTab = quickEditMenu[0].key;
  242. for (const tab of quickEditMenu)
  243. {
  244. const isActive = docApp.quickEditMenuActiveTab === tab.key;
  245. const id = `quick-edit-menu-tab-${tab.key}`;
  246. navHtml.push(`<li class="nav-item"><a class="${isActive ? 'active' : ''}" data-toggle="tab" href="#${id}">${tab.text}</a></li>`);
  247. const itemsCode = zui.jsRaw(tab.items).replaceAll('"', "&quot;").replaceAll('<', '&lt;').replaceAll('>', '&gt;');
  248. contentHtml.push(`<div class="tab-pane fade duration-500${isActive ? ' active in' : ''}" id="${id}" data-key="${tab.key}"><menu zui-create="tree" zui-create-tree="{preserve: 'quick-edit-menu-tree-${tab.key}', items: ${itemsCode}, getItem: window.getQuickEditMenuItem}"></menu></div>`);
  249. }
  250. return {html: `<ul class="nav nav-tabs">${navHtml.join('')}</ul><div class="tab-content">${contentHtml.join('')}</div>`, className: 'quick-edit-menu', executeScript: true};
  251. }
  252. });
  253. }
  254. return tabs;
  255. };
  256. /**
  257. * 获取快捷编辑菜单项。
  258. * Get the quick edit menu item.
  259. *
  260. * @param {object} item
  261. * @returns {object}
  262. */
  263. window.getQuickEditMenuItem = function(item)
  264. {
  265. const holder = item.holder;
  266. if(holder && !item.command && !item.url)
  267. {
  268. const commandParts = [holder.block ? 'editor$.insertHolderBlock' : 'editor$.insertHolderText', encodeURIComponent(holder.name)];
  269. commandParts.push(encodeURIComponent(JSON.stringify({text: holder.text, hint: item.text})));
  270. if(holder.data) commandParts.push(encodeURIComponent(JSON.stringify(holder.data)));
  271. return $.extend({url: `#!${commandParts.join('/')}`}, item);
  272. }
  273. return item;
  274. };
  275. /* 插入甘特图。*/
  276. window.insertExecutionChart = function(e, info)
  277. {
  278. const doc = getDocApp().doc;
  279. $.ajaxSubmit({
  280. url: $.createLink('reporttemplate', 'ajaxBuildZentaoChartConfig', `type=${info.item['data-type']}&blockID=0&projectID=${info.item['data-project']}`),
  281. data: {templateID: doc.data.id}
  282. });
  283. }
  284. /* 设置图表配置。*/
  285. function getZentaoChartProps(type, blockID, props)
  286. {
  287. if(props && props.isTemplate) blockID = '__TML_ZENTAOCHART__' + blockID;
  288. return $.extend(
  289. {
  290. exportUrl: `exportZentaoChart_${blockID}`,
  291. fetcher: $.createLink('reporttemplate', 'ajaxZentaoChart', `type=${type}&blockID=${blockID}&projectID=${projectID}`),
  292. clearBeforeLoad: false
  293. }, props);
  294. }
  295. /* 插入图表。*/
  296. window.insertZentaoChart = function(type, blockID, props, replaceBlockID)
  297. {
  298. if(Number(replaceBlockID)) return replaceZentaoChart(replaceBlockID, type, blockID, props);
  299. getDocApp().editor.$.insertCustom(getZentaoChartProps(type, blockID, props));
  300. }
  301. /* 替换禅道图表。*/
  302. window.replaceZentaoChart = function(oldBlockID, newType, newblockID, props)
  303. {
  304. return updateZentaoChart(oldBlockID, {content: getZentaoChartProps(newType, newblockID, props), renderID: $.guid++});
  305. };
  306. /* 更新禅道图表。*/
  307. window.updateZentaoChart = function(blockID, props)
  308. {
  309. const $block = $('#docApp').find(`.zentao-chart[data-id="${blockID}"]`);
  310. if(!$block.length) return false;
  311. getDocApp().editor.$.updateBlock($block[0], props);
  312. };
  313. /* 更新报告数据。*/
  314. function refreshReportData(projectID)
  315. {
  316. const formData = new FormData();
  317. const $holderElements = $('#docApp .affine-zui-holder');
  318. const $listElements = $('#docApp .zentao-list');
  319. const $chartElements = $('#docApp .zentao-chart');
  320. $holderElements.each(function()
  321. {
  322. const holderName = $(this).attr('data-holder-name');
  323. formData.append('holders[]', holderName);
  324. });
  325. $listElements.each(function()
  326. {
  327. formData.append('zentaoList[]', $(this).attr('data-id'));
  328. });
  329. $chartElements.each(function()
  330. {
  331. formData.append('zentaoChart[]', $(this).attr('data-id'));
  332. });
  333. $.post($.createLink('weekly', 'ajaxRefreshData', `projectID=${projectID}`), formData, function(result)
  334. {
  335. result = JSON.parse(result);
  336. if(result.result == 'success')
  337. {
  338. /* Update property and measurement. */
  339. for(let key in result.data.holders)
  340. {
  341. const hint = $(`#docApp [data-holder-name=${key}] .affine-zui-holder-container`).attr('title');
  342. getDocApp().editor.$.updateHolderText({name: key, text: result.data.holders[key].toString(), hint: hint})
  343. }
  344. /* Update task, story list data. */
  345. for(let key in result.data.zentaoList)
  346. {
  347. const zentaoList = result.data.zentaoList[key];
  348. window.insertZentaoList(zentaoList.type, zentaoList.newBlockID, zentaoList.props, zentaoList.oldBlockID);
  349. }
  350. /* Update chart data. */
  351. for(let key in result.data.zentaoChart)
  352. {
  353. const zentaoChart = result.data.zentaoChart[key];
  354. window.insertZentaoChart(zentaoChart.type, zentaoChart.newBlockID, zentaoChart.props, zentaoChart.oldBlockID);
  355. }
  356. }
  357. });
  358. }