class-list.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. class ClassListModule {
  2. static SearchQuery = '';
  3. static ModeCookieName = 'doczilla-js-docs-class-list-mode';
  4. static OpenedFoldersCookieName = 'doczilla-js-docs-class-list-opened-folders';
  5. static Mode = {
  6. Structurized: 'structurized',
  7. Unstructurized: 'unstructurized'
  8. };
  9. static _Mode = ClassListModule.Mode.Structurized;
  10. static setQuery(query, reload) {
  11. ClassListModule.SearchQuery = query.toLowerCase();
  12. if(reload)
  13. ClassListModule.reload();
  14. }
  15. static load() {
  16. ClassListModule.classListElement = ClassListModule.classListElement ? ClassListModule.classListElement : DOM.get('.class-list');
  17. DOM.get('.class-list-mode-button.structurized').switchClass('selected', ClassListModule._Mode === ClassListModule.Mode.Structurized);
  18. DOM.get('.class-list-mode-button.unstructurized').switchClass('selected', ClassListModule._Mode === ClassListModule.Mode.Unstructurized);
  19. ClassListModule.loadClasses(ClassListModule._Mode);
  20. }
  21. static loadClasses(mode) {
  22. let classList = {};
  23. if(mode === ClassListModule.Mode.Structurized) {
  24. for(const root of RepoNames) {
  25. classList[root] = { type: 'dir', contents: {} };
  26. Object.keys(ClassList).filter((key) => {
  27. return ClassList[key].filePath.indexOf(root) === 0 && (ClassListModule.SearchQuery.length == 0 || key.toLowerCase().includes(ClassListModule.SearchQuery) || (ClassList[key].shortName || '').toLowerCase().includes(ClassListModule.SearchQuery));
  28. }).sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase())).map((key) => {
  29. classList[root].contents[key] = { type: 'cls', class: ClassList[key] };
  30. });
  31. }
  32. classList = ClassListModule.transformStructure(classList);
  33. Object.keys(classList).forEach((key) => {
  34. if(Object.keys(classList[key].contents).length > 0)
  35. ClassListModule.renderDir(key, key, classList[key].contents, ClassListModule.classListElement);
  36. });
  37. } else {
  38. Object.keys(ClassList).sort((a, b) => {
  39. return a.toLowerCase().localeCompare(b.toLowerCase());
  40. }).filter((key) => {
  41. return ClassListModule.SearchQuery.length == 0 || key.toLowerCase().includes(ClassListModule.SearchQuery) || (ClassList[key].shortName || '').toLowerCase().includes(ClassListModule.SearchQuery);
  42. }).map((key) => { classList[key] = ClassList[key]; });
  43. for(const className of Object.keys(classList)) {
  44. const icon = DOM.create({ tag: 'div', cls: 'class-icon' });
  45. const name = DOM.create({ tag: 'div', cls: 'class-name', innerHTML: className });
  46. DOM.create({
  47. tag: 'div',
  48. cls: `class-item ${ Class ? (Class.name === className ? 'selected' : '') : '' }`,
  49. cn: [icon, name],
  50. attr: {
  51. 'data-class-name': className,
  52. 'data-class-shortname': classList[className].shortName,
  53. 'title': className
  54. }
  55. }, ClassListModule.classListElement).on('mousedown', ClassListModule.onListItemMouseDown);
  56. }
  57. }
  58. }
  59. static renderDir(dirName, dirPath, dirContents, parentContainer) {
  60. if(ClassListModule.SearchQuery.length > 0 && Object.keys(dirContents).length === 0)
  61. return;
  62. const dirCollapsedIconEl = DOM.create({ tag: 'div', cls: 'dir-collapsed-icon' });
  63. const dirIconEl = DOM.create({ tag: 'div', cls: 'dir-icon' });
  64. const dirNameEl = DOM.create({ tag: 'div', cls: 'dir-name', cn: [dirCollapsedIconEl, dirIconEl], innerHTML: dirName }).on('mousedown', ClassListModule.onListItemMouseDown);
  65. const dirContentEl = DOM.create({ tag: 'div', cls: 'dir-content' });
  66. const dirItem = DOM.create({ tag: 'div', cls: `dir-item ${ Class && Class.isPackage ? (Class.root + '/' + Class.name.replaceAll('.', '/') === dirPath ? 'selected' : '') : '' }`, cn: [dirNameEl, dirContentEl], attr: { 'data-dir-path': dirPath } }, parentContainer);
  67. const opened = (DOM.getCookieProperty(App.CookieName, ClassListModule.OpenedFoldersCookieName) || []).indexOf(dirPath) > -1;
  68. if(CDUtils.isEmpty(ClassListModule.SearchQuery) && !opened)
  69. dirItem.addClass('collapsed');
  70. Object.keys(dirContents).sort((a, b) => {
  71. return -dirContents[a].type.localeCompare(dirContents[b].type); // 'dir' > 'cls'
  72. }).forEach((key) => {
  73. const item = dirContents[key];
  74. if(item.type === 'cls') {
  75. ClassListModule.renderClass(item.class, dirContentEl);
  76. } else {
  77. ClassListModule.renderDir(key, `${dirPath}/${key}`, item.contents, dirContentEl);
  78. }
  79. });
  80. }
  81. static renderClass(cls, container) {
  82. const className = cls.name;
  83. const classShortName = cls.shortName;
  84. const icon = DOM.create({ tag: 'div', cls: 'class-icon' });
  85. const name = DOM.create({ tag: 'div', cls: 'class-name', innerHTML: className });
  86. DOM.create({
  87. tag: 'div',
  88. cls: `class-item ${ Class ? (Class.name === className ? 'selected' : '') : '' }`,
  89. cn: [icon, name],
  90. attr: {
  91. 'data-class-name': className,
  92. 'data-class-shortname': classShortName,
  93. 'title': className
  94. }
  95. }, container).on('mousedown', ClassListModule.onListItemMouseDown);
  96. }
  97. static transformStructure(obj) {
  98. const newObj = {};
  99. function recursiveTransform(parent, path, type, cls) {
  100. const [currentKey, ...rest] = path.split('.');
  101. if (!currentKey) return;
  102. if (!parent[currentKey]) {
  103. parent[currentKey] = {};
  104. parent[currentKey].type = rest.length === 0 ? type : 'dir';
  105. parent[currentKey].contents = {};
  106. }
  107. if (rest.length === 0) {
  108. parent[currentKey].type = type;
  109. parent[currentKey].class = cls;
  110. } else {
  111. recursiveTransform(parent[currentKey].contents, rest.join('.'), type, cls);
  112. }
  113. }
  114. function sortContents(contents) {
  115. const sortedKeys = Object.keys(contents).sort((a, b) => {
  116. const typeA = contents[a].type === 'dir' ? 0 : 1;
  117. const typeB = contents[b].type === 'dir' ? 0 : 1;
  118. if (typeA !== typeB) return typeA - typeB;
  119. return a.localeCompare(b);
  120. });
  121. const sortedContents = {};
  122. sortedKeys.forEach(key => {
  123. sortedContents[key] = contents[key];
  124. });
  125. return sortedContents;
  126. }
  127. for (const rootKey in obj) {
  128. if (obj.hasOwnProperty(rootKey)) {
  129. const { type, contents } = obj[rootKey];
  130. newObj[rootKey] = { type: 'dir', contents: sortContents({}) };
  131. for (const classKey in contents) {
  132. if (contents.hasOwnProperty(classKey)) {
  133. const { type, class: cls } = contents[classKey];
  134. recursiveTransform(newObj[rootKey].contents, classKey, type, cls);
  135. }
  136. }
  137. }
  138. }
  139. return newObj;
  140. }
  141. static onListItemClick(e) {
  142. let target = CDElement.get(e.target);
  143. while(!target.hasClass('class-item') && !target.hasClass('dir-name'))
  144. target = target.getParent();
  145. if(target.hasClass('class-item')) {
  146. ClassListModule.goToClass(target);
  147. } else if(target.hasClass('dir-name')) {
  148. ClassListModule.onDirClick(target);
  149. }
  150. }
  151. static onDirClick(target) {
  152. const parent = target.getParent();
  153. const dataDirPath = parent.getAttribute('data-dir-path');
  154. parent.switchClass('collapsed');
  155. if(!CDUtils.isEmpty(ClassListModule.SearchQuery))
  156. return;
  157. let openedFoldersCookieValue = DOM.getCookieProperty(App.CookieName, ClassListModule.OpenedFoldersCookieName) || [];
  158. if(parent.hasClass('collapsed')) {
  159. openedFoldersCookieValue.splice(openedFoldersCookieValue.indexOf(dataDirPath), 1);
  160. } else {
  161. openedFoldersCookieValue.push(dataDirPath);
  162. }
  163. DOM.setCookieProperty(App.CookieName, ClassListModule.OpenedFoldersCookieName, openedFoldersCookieValue, 24);
  164. }
  165. static getDirPath(target) {
  166. return CDElement.get(target).getAttribute('data-dir-path');
  167. }
  168. static getClassName(target) {
  169. return CDElement.get(target).getAttribute('data-class-name');
  170. }
  171. static getPackageName(target) {
  172. const path = ClassListModule.getPackagePath(target).split('/');
  173. return path[path.length - 1];
  174. }
  175. static getClassPath(target) {
  176. return `/class/${ClassListModule.getClassName(target)}`;
  177. }
  178. static getPackagePath(target) {
  179. const replaceSlashes = (str) => {
  180. let firstSlash = str.indexOf('/');
  181. if (firstSlash === -1) {
  182. return str;
  183. }
  184. let beforeFirstSlash = str.slice(0, firstSlash + 1);
  185. let afterFirstSlash = str.slice(firstSlash + 1).replace(/\//g, '.');
  186. return beforeFirstSlash + afterFirstSlash;
  187. }
  188. const dirPath = replaceSlashes(ClassListModule.getDirPath(target));
  189. return `/package/${dirPath}`;
  190. }
  191. static goToClass(target, newTab) {
  192. Url.goTo(ClassListModule.getClassPath(target), newTab);
  193. }
  194. static goToPackage(target, newTab) {
  195. Url.goTo(ClassListModule.getPackagePath(target), newTab);
  196. }
  197. static onModeButtonClick(e) {
  198. const target = CDElement.get(e.target);
  199. const mode = target.getAttribute('data-mode');
  200. ClassListModule._Mode = mode;
  201. DOM.setCookieProperty(App.CookieName, ClassListModule.ModeCookieName, mode);
  202. ClassListModule.reload();
  203. }
  204. static clear() {
  205. DOM.get('.class-list').getChildrenRecursive().forEach((item) => {
  206. if(item.hasClass('class-item') || item.hasClass('dir-name'))
  207. item.un('mousedown', ClassListModule.onListItemMouseDown)
  208. item.remove();
  209. });
  210. }
  211. static reload() {
  212. ClassListModule.clear();
  213. ClassListModule.load();
  214. }
  215. static updateSources() {
  216. fetch('/updateSources', {
  217. method: 'POST'
  218. });
  219. }
  220. static onListItemMouseDown(e) {
  221. const element = CDElement.get(e.target);
  222. if(e.buttons === DOM.MouseButtons.Left) {
  223. ClassListModule.onListItemClick(e);
  224. } else if(e.buttons === DOM.MouseButtons.Right) {
  225. setTimeout(() => {
  226. ClassListModule.showContextMenu(element, { x: e.pageX, y: e.pageY });
  227. }, 10);
  228. }
  229. }
  230. static showContextMenu(target, pos) {
  231. while(!target.hasClass('class-item') && !target.hasClass('dir-name'))
  232. target = target.getParent();
  233. const contextMenu = ContextMenu.init();
  234. if(target.hasClass('class-item')) {
  235. contextMenu.addItem('Open', 'Open', () => {
  236. ClassListModule.goToClass(target, false);
  237. });
  238. contextMenu.addItem('OpenInNewTab', 'Open in a new tab', () => {
  239. ClassListModule.goToClass(target, true);
  240. });
  241. contextMenu.addDelimiter();
  242. contextMenu.addItem('CopyLink', 'Copy link', () => {
  243. DOM.copyToClipboard(Url.setPath(ClassListModule.getClassPath(target)).setHash('').toString());
  244. });
  245. contextMenu.addItem('CopyHtmlLink', 'Copy HTML link', () => {
  246. DOM.copyToClipboard(`<a href="${ClassListModule.getClassPath(target)}">${ClassListModule.getClassName(target)}</a>`);
  247. });
  248. } else if(target.hasClass('dir-name') && !target.getParent().getParent().hasClass('class-list')) {
  249. contextMenu.addItem('Open', 'Open', () => {
  250. ClassListModule.goToPackage(target.getParent(), false);
  251. });
  252. contextMenu.addItem('OpenInNewTab', 'Open in a new tab', () => {
  253. ClassListModule.goToPackage(target.getParent(), true);
  254. });
  255. contextMenu.addDelimiter();
  256. contextMenu.addItem('CopyLink', 'Copy link', () => {
  257. DOM.copyToClipboard(Url.setPath(ClassListModule.getPackagePath(target.getParent())).setHash('').toString());
  258. });
  259. contextMenu.addItem('CopyHtmlLink', 'Copy HTML link', () => {
  260. DOM.copyToClipboard(`<a href="${ClassListModule.getPackagePath(target.getParent())}">${ClassListModule.getPackageName(target.getParent())}</a>`);
  261. });
  262. }
  263. contextMenu.show(pos);
  264. }
  265. static init() {
  266. DOM.get('.class-list-mode-button.structurized').on(DOM.Events.Click, ClassListModule.onModeButtonClick);
  267. DOM.get('.class-list-mode-button.unstructurized').on(DOM.Events.Click, ClassListModule.onModeButtonClick);
  268. DOM.get('.faq-button').on(DOM.Events.Click, (e) => { Url.goTo('/faq'); });
  269. const refreshButton = DOM.get('.refresh-button');
  270. if(refreshButton) {
  271. refreshButton.on(DOM.Events.Click, (e) => {
  272. if(confirm('Are you sure you want to update sources? It will take approximately 3-5 minutes. The system will not be available until the process finished.')) {
  273. ClassListModule.updateSources();
  274. Url.goTo('/');
  275. }
  276. });
  277. }
  278. const statisticsButton = DOM.get('.statistics-button');
  279. if(statisticsButton) {
  280. statisticsButton.on(DOM.Events.Click, (e) => {
  281. Url.goTo('/statistics');
  282. });
  283. }
  284. const modeCookieValue = DOM.getCookieProperty(App.CookieName, ClassListModule.ModeCookieName);
  285. ClassListModule._Mode = modeCookieValue || ClassListModule.Mode.Structurized;
  286. if(!modeCookieValue)
  287. DOM.setCookieProperty(App.CookieName, ClassListModule.ModeCookieName, ClassListModule.Mode.Structurized);
  288. ClassListModule.load();
  289. ClassListModule.classListElement.scrollTo('.class-item.selected');
  290. }
  291. }
  292. window_.on('load', (e) => {
  293. ClassListModule.init();
  294. });