# src/view/LeftNav.ts

`1<comment>/**
2 * LeftNav: Responsible for constructing the left-hand navigation pane.
3 */</comment>
4
5<comment>/** */</comment>
6import { m, Vnode} from 'hslayout';
7import { Layout }from 'hslayout';
8import { Collapsible }from 'hswidget';
9import { DocSets } from '../DocSets';
10import { libLink } from './Parts';
11
12
13<comment>/**
14 * Constructs the left-hand navigation pane
15 */</comment>
16export class LeftNav extends Layout {
17 getComponents(node: Vnode): Vnode {
18 let lib:string;
19 let field:string;
20 if (node.attrs && node.attrs.route) {
21 lib = node.attrs.route.lib;
22 field = node.attrs.route.field;
23 }
24 const docSet = DocSets.get(lib, 0) || {name:'unknown', id:0};
25 return m('.hs-left', [m('.hs-left-nav', navList(docSet, field))]);
26 }
27}
28
29<comment>/** creates the list if modules (`*.ts` files) */</comment>
30function navList(docSet:any, field:string):Vnode[] {
31 <comment>/** */</comment>
32 function collectModules(docSet:any) {
33 const modulesByName = {};
34 docSet.modules = [];
35 if (docSet.children) {
36 docSet.children.forEach((c:any) => {
37 // don't show modules from other projects (isExternal flag) or modules on the ignore list
38 if (!(c.flags && c.flags.isExternal) && !ignoreModules[c.name]) {
39 const name = c.innerModule? c.innerModule : c.name;
40 let module = modulesByName[name];
41 if (!module) { // new module
42 docSet.modules.push(module = modulesByName[name] = {
43 name: name,
44 lib: docSet.lib,
45 fullPath: docSet.fullPath + '.'+ name,
46 groups:[]
47 });
48 }
49 // get existing module groups
50 const groups = {};
51 module.groups.forEach((g:any) => groups[g.title] = g);
52 // for each group in child:
53 if (c.groups) { c.groups.forEach((g:any) => {
54 let group = groups[g.title]; // get existing
55 if (!group) { // else create new
56 group = groups[g.title] = {
57 children:[],
58 kind: g.kind,
59 title: g.title
60 };
61 module.groups.push(group);
62 }
63 group.children = group.children.concat(g.children);
64 });}
65 }
66 });
67 }
68 }
69 <comment>/**
70 * processes a module, i.e. a `.ts` file.
71 */</comment>
72 function externalModule(mdl:any) {
73 let selected = false;
74 if (field===''+mdl.id || field.indexOf(mdl.fullPath) === 0) { selected=true; }
75
76 return m(Collapsible, {css:`.hs-left-nav-module`, preArrow: true, isExpanded:selected, components:[
77 m(`${selected?'.hs-left-nav-selected':''}`, libLink(`a.hs-left-nav-module-name `, mdl.lib, mdl.fullPath, mdl.name)),
78 !mdl.groups? undefined : mdl.groups.map((g:any) => entries(g, mdl, field))
79 ]});
80 }
81
82 if (docSet.kind === 0) { // External DocSets
83 collectModules(docSet);
84// const modules = docSet.children? docSet.children.map(externalModule) : ['no children'];
85 const modules = docSet.modules.map(externalModule);
86 modules.unshift(m('.hs-left-nav-header', 'Modules'));
87 return [m('.hs-left-nav-content', modules)];
88 }
89}
90
91<comment>/**
92 * modules to ignore in the list
93 */</comment>
94const ignoreModules = {
95 overview: true, // the project overview.ts file
96 index: true // the index.ts file
97};
98
99//const expanded: {string?:boolean} = {};
100
101<comment>/**
102 * processes a group of entries, e.g. Variables, Functions, or Classes.
103 * @param group the group structure within the docset
104 * @param mdl the entire module docset
105 * @param field the field ID to be displayed on the main panel
106 */</comment>
107function entries(group:any, mdl:any, field:string) {
108 function moduleGet(c:any) {
109 return DocSets.get(mdl.lib, c);
110 }
111 <comment>/**
112 * processes one entry within a group, e.g. one variable, function, or class.
113 */</comment>
114 function entry(mod:any) {
115 const selected = (field===''+mod.id || field===mod.fullPath)? '.hs-left-nav-selected' : '';
116 const exported = (mod.flags && mod.flags.isExported);
117 const statik = (mod.flags && mod.flags.isStatic);
118 const css = `a.hs-left-nav-entry ${selected} ${exported?'.hs-left-nav-exported' : ''}`;
119 return (!exported && group.title==='Variables')? '' : // ignore local module variables
120 m('', [
121 statik? 'static': '',
122 libLink(css, mod.lib, mod.fullPath, mod.name)
123 ]);
124 }
125
126 function empty(mod:any) { return mod !== ''; }
127
128 let grp = [];
129 if (group && group.children) {
130 grp = group.children
131 .map(moduleGet) // replace id reference by module
132 .sort(exportAscending) // sort: exported first, then alphabetically
133 .map(entry) // replace module by vnode structure
134 .filter(empty); // filter empty elements
135 if (grp.length > 0) { // add an entries header if there are elements
136 grp.unshift(m('.hs-left-nav-header', group.title));
137 }
138 }
139 return (grp.length > 1)? m(`.hs-left-nav-entries`, grp) : '';
140}
141
142<comment>/**
143 * sorting function: sort first by exported status, then alphabetically.
144 */</comment>
145function exportAscending(a:any, b:any):boolean|number {
146 if (a.flags && b.flags) {
147 if (a.flags.isExported && b.flags.isExported) { return a.name > b.name; }
148 else if (a.flags.isExported) { return -1; }
149 else if (b.flags.isExported) { return 1; }
150 else { return a.name > b.name; }
151 } else { return a.name > b.name; }
152}
153
154
`