Improve the jsdoc index page.
This alters the jaguarjs-jsdoc template by replacing the mainpage and
other templates and adding some css.  The resulting index page lists the
GeoJS version and has links to all classes and typedefs.
MODIFY
.eslintignore
.eslintignore
src/util/distanceGrid.js
dist/**
-testing/**
website/**
-geo.js
**/*.min.js
examples/common/jsonlint.js
+jsdoc/template/publish.js
+jsdoc/template/static/**
+jsdoc/template/tmpl/**

MODIFY
.gitignore
.gitignore
.eslintcache
/lcov
/notes
+/jsdoc/template/tmpl
+/jsdoc/template/static

MODIFY
jsdoc.conf.json
jsdoc.conf.json
{
"opts": {
- "template": "node_modules/jaguarjs-jsdoc"
+ "template": "jsdoc/template"
},
"plugins": [
"jsdoc/plugins/events",

ADD
jsdoc/template/mainpage.tmpl
+<?js
+var data = obj;
+var self = this;
+?>
+
+<?js if (data.kind === 'package') { ?>
+ <h2><?js= env.conf.templates.applicationName ?> <?js= data.version ?></h2>
+ <?js if (data.gitsha && data.homepage) { ?>
+ <span><a href="<?js= data.homepage + '/commit/' + data.gitsha ?>"> <?js= data.name ?> commit <?js= data.gitsha.substr(0, 7) ?></a></span>
+ <?js } ?>
+<?js } ?>
+
+<?js if (data.readme) { ?>
+ <section>
+ <article class="readme"><?js= data.readme ?></article>
+ </section>
+<?js } ?>
+
+<?js if (data.kind === 'package') { ?>
+ <h3>Classes</h3>
+ <ul class="list">
+ <?js
+ this.nav.forEach(function (item) {
+ if (item.type === 'class') {
+ ?>
+ <li class="item" data-name="<?js= item.longname ?>">
+ <span class="title">
+ <?js= self.linkto(item.longname, item.longname) ?>
+ </span>
+ <?js
+ }
+ });
+ ?>
+ </ul>
+
+ <h3>Typedefs</h3>
+ <ul class="list">
+ <?js
+ var typedefs = [];
+ this.nav.forEach(function (parent) {
+ (parent.typedefs || []).forEach(function (item) {
+ typedefs.push(item);
+ });
+ });
+ typedefs.sort(function (a, b) { return a.longname > b.longname ? 1 : -1; });
+ typedefs.forEach(function (item) {
+ ?>
+ <li class="item" data-name="<?js= item.longname ?>">
+ <span class="title">
+ <?js= self.linkto(item.longname, item.longname) ?>
+ </span>
+ <?js
+ });
+ ?>
+ </ul>
+<?js } ?>
+

ADD
jsdoc/template/properties.tmpl
+<?js
+ var props = obj;
+
+ /* sort subprops under their parent props (like opts.classname) */
+ var parentProp = null;
+ props.forEach(function(prop, i) {
+ if (!prop) { return; }
+ if ( parentProp && prop.name && prop.name.indexOf(parentProp.name + '.') === 0 ) {
+ prop.name = prop.name.substr(parentProp.name.length+1);
+ parentProp.subprops = parentProp.subprops || [];
+ parentProp.subprops.push(prop);
+ props[i] = null;
+ }
+ else {
+ parentProp = prop;
+ }
+ });
+
+ /* determine if we need extra columns, "attributes" and "default" */
+ props.hasAttributes = false;
+ props.hasDefault = false;
+ props.hasName = false;
+
+ props.forEach(function(prop) {
+ if (!prop) { return; }
+
+ if (prop.optional || prop.nullable) {
+ props.hasAttributes = true;
+ }
+
+ if (prop.name) {
+ props.hasName = true;
+ }
+
+ if (typeof prop.defaultvalue !== 'undefined') {
+ props.hasDefault = true;
+ }
+ });
+?>
+
+<table class="props">
+ <thead>
+ <tr>
+ <?js if (props.hasName) {?>
+ <th>Name</th>
+ <?js } ?>
+
+ <th>Type</th>
+
+ <?js if (props.hasAttributes) {?>
+ <th>Argument</th>
+ <?js } ?>
+
+ <?js if (props.hasDefault) {?>
+ <th>Default</th>
+ <?js } ?>
+
+ <th class="last">Description</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ <?js
+ var self = this;
+ props.forEach(function(prop) {
+ if (!prop) { return; }
+ ?>
+
+ <tr>
+ <?js if (props.hasName) {?>
+ <td class="name">
+ <code <?js= prop.inherited ? 'class="inherited"' : ''?>><?js= prop.inherited ? self.linkto(prop.inherited, prop.name) : prop.name ?></code></td>
+ <?js } ?>
+
+ <td class="type">
+ <?js if (prop.type && prop.type.names) {?>
+ <?js= self.partial('type.tmpl', prop.type.names) ?>
+ <?js } ?>
+ </td>
+
+ <?js if (props.hasAttributes) {?>
+ <td class="attributes">
+ <?js if (prop.optional) { ?>
+ &lt;optional><br>
+ <?js } ?>
+
+ <?js if (prop.nullable) { ?>
+ &lt;nullable><br>
+ <?js } ?>
+ </td>
+ <?js } ?>
+
+ <?js if (props.hasDefault) {?>
+ <td class="default">
+ <?js if (typeof prop.defaultvalue !== 'undefined') { ?>
+ <?js= self.htmlsafe(prop.defaultvalue) ?>
+ <?js } ?>
+ </td>
+ <?js } ?>
+
+ <td class="description last"><?js= prop.description ?><?js if (prop.subprops) { ?>
+ <h6>Properties</h6><?js= self.partial('properties.tmpl', prop.subprops) ?>
+ <?js } ?></td>
+ </tr>
+
+ <?js }); ?>
+ </tbody>
+</table>

ADD
jsdoc/template/publish.js
+/*global env: true */
+
+/* This is largely copied from jaguarjs-jsdoc's publish.js file. The outdir
+ * outdir does not have the package name and version appended to it, and we add
+ * the git commit sha to the available information.
+ */
+
+/* Copy the jaguarjs-jsdoc template, then add in our own files. */
+var fse = require('fs-extra');
+
+fse.copySync('node_modules/jaguarjs-jsdoc/tmpl', 'jsdoc/template/tmpl');
+fse.copySync('node_modules/jaguarjs-jsdoc/static', 'jsdoc/template/static');
+fse.copySync('jsdoc/template/mainpage.tmpl', 'jsdoc/template/tmpl/mainpage.tmpl');
+fse.copySync('jsdoc/template/properties.tmpl', 'jsdoc/template/tmpl/properties.tmpl');
+fse.appendFileSync('jsdoc/template/static/styles/jaguar.css', fse.readFileSync('jsdoc/template/style.css').toString());
+
+/* Also get the git sha, if possible */
+
+var gitsha;
+try {
+ gitsha = require('child_process').execSync('git rev-parse HEAD').toString().trim();
+} catch (err) { }
+
+/* Original publish.js commands */
+
+var template = require('jsdoc/template'),
+ fs = require('jsdoc/fs'),
+ path = require('jsdoc/path'),
+ taffy = require('taffydb').taffy,
+ handle = require('jsdoc/util/error').handle,
+ helper = require('jsdoc/util/templateHelper'),
+ _ = require('underscore'),
+ htmlsafe = helper.htmlsafe,
+ linkto = helper.linkto,
+ resolveAuthorLinks = helper.resolveAuthorLinks,
+ scopeToPunc = helper.scopeToPunc,
+ hasOwnProp = Object.prototype.hasOwnProperty,
+ data,
+ view,
+ outdir = env.opts.destination;
+
+function find(spec) {
+ return helper.find(data, spec);
+}
+
+function tutoriallink(tutorial) {
+ return helper.toTutorial(tutorial, null, { tag: 'em', classname: 'disabled', prefix: 'Tutorial: ' });
+}
+
+function getAncestorLinks(doclet) {
+ return helper.getAncestorLinks(data, doclet);
+}
+
+function hashToLink(doclet, hash) {
+ if ( !/^(#.+)/.test(hash) ) { return hash; }
+
+ var url = helper.createLink(doclet);
+
+ url = url.replace(/(#.+|$)/, hash);
+ return '<a href="' + url + '">' + hash + '</a>';
+}
+
+function needsSignature(doclet) {
+ var needsSig = false;
+
+ // function and class definitions always get a signature
+ if (doclet.kind === 'function' || doclet.kind === 'class') {
+ needsSig = true;
+ }
+ // typedefs that contain functions get a signature, too
+ else if ((doclet.kind === 'typedef' || doclet.kind === 'interface') && doclet.type && doclet.type.names &&
+ doclet.type.names.length) {
+ for (var i = 0, l = doclet.type.names.length; i < l; i++) {
+ if (doclet.type.names[i].toLowerCase() === 'function') {
+ needsSig = true;
+ break;
+ }
+ }
+ }
+
+ return needsSig;
+}
+
+function addSignatureParams(f) {
+ var params = helper.getSignatureParams(f, 'optional');
+
+ f.signature = (f.signature || '') + '('+params.join(', ')+')';
+}
+
+function addSignatureReturns(f) {
+ var returnTypes = helper.getSignatureReturns(f);
+
+ f.signature = '<span class="signature">'+(f.signature || '') + '</span>';
+
+ if (returnTypes.length) {
+ f.signature += '<span class="glyphicon glyphicon-circle-arrow-right"></span><span class="type-signature returnType">'+(returnTypes.length ? '{'+returnTypes.join('|')+'}' : '')+'</span>';
+ }
+}
+
+function addSignatureTypes(f) {
+ var types = helper.getSignatureTypes(f);
+
+ f.signature = (f.signature || '') + '<span class="type-signature">'+(types.length? ' :'+types.join('|') : '')+'</span>';
+}
+
+function addAttribs(f) {
+ var attribs = helper.getAttribs(f);
+
+ if (attribs.length) {
+ f.attribs = '<span class="type-signature ' + (attribs[0] === 'static' ? 'static' : '') + '">' + htmlsafe(attribs.length ? attribs.join(',') : '') + '</span>';
+ }
+}
+
+function shortenPaths(files, commonPrefix) {
+ // always use forward slashes
+ var regexp = new RegExp('\\\\', 'g');
+
+ Object.keys(files).forEach(function(file) {
+ files[file].shortened = files[file].resolved.replace(commonPrefix, '')
+ .replace(regexp, '/');
+ });
+
+ return files;
+}
+
+function resolveSourcePath(filepath) {
+ return path.resolve(process.cwd(), filepath);
+}
+
+function getPathFromDoclet(doclet) {
+ if (!doclet.meta) {
+ return;
+ }
+
+ var filepath = doclet.meta.path && doclet.meta.path !== 'null' ?
+ doclet.meta.path + '/' + doclet.meta.filename :
+ doclet.meta.filename;
+
+ return filepath;
+}
+
+function generate(title, docs, filename, resolveLinks) {
+ resolveLinks = resolveLinks === false ? false : true;
+
+ var docData = {
+ filename: filename,
+ title: title,
+ docs: docs
+ };
+
+ var outpath = path.join(outdir, filename),
+ html = view.render('container.tmpl', docData);
+
+ if (resolveLinks) {
+ html = helper.resolveLinks(html); // turn {@link foo} into <a href="foodoc.html">foo</a>
+
+ // Add a link target for external links @davidshimjs
+ html = html.toString().replace(/<a\s+([^>]*href\s*=\s*['"]*[^\s'"]*:\/\/)/ig, '<a target="_blank" $1');
+ }
+
+ fs.writeFileSync(outpath, html, 'utf8');
+}
+
+function generateSourceFiles(sourceFiles) {
+ Object.keys(sourceFiles).forEach(function(file) {
+ var source;
+ // links are keyed to the shortened path in each doclet's `meta.filename` property
+ var sourceOutfile = helper.getUniqueFilename(sourceFiles[file].shortened);
+ helper.registerLink(sourceFiles[file].shortened, sourceOutfile);
+
+ try {
+ source = {
+ kind: 'source',
+ code: helper.htmlsafe( fs.readFileSync(sourceFiles[file].resolved, 'utf8') )
+ };
+ }
+ catch(e) {
+ handle(e);
+ }
+
+ generate('Source: ' + sourceFiles[file].shortened, [source], sourceOutfile,
+ false);
+ });
+}
+
+/**
+ * Look for classes or functions with the same name as modules (which indicates that the module
+ * exports only that class or function), then attach the classes or functions to the `module`
+ * property of the appropriate module doclets. The name of each class or function is also updated
+ * for display purposes. This function mutates the original arrays.
+ *
+ * @private
+ * @param {Array.<module:jsdoc/doclet.Doclet>} doclets - The array of classes and functions to
+ * check.
+ * @param {Array.<module:jsdoc/doclet.Doclet>} modules - The array of module doclets to search.
+ */
+function attachModuleSymbols(doclets, modules) {
+ var symbols = {};
+
+ // build a lookup table
+ doclets.forEach(function(symbol) {
+ symbols[symbol.longname] = symbol;
+ });
+
+ return modules.map(function(module) {
+ if (symbols[module.longname]) {
+ module.module = symbols[module.longname];
+ module.module.name = module.module.name.replace('module:', 'require("') + '")';
+ }
+ });
+}
+
+/**
+ * Create the navigation sidebar.
+ * @param {object} members The members that will be used to create the sidebar.
+ * @param {array<object>} members.classes
+ * @param {array<object>} members.externals
+ * @param {array<object>} members.globals
+ * @param {array<object>} members.mixins
+ * @param {array<object>} members.modules
+ * @param {array<object>} members.namespaces
+ * @param {array<object>} members.tutorials
+ * @param {array<object>} members.interfaces
+ * @param {array<object>} members.events
+ * @return {string} The HTML for the navigation sidebar.
+ */
+function buildNav(members) {
+ var nav = [];
+
+ if (members.namespaces.length) {
+ _.each(members.namespaces, function (v) {
+ nav.push({
+ type: 'namespace',
+ longname: v.longname,
+ name: v.name,
+ members: find({
+ kind: 'member',
+ memberof: v.longname
+ }),
+ methods: find({
+ kind: 'function',
+ memberof: v.longname
+ }),
+ typedefs: find({
+ kind: 'typedef',
+ memberof: v.longname
+ }),
+ interfaces: find({
+ kind: 'interface',
+ memberof: v.longname
+ }),
+ events: find({
+ kind: 'event',
+ memberof: v.longname
+ }),
+ classes: find({
+ kind: 'class',
+ memberof: v.longname
+ })
+ });
+ });
+ }
+
+ if (members.classes.length) {
+ _.each(members.classes, function (v) {
+ nav.push({
+ type: 'class',
+ longname: v.longname,
+ name: v.name,
+ members: find({
+ kind: 'member',
+ memberof: v.longname
+ }),
+ methods: find({
+ kind: 'function',
+ memberof: v.longname
+ }),
+ typedefs: find({
+ kind: 'typedef',
+ memberof: v.longname
+ }),
+ interfaces: find({
+ kind: 'interface',
+ memberof: v.longname
+ }),
+ events: find({
+ kind: 'event',
+ memberof: v.longname
+ })
+ });
+ });
+ }
+
+ if (members.tutorials.length) {
+ _.each(members.tutorials, function(v) {
+ nav.push({
+ type: 'tutorial',
+ name: v.name,
+ longname: v.longname,
+ title: v.title,
+ children: v.children,
+ parent: v.parent
+ });
+ });
+ }
+
+ return nav;
+}
+
+
+/**
+ @param {TAFFY} taffyData See <http://taffydb.com/>.
+ @param {object} opts
+ @param {Tutorial} tutorials
+ */
+exports.publish = function(taffyData, opts, tutorials) {
+ data = taffyData;
+
+ var conf = env.conf.templates || {};
+ conf['default'] = conf['default'] || {};
+
+ var templatePath = opts.template;
+ view = new template.Template(templatePath + '/tmpl');
+
+ // claim some special filenames in advance, so the All-Powerful Overseer of Filename Uniqueness
+ // doesn't try to hand them out later
+ var indexUrl = helper.getUniqueFilename('index');
+ // don't call registerLink() on this one! 'index' is also a valid longname
+
+ var globalUrl = helper.getUniqueFilename('global');
+ helper.registerLink('global', globalUrl);
+
+ // set up templating
+ view.layout = 'layout.tmpl';
+
+ // set up tutorials for helper
+ helper.setTutorials(tutorials);
+
+ data = helper.prune(data);
+ data.sort('longname, version, since');
+ helper.addEventListeners(data);
+
+ var sourceFiles = {};
+ var sourceFilePaths = [];
+ data().each(function(doclet) {
+ doclet.attribs = '';
+
+ if (doclet.examples) {
+ doclet.examples = doclet.examples.map(function(example) {
+ var caption, code;
+
+ if (example.match(/^\s*(?:<p>)?\s*<caption>([\s\S]+?)<\/caption>\s*(?:<\/p>)?[\s\r\n]*([\s\S]+)$/i)) {
+ caption = RegExp.$1;
+ code = RegExp.$2;
+ }
+
+ return {
+ caption: caption || '',
+ code: code || example
+ };
+ });
+ }
+ if (doclet.see) {
+ doclet.see.forEach(function(seeItem, i) {
+ doclet.see[i] = hashToLink(doclet, seeItem);
+ });
+ }
+
+ // build a list of source files
+ var sourcePath;
+ var resolvedSourcePath;
+ if (doclet.meta) {
+ sourcePath = getPathFromDoclet(doclet);
+ resolvedSourcePath = resolveSourcePath(sourcePath);
+ sourceFiles[sourcePath] = {
+ resolved: resolvedSourcePath,
+ shortened: null
+ };
+ sourceFilePaths.push(resolvedSourcePath);
+ }
+ });
+
+ // update outdir if necessary, then create outdir
+ var packageInfo = ( find({kind: 'package'}) || [] ) [0];
+ packageInfo.gitsha = gitsha;
+ /*
+ if (packageInfo && packageInfo.name) {
+ outdir = path.join(outdir, packageInfo.name, packageInfo.version);
+ }
+ */
+ fs.mkPath(outdir);
+
+ // copy the template's static files to outdir
+ var fromDir = path.join(templatePath, 'static');
+ var staticFiles = fs.ls(fromDir, 3);
+
+ staticFiles.forEach(function(fileName) {
+ var toDir = fs.toDir( fileName.replace(fromDir, outdir) );
+ fs.mkPath(toDir);
+ fs.copyFileSync(fileName, toDir);
+ });
+
+ // copy user-specified static files to outdir
+ var staticFilePaths;
+ var staticFileFilter;
+ var staticFileScanner;
+ if (conf['default'].staticFiles) {
+ staticFilePaths = conf['default'].staticFiles.paths || [];
+ staticFileFilter = new (require('jsdoc/src/filter')).Filter(conf['default'].staticFiles);
+ staticFileScanner = new (require('jsdoc/src/scanner')).Scanner();
+
+ staticFilePaths.forEach(function(filePath) {
+ var extraStaticFiles = staticFileScanner.scan([filePath], 10, staticFileFilter);
+
+ extraStaticFiles.forEach(function(fileName) {
+ var sourcePath = fs.statSync(filePath).isDirectory() ? filePath :
+ path.dirname(filePath);
+ var toDir = fs.toDir( fileName.replace(sourcePath, outdir) );
+ fs.mkPath(toDir);
+ fs.copyFileSync(fileName, toDir);
+ });
+ });
+ }
+
+ if (sourceFilePaths.length) {
+ sourceFiles = shortenPaths( sourceFiles, path.commonPrefix(sourceFilePaths) );
+ }
+ data().each(function(doclet) {
+ var url = helper.createLink(doclet);
+ helper.registerLink(doclet.longname, url);
+
+ // replace the filename with a shortened version of the full path
+ var docletPath;
+ if (doclet.meta) {
+ docletPath = getPathFromDoclet(doclet);
+ docletPath = sourceFiles[docletPath].shortened;
+ if (docletPath) {
+ doclet.meta.filename = docletPath;
+ }
+ }
+ });
+
+ data().each(function(doclet) {
+ var url = helper.longnameToUrl[doclet.longname];
+
+ if (url.indexOf('#') > -1) {
+ doclet.id = helper.longnameToUrl[doclet.longname].split(/#/).pop();
+ }
+ else {
+ doclet.id = doclet.name;
+ }
+
+ if ( needsSignature(doclet) ) {
+ addSignatureParams(doclet);
+ addSignatureReturns(doclet);
+ addAttribs(doclet);
+ }
+ });
+
+ // do this after the urls have all been generated
+ data().each(function(doclet) {
+ doclet.ancestors = getAncestorLinks(doclet);
+
+ if (doclet.kind === 'member') {
+ addSignatureTypes(doclet);
+ addAttribs(doclet);
+ }
+
+ if (doclet.kind === 'constant') {
+ addSignatureTypes(doclet);
+ addAttribs(doclet);
+ doclet.kind = 'member';
+ }
+ });
+
+ var members = helper.getMembers(data);
+ members.tutorials = tutorials.children;
+
+ // add template helpers
+ view.find = find;
+ view.linkto = linkto;
+ view.resolveAuthorLinks = resolveAuthorLinks;
+ view.tutoriallink = tutoriallink;
+ view.htmlsafe = htmlsafe;
+ view.members = members; //@davidshimjs: To make navigation for customizing
+
+ // once for all
+ view.nav = buildNav(members);
+ attachModuleSymbols( find({ kind: ['class', 'function'], longname: {left: 'module:'} }),
+ members.modules );
+
+ // only output pretty-printed source files if requested; do this before generating any other
+ // pages, so the other pages can link to the source files
+ if (conf['default'].outputSourceFiles) {
+ generateSourceFiles(sourceFiles);
+ }
+
+ if (members.globals.length) { generate('Global', [{kind: 'globalobj'}], globalUrl); }
+
+ // index page displays information from package.json and lists files
+ var files = find({kind: 'file'}),
+ packages = find({kind: 'package'});
+
+ generate('Index',
+ packages.concat(
+ [{kind: 'mainpage', readme: opts.readme, longname: (opts.mainpagetitle) ? opts.mainpagetitle : 'Main Page'}]
+ ).concat(files),
+ indexUrl);
+
+ // set up the lists that we'll use to generate pages
+ var classes = taffy(members.classes);
+ var modules = taffy(members.modules);
+ var namespaces = taffy(members.namespaces);
+ var mixins = taffy(members.mixins);
+ var externals = taffy(members.externals);
+ var interfaces = taffy(members.interfaces);
+
+ for (var longname in helper.longnameToUrl) {
+ if ( hasOwnProp.call(helper.longnameToUrl, longname) ) {
+ var myClasses = helper.find(classes, {longname: longname});
+ if (myClasses.length) {
+ generate('Class: ' + myClasses[0].name, myClasses, helper.longnameToUrl[longname]);
+ }
+
+ var myModules = helper.find(modules, {longname: longname});
+ if (myModules.length) {
+ generate('Module: ' + myModules[0].name, myModules, helper.longnameToUrl[longname]);
+ }
+
+ var myNamespaces = helper.find(namespaces, {longname: longname});
+ if (myNamespaces.length) {
+ generate('Namespace: ' + myNamespaces[0].name, myNamespaces, helper.longnameToUrl[longname]);
+ }
+
+ var myMixins = helper.find(mixins, {longname: longname});
+ if (myMixins.length) {
+ generate('Mixin: ' + myMixins[0].name, myMixins, helper.longnameToUrl[longname]);
+ }
+
+ var myExternals = helper.find(externals, {longname: longname});
+ if (myExternals.length) {
+ generate('External: ' + myExternals[0].name, myExternals, helper.longnameToUrl[longname]);
+ }
+
+ var myInterfaces = helper.find(interfaces, {longname: longname});
+ if (myInterfaces.length) {
+ generate('Interface: ' + myInterfaces[0].name, myInterfaces, helper.longnameToUrl[longname]);
+ }
+ }
+ }
+
+ // TODO: move the tutorial functions to templateHelper.js
+ function generateTutorial(title, tutorial, filename) {
+ var tutorialData = {
+ title: title,
+ header: tutorial.title,
+ content: tutorial.parse(),
+ children: tutorial.children,
+ filename: filename
+ };
+
+ var tutorialPath = path.join(outdir, filename),
+ html = view.render('tutorial.tmpl', tutorialData);
+
+ // yes, you can use {@link} in tutorials too!
+ html = helper.resolveLinks(html); // turn {@link foo} into <a href="foodoc.html">foo</a>
+
+ fs.writeFileSync(tutorialPath, html, 'utf8');
+ }
+
+ // tutorials can have only one parent so there is no risk for loops
+ function saveChildren(node) {
+ node.children.forEach(function(child) {
+ generateTutorial('Tutorial: ' + child.title, child, helper.tutorialToUrl(child.name));
+ saveChildren(child);
+ });
+ }
+ saveChildren(tutorials);
+};

ADD
jsdoc/template/style.css
+.main ul.list {
+ column-width: 300px;
+}
+.main td.name .inherited {
+ background-color: #ccc!important;
+}
+.main td.name .inherited a {
+ color: #c7254e;
+}
+

MODIFY
package.json
package.json
"examples": "webpack-dev-server --config webpack-examples.config.js --host ${HOST-127.0.0.1} --port ${PORT-8082} --content-base dist/",
"start-test": "node examples/build.js; forever start ./tests/runners/server.js",
"stop-test": "forever stop ./tests/runners/server.js",
- "docs": "jsdoc --pedantic -d dist/apidocs -r src -c jsdoc.conf.json",
+ "docs": "jsdoc --pedantic -d dist/apidocs -r src package.json -c jsdoc.conf.json",
"website": "cd website && npx hexo server",
"setup-website": "cd website && npm install",
"build-website": "npm run build && cp -a dist/built/. website/source/built && npm run build-website-examples && npm run build-website-tutorials && npm run docs && cd dist && find data \\( -name tiles -o -name base-images -o -name '*-hash-stamp' -o -name '*.tgz' \\) -prune -o \\( -print0 \\) | cpio -pmdL0 ../website/source && cp -ar apidocs/. ../website/source/apidocs && cd ../website && npm install && rm -f db.json && npx hexo generate",

Improve the jsdoc index page.

This alters the jaguarjs-jsdoc template by replacing the mainpage and
other templates and adding some css.  The resulting index page lists the
GeoJS version and has links to all classes and typedefs.