index.ui.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  1. /**
  2. * 获取列表类型。
  3. * Get the list type.
  4. *
  5. * @param {object} docApp
  6. * @returns {string}
  7. */
  8. function getListType(docApp)
  9. {
  10. docApp = docApp || getDocApp();
  11. if(docApp.mode !== 'list' || docApp.module) return '';
  12. const listType = docApp.signals.listType.value;
  13. return listType;
  14. }
  15. /**
  16. * 定义页面上的自定义渲染器。
  17. * Define the custom renderers on the page.
  18. */
  19. const customRenders =
  20. {
  21. /**
  22. * 落地页渲染,根据 API 接口类型显示不同的内容。
  23. * Home page render, show different content according to the API interface type.
  24. */
  25. home: function()
  26. {
  27. const docApp = this;
  28. const filterType = docApp.location.filterType || 'nolink';
  29. const pager = $.extend({page: 1, recPerPage: 20}, docApp.signals.pager.value);
  30. const params = docApp.signals.params.value || 'notempty_unclosed';
  31. const homeViewUrl = $.createLink('api', 'ajaxgethome', `type=${filterType}&params=${params}&recPerPage=${pager.recPerPage}&pageID=${pager.page}`);
  32. return {fetcher: homeViewUrl, clearBeforeLoad: false, className: 'doc-api-home h-full', class: 'h-full col',htmlRender: (element, props) => $(element).morphInner(`<div class="lazy-content doc-api-home h-full">${props.html}</div>`)};
  33. },
  34. /**
  35. * 定义 API 文档编辑器渲染,包括查看、编辑和创建。
  36. * Define the API doc editor render, including view, edit and create.
  37. */
  38. editor: function()
  39. {
  40. const mode = this.mode;
  41. if(mode === 'create')
  42. {
  43. const libID = this.libID;
  44. const moduleID = this.moduleID;
  45. return {fetcher: $.createLink('api', 'create', `libID=${libID}&moduleID=${moduleID}&space=api`), loadingText: getDocAppLang('loading')};
  46. }
  47. if(mode === 'edit')
  48. {
  49. return {fetcher: $.createLink('api', 'edit', `apiID=${this.docID}`), loadingText: getDocAppLang('loading')};
  50. }
  51. const docInfo = this.doc;
  52. const doc = docInfo ? docInfo.data : {id: this.docID, lib: this.libID, module: this.moduleID};
  53. if(!docAppHasPriv('view')) return {html: `<h1>${doc ? doc.title : ''}</h1><p>${getDocAppLang('accessDenied')}</p>`};
  54. const release = this.signals.libReleaseMap.value[doc.lib] || 0;
  55. const version = this.signals.docVersion.value || 0;
  56. return {fetcher: $.createLink('api', 'view', `libID=${doc.lib}&apiID=${doc.id}&moduleID=${doc.module}&version=${version}&release=${release}`), loadingText: getDocAppLang('loadingDocsData'), className: 'doc-editor-content'};
  57. },
  58. /**
  59. * 定义 API 文档列表渲染,包括结构和版本。
  60. * Define the API doc list render, including structs and releases.
  61. */
  62. list: function()
  63. {
  64. const listType = getListType(this);
  65. const libID = this.libID;
  66. const releaseID = this.signals.libReleaseMap.value[libID] || 0;
  67. if(listType === 'structs') return {fetcher: $.createLink('api', 'struct', `libID=${this.libID}&releaseID=${releaseID}`), loadingText: getDocAppLang('loading'), className: 'api-struct-list'};
  68. if(listType === 'releases') return {fetcher: $.createLink('api', 'releases', `libID=${this.libID}`), loadingText: getDocAppLang('loading'), className: 'api-release-list'};
  69. },
  70. /**
  71. * 定义 API 文档列表筛选菜单渲染,当展示结构和版本时不显示筛选菜单。
  72. * Define the API doc list filter menu render, not show the filter menu when showing structs and releases.
  73. */
  74. filters: function()
  75. {
  76. const listType = getListType(this);
  77. if(this.mode !== 'list' || !listType) return;
  78. return null;
  79. },
  80. /**
  81. * 定义 API 文档列表工具栏渲染。
  82. * Define the API doc list toolbar render.
  83. */
  84. toolbar: function()
  85. {
  86. const listType = getListType(this);
  87. if(this.mode === 'list' && listType)
  88. {
  89. const items = [];
  90. if(docAppHasPriv('createStruct') && listType === 'structs') items.push({text: getDocAppLang('createStruct'), icon: 'plus', btnType: 'primary', 'data-toggle': 'modal', 'data-size': 'lg', url: $.createLink('api', 'createStruct', `libID=${this.libID}`)});
  91. if(docAppHasPriv('createRelease') && listType === 'releases') items.push({text: getDocAppLang('createRelease'), icon: 'plus', btnType: 'primary', 'data-toggle': 'modal', url: $.createLink('api', 'createRelease', `libID=${this.libID}`)});
  92. if(!items.length) return null;
  93. return {component: 'toolbar', props: {items: items}};
  94. }
  95. },
  96. /**
  97. * 定义顶部面包屑导航渲染。
  98. * Define the top breadcrumb render.
  99. */
  100. 'app-nav': function(items)
  101. {
  102. const lib = this.lib;
  103. if(!lib) return items;
  104. const versions = lib.data.versions;
  105. if(versions && versions.length)
  106. {
  107. const viewIndex = items.findIndex(item => item[0] === 'lib');
  108. if(viewIndex >= 0)
  109. {
  110. const libView = items[viewIndex][1];
  111. const libID = lib.data.id;
  112. const release = this.signals.libReleaseMap.value[libID] || 0;
  113. const options = [{text: getDocAppLang('latestVersion'), value: 0, selected: !release, command: `changeLibRelease/${libID}/0`}];
  114. versions.forEach(version => options.push({selected: release === version.id, text: `v${version.version}`, value: version.id, command: `changeLibRelease/${libID}/${version.id}`}));
  115. const currentVersion = versions.find(x => x.id === release);
  116. const versionPicker = zui.renderCustomContent(
  117. {
  118. content:
  119. {
  120. component: 'DropdownButton',
  121. props:
  122. {
  123. text : currentVersion ? currentVersion.version : getDocAppLang('latestVersion'),
  124. size : 'xs',
  125. type : 'gray-pale',
  126. rounded : 'full',
  127. className: 'h-4 gap-1 mr-2',
  128. items : options,
  129. }
  130. }
  131. });
  132. items[viewIndex] = ['lib', [libView, versionPicker]];
  133. }
  134. }
  135. const listType = getListType(this);
  136. if(this.mode !== 'list' || !listType) return items;
  137. if(listType === 'structs') items.push([listType, zui.renderCustomContent({className: 'mx-2', content: getDocAppLang('struct')})]);
  138. if(listType === 'releases') items.push([listType, zui.renderCustomContent({className: 'mx-2', content: getDocAppLang('releases')})]);
  139. return items;
  140. },
  141. /**
  142. * 定义侧边栏渲染,显示结构、版本和模块。
  143. * Define the sidebar render, show structs, releases and modules.
  144. */
  145. 'sidebar-before': function()
  146. {
  147. if(!this.libID) return;
  148. const canViewStructs = docAppHasPriv('struct');
  149. const canViewReleases = docAppHasPriv('releases');
  150. if(!canViewStructs && !canViewReleases) return;
  151. const isListMode = this.mode === 'list';
  152. const lang = getDocAppLang();
  153. const listType = getListType(this);
  154. const items = [];
  155. if(canViewStructs) items.push({text: lang.struct, selected: listType === 'structs' && isListMode, icon: 'treemap muted', command: 'showStructs'}, {type: 'divider', className: 'my-1'});
  156. if(canViewReleases) items.push({text: lang.releases, selected: listType === 'releases' && isListMode, icon: 'version muted', command: 'showReleases'}, {type: 'divider', className: 'my-1'});
  157. items.push({text: lang.module, icon: 'list muted', command: 'showModules'});
  158. return {
  159. component: 'tree',
  160. className: 'p-2 pb-0 api-lib-menu',
  161. props: {items: items, itemProps: {className: 'state'}}
  162. };
  163. }
  164. };
  165. /**
  166. * 获取查看视图的 URL,用于更新浏览器地址栏。
  167. * Get the view mode URL for updating the browser address bar.
  168. */
  169. function getViewModeUrl(options)
  170. {
  171. const doc = this.doc;
  172. options = $.extend({release: (doc ? this.signals.libReleaseMap.value[doc.lib] : 0) || 0}, options);
  173. let url;
  174. if(this.mode === 'view')
  175. {
  176. const params = zui.formatString('libID={libID}&apiID={docID}&moduleID={moduleID}&version={docVersion}&release={release}', options).replace('&version=0&release=0', '');
  177. url = $.createLink('api', 'view', params);
  178. }
  179. else
  180. {
  181. const params = zui.formatString('libID={libID}&moduleID={moduleID}&apiID={docID}&version={docVersion}&release={release}&browseType={filterType}&params={params}&orderBy={orderBy}&recTotal={recTotal}&recPerPage={recPerPage}&pageID={page}&mode={mode}&search={search}', options).replace('libID=0&moduleID=0&apiID=0&version=0&release=0&browseType=all&params=0&orderBy=order_asc&recTotal=0&recPerPage=20&pageID=1&mode=&search=', '').replace('&apiID=0&version=0&release=0&browseType=all&params=0&orderBy=order_asc&recTotal=0&recPerPage=20&pageID=1&mode=&search=', '').replace('&browseType=all&params=0&orderBy=order_asc&recTotal=0&recPerPage=20&pageID=1&mode=&search=', '');
  182. url = $.createLink('api', 'index', params);
  183. }
  184. return url;
  185. }
  186. /**
  187. * 获取文档详情侧边栏标签页定义。
  188. * Get the doc view sidebar tabs.
  189. *
  190. * @param {object} doc
  191. */
  192. function getDocViewSidebarTabs(doc, info)
  193. {
  194. const lang = getDocAppLang();
  195. if(info.isNewDoc || info.mode === 'create') return [];
  196. return [
  197. {key: 'info', icon: 'info', title: lang.docInfo},
  198. info.mode === 'edit' ? null : {key: 'outline', icon: 'list-box', title: lang.docOutline},
  199. {key: 'history', icon: 'file-log', title: lang.history},
  200. ].filter(Boolean);
  201. }
  202. function getSpaceFetcher(spaceType, spaceID)
  203. {
  204. const parts = String(spaceID).split('.');
  205. const objectType = parts[0] || 'nolink';
  206. const objectID = parts[1] || 0;
  207. const libID = getDocApp().libID;
  208. return $.createLink('api', 'ajaxGetDropMenu', `objectType=${objectType}&objectID=${objectID}&libID=${libID}`);
  209. }
  210. function handleClickSpaceMenu(event, value)
  211. {
  212. event.preventDefault();
  213. if($(event.target).closest('.dropmenu-nav').length) return;
  214. const parts = String(value).split('.');
  215. const type = parts[0];
  216. const id = parts[1] || 0;
  217. const docApp = getDocApp();
  218. if(type === 'product' || type === 'project') docApp.selectSpace(value, true, true);
  219. else docApp.selectSpace('nolink', +id, true);
  220. }
  221. function getSpaceMenuText(text, state)
  222. {
  223. const spaceID = this.spaceID;
  224. if(spaceID === 'nolink') return text;
  225. const libTypeList = getDocAppLang('libTypeList');
  226. return `${libTypeList[spaceID.split('.')[0]]} / ${text}`;
  227. }
  228. function getSpaceMenuOptions(spaceType, spaceID)
  229. {
  230. const libID = this.libID;
  231. const lang = $('html').attr('lang');
  232. return {
  233. caret : true,
  234. popWidth : ['zh-cn', 'zh-tw'].includes(lang) ? 350 : 400,
  235. onClickItem : handleClickSpaceMenu,
  236. defaultValue: spaceID === 'nolink' ? `lib.${libID}`: spaceID,
  237. display : getSpaceMenuText.bind(this)
  238. };
  239. }
  240. /* 扩展文档应用操作按钮生成定义。 Extend the doc app action definition. */
  241. $.extend(window.docAppActions,
  242. {
  243. /**
  244. * 定义文档编辑时的操作按钮。
  245. * Define the actions on toolbar of the doc editing page.
  246. */
  247. 'doc-edit': function(info)
  248. {
  249. const doc = info.data;
  250. if(!doc) return;
  251. const lang = getDocAppLang();
  252. return [
  253. {text: lang.save, size: 'md', className: 'btn-wide', type: 'primary', command: 'saveApiDoc'},
  254. {text: lang.cancel, size: 'md', className: 'btn-wide', type: 'primary-outline', command: 'cancelEditDoc'},
  255. ];
  256. },
  257. /**
  258. * 定义文档创建时的操作按钮。
  259. * Define the actions on toolbar of the doc editing page.
  260. */
  261. 'doc-create': function(info)
  262. {
  263. const doc = info.data;
  264. if(!doc) return;
  265. const lang = getDocAppLang();
  266. return [
  267. {text: lang.save, size: 'md', className: 'btn-wide', type: 'primary', command: 'saveApiDoc'},
  268. {text: lang.cancel, size: 'md', className: 'btn-wide', type: 'primary-outline', command: 'cancelCreateDoc'},
  269. ];
  270. },
  271. });
  272. /* 扩展文档应用命令定义。 Extend the doc app command definition. */
  273. $.extend(window.docAppCommands,
  274. {
  275. /**
  276. * 保存 API 文档数据。
  277. * Save the API doc data.
  278. */
  279. saveApiDoc: function()
  280. {
  281. /* 触发 API 表单的提交事件。 */
  282. $('#docApp .doc-view form').trigger('submit');
  283. },
  284. /**
  285. * 取消编辑 API 文档库。
  286. * Create api lib.
  287. */
  288. createLib: function(_, args)
  289. {
  290. const docApp = getDocApp();
  291. const spaceID = args[0] || docApp.spaceID;
  292. const parts = String(spaceID).split('.');
  293. const url = $.createLink('api', 'createLib', `type=${parts[0] || 'nolink'}&objectID=${parts[1] || 0}`);
  294. zui.Modal.open({size: 'sm', url: url});
  295. },
  296. /**
  297. * 加载指定的 API 文档。
  298. * Load the specified API doc.
  299. */
  300. loadApi: function(_, args)
  301. {
  302. const apiID = args[0] || this.docID;
  303. const version = args[1] || 0;
  304. const release = args[2] || 0;
  305. const select = !!args[3];
  306. $.getJSON($.createLink('api', 'ajaxGetApi', `apiID=${apiID}&version=${version}&release=${release}`), function(result)
  307. {
  308. if(!result || typeof result !== 'object') return;
  309. const docApp = getDocApp();
  310. docApp.update('doc', result);
  311. if(select) docApp.selectDoc(apiID);
  312. });
  313. },
  314. /**
  315. * 选择 API 文档。
  316. * Select the specified API doc.
  317. */
  318. selectApi: function(_, args)
  319. {
  320. const apiID = args[0];
  321. getDocApp().selectDoc(apiID);
  322. },
  323. /**
  324. * 显示结构列表。
  325. * Show the struct list.
  326. */
  327. showStructs: function()
  328. {
  329. getDocApp().changeState(
  330. {
  331. mode: 'list',
  332. moduleID: 0,
  333. docID: 0,
  334. listType: 'structs',
  335. });
  336. },
  337. /**
  338. * 显示版本列表。
  339. * Show the release list.
  340. */
  341. showReleases: function()
  342. {
  343. getDocApp().changeState(
  344. {
  345. mode: 'list',
  346. moduleID: 0,
  347. docID: 0,
  348. listType: 'releases',
  349. });
  350. },
  351. /**
  352. * 显示API 目录。
  353. * Show the api catalog.
  354. */
  355. showModules: function()
  356. {
  357. getDocApp().changeState(
  358. {
  359. mode: 'list',
  360. moduleID: 0,
  361. docID: 0,
  362. listType: '',
  363. });
  364. },
  365. /**
  366. * 获取库的 API 列表。
  367. * Get the API list of the library.
  368. */
  369. loadLibApi: function(_, args)
  370. {
  371. const docApp = getDocApp();
  372. const libReleaseMap = docApp.signals.libReleaseMap.value;
  373. const libID = args[0] || docApp.libID;
  374. if(!libID) return;
  375. const releaseID = args[1] || libReleaseMap[libID] || 0;
  376. const url = $.createLink('api', 'ajaxGetLibApiList', `libID=${libID}&releaseID=${releaseID}`);
  377. $.getJSON(url, function(data)
  378. {
  379. const lib = docApp.getLib(libID);
  380. if(lib && lib.docs && lib.docs.length) docApp.delete('doc', lib.docs.map(x => x.data.id));
  381. docApp.update('doc', data);
  382. });
  383. },
  384. /**
  385. * 更改当前库的发布版本。
  386. * Change the release of the current library.
  387. */
  388. changeLibRelease: function(_, args)
  389. {
  390. const docApp = getDocApp();
  391. const libID = args[0];
  392. const release = args[1];
  393. const libReleaseMap = docApp.signals.libReleaseMap.value;
  394. if(libReleaseMap[libID] === release) return;
  395. libReleaseMap[libID] = release;
  396. docApp.signals.libReleaseMap.value = $.extend({}, libReleaseMap);
  397. docApp.executeCommand('loadLibApi', [libID, release]);
  398. const doc = docApp.doc;
  399. if(doc && doc.data.lib === libID) docApp.executeCommand('loadApi', [doc.data.id, 0, release]);
  400. },
  401. /**
  402. * 加载懒加载内容。
  403. * Load the lazy content.
  404. */
  405. loadLazyContent: function(_, args)
  406. {
  407. const selector = args[0];
  408. if(!selector) return;
  409. $(selector).closest('.lazy-content').trigger('loadContent');
  410. },
  411. /**
  412. * 更新懒加载内容。
  413. * Update the lazy content.
  414. */
  415. updateLazyContent: function(context, args)
  416. {
  417. const event = context.event;
  418. const $element = $(event.currentTarget);
  419. const selector = args[0] || $element.data('lazyTarget');
  420. const $lazy = (selector ? $(selector) : $element).closest('.lazy-content');
  421. const url = $element.data('url') || $element.attr('href');
  422. if(url) $lazy.trigger('loadContent', url);
  423. event.preventDefault();
  424. event.stopPropagation();
  425. },
  426. /**
  427. * 更新首页内容。
  428. * Update the home content.
  429. */
  430. loadHome: function(context, args)
  431. {
  432. const event = context.event;
  433. const $element = $(event.currentTarget);
  434. const data = $element.data();
  435. const type = args[0] !== undefined ? args[0] : data.type;
  436. const params = args[1] !== undefined ? args[1] : data.params;
  437. const recPerPage = args[2] !== undefined ? args[2] : data.recPerPage;
  438. const pageID = args[3] !== undefined ? args[3] : data.pageID;
  439. window.loadHome(type, params, recPerPage, pageID);
  440. },
  441. /**
  442. * 显示首页菜单。
  443. * Show the home item menu.
  444. */
  445. showHomeItemMenu: function(context, args)
  446. {
  447. const event = context.event;
  448. const type = args[0];
  449. const id = args[1];
  450. const lang = getDocAppLang();
  451. const items = [];
  452. if(type === 'nolink')
  453. {
  454. if(docAppHasPriv('editLib')) items.push({text: lang.actions.editLib, command: `editLib/${id}`});
  455. if(docAppHasPriv('deleteLib')) items.push({text: lang.actions.deleteLib, command: `deleteLib/${id}`});
  456. }
  457. else if(docAppHasPriv('createLib'))
  458. {
  459. items.push({text: lang.createLib, command: `createLib/${type}.${id}`});
  460. }
  461. if(items.length)
  462. {
  463. zui.ContextMenu.show({event: event, items: items, placement: 'bottom-end'});
  464. }
  465. event.preventDefault();
  466. event.stopPropagation();
  467. },
  468. });
  469. window.loadHome = function(type, params, recPerPage, pageID)
  470. {
  471. const location = {};
  472. if(typeof type === 'object') $.extend(location, type);
  473. else if(type !== undefined) location.filterType = type;
  474. if(params !== undefined) location.params = params;
  475. if(recPerPage !== undefined || pageID !== undefined)
  476. {
  477. location.pager = {};
  478. if(recPerPage !== undefined) location.pager.recPerPage = recPerPage;
  479. if(pageID !== undefined) location.pager.page = pageID;
  480. }
  481. if(Object.keys(location).length) getDocApp().switchView(location, 'home');
  482. };
  483. /**
  484. * 重写文档应用的配置选项方法。
  485. * Override the method to set the doc app options.
  486. */
  487. window._setDocAppOptions = window.setDocAppOptions; // Save the original method.
  488. window.setDocAppOptions = function(_, options) // Override the method.
  489. {
  490. options = window._setDocAppOptions(_, options);
  491. const oldIsMatchFilter = options.isMatchFilter; // Save the original isMatchFilter method.
  492. return $.extend(options,
  493. {
  494. defaultState : {libReleaseMap: {}, listType: ''},
  495. spaceMenuOptions : getSpaceMenuOptions,
  496. customRenders : customRenders,
  497. viewModeUrl : getViewModeUrl,
  498. getDocViewSidebarTabs: getDocViewSidebarTabs,
  499. getSpaceFetcher : getSpaceFetcher,
  500. isMatchFilter : function(type, filterType, item)
  501. {
  502. if(type === 'api') return (item.objectType || 'nolink').toLowerCase() === filterType.toLowerCase();
  503. return oldIsMatchFilter.call(this, type, filterType, item);
  504. }
  505. });
  506. };