2017-01-03 21:16:48 +00:00
|
|
|
// Copyright Joyent, Inc. and other Node contributors.
|
|
|
|
//
|
|
|
|
// Permission is hereby granted, free of charge, to any person obtaining a
|
|
|
|
// copy of this software and associated documentation files (the
|
|
|
|
// "Software"), to deal in the Software without restriction, including
|
|
|
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|
|
|
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
|
|
|
// persons to whom the Software is furnished to do so, subject to the
|
|
|
|
// following conditions:
|
|
|
|
//
|
|
|
|
// The above copyright notice and this permission notice shall be included
|
|
|
|
// in all copies or substantial portions of the Software.
|
|
|
|
//
|
|
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
|
|
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
|
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
|
|
|
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
|
|
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
|
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
|
|
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
|
2016-01-30 18:17:57 +00:00
|
|
|
'use strict';
|
|
|
|
|
2015-11-14 02:40:38 +00:00
|
|
|
const common = require('./common.js');
|
|
|
|
const fs = require('fs');
|
|
|
|
const marked = require('marked');
|
|
|
|
const path = require('path');
|
|
|
|
const typeParser = require('./type-parser.js');
|
2012-02-27 18:59:01 +00:00
|
|
|
|
|
|
|
module.exports = toHTML;
|
|
|
|
|
2018-04-25 15:24:27 +00:00
|
|
|
// Make `marked` to not automatically insert id attributes in headings.
|
2017-06-19 15:55:10 +00:00
|
|
|
const renderer = new marked.Renderer();
|
2018-04-25 15:24:27 +00:00
|
|
|
renderer.heading = (text, level) => `<h${level}>${text}</h${level}>\n`;
|
|
|
|
marked.setOptions({ renderer });
|
2016-04-25 23:44:37 +00:00
|
|
|
|
2018-04-25 20:44:38 +00:00
|
|
|
const docPath = path.resolve(__dirname, '..', '..', 'doc');
|
|
|
|
|
|
|
|
const gtocPath = path.join(docPath, 'api', '_toc.md');
|
|
|
|
const gtocMD = fs.readFileSync(gtocPath, 'utf8').replace(/^@\/\/.*$/gm, '');
|
|
|
|
const gtocHTML = marked(gtocMD).replace(
|
|
|
|
/<a href="(.*?)"/g,
|
2018-04-25 15:24:27 +00:00
|
|
|
(all, href) => `<a class="nav-${href.replace('.html', '')
|
|
|
|
.replace(/\W+/g, '-')}" href="${href}"`
|
2018-04-25 20:44:38 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
const templatePath = path.join(docPath, 'template.html');
|
|
|
|
const template = fs.readFileSync(templatePath, 'utf8');
|
|
|
|
|
2018-04-25 15:24:27 +00:00
|
|
|
function toHTML({ input, filename, nodeVersion, analytics }, cb) {
|
|
|
|
filename = path.basename(filename, '.md');
|
2015-11-18 22:40:03 +00:00
|
|
|
|
2018-04-25 15:24:27 +00:00
|
|
|
const lexed = marked.lexer(input);
|
2012-02-27 18:59:01 +00:00
|
|
|
|
2018-04-25 15:24:27 +00:00
|
|
|
const firstHeading = lexed.find(({ type }) => type === 'heading');
|
|
|
|
const section = firstHeading ? firstHeading.text : 'Index';
|
2012-02-27 18:59:01 +00:00
|
|
|
|
2018-04-25 15:24:27 +00:00
|
|
|
preprocessText(lexed);
|
2018-05-14 22:23:55 +00:00
|
|
|
preprocessElements(lexed, filename);
|
2017-01-25 20:54:34 +00:00
|
|
|
|
2018-04-25 15:24:27 +00:00
|
|
|
// Generate the table of contents. This mutates the lexed contents in-place.
|
|
|
|
const toc = buildToc(lexed, filename);
|
2017-01-23 03:16:21 +00:00
|
|
|
|
2018-04-25 15:24:27 +00:00
|
|
|
const id = filename.replace(/\W+/g, '-');
|
2012-02-27 18:59:01 +00:00
|
|
|
|
2018-04-25 15:24:27 +00:00
|
|
|
let HTML = template.replace('__ID__', id)
|
|
|
|
.replace(/__FILENAME__/g, filename)
|
|
|
|
.replace('__SECTION__', section)
|
|
|
|
.replace(/__VERSION__/g, nodeVersion)
|
|
|
|
.replace('__TOC__', toc)
|
|
|
|
.replace('__GTOC__', gtocHTML.replace(
|
|
|
|
`class="nav-${id}`, `class="nav-${id} active`));
|
2012-02-27 18:59:01 +00:00
|
|
|
|
2018-04-25 15:24:27 +00:00
|
|
|
if (analytics) {
|
|
|
|
HTML = HTML.replace('<!-- __TRACKING__ -->', `
|
2017-01-25 20:54:34 +00:00
|
|
|
<script src="assets/dnt_helper.js"></script>
|
|
|
|
<script>
|
|
|
|
if (!_dntEnabled()) {
|
|
|
|
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;
|
|
|
|
i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)},
|
|
|
|
i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];
|
|
|
|
a.async=1;a.src=g;m.parentNode.insertBefore(a,m)})(window,document,
|
|
|
|
'script','//www.google-analytics.com/analytics.js','ga');
|
|
|
|
ga('create', '${analytics}', 'auto');
|
|
|
|
ga('send', 'pageview');
|
|
|
|
}
|
2018-04-25 15:24:27 +00:00
|
|
|
</script>`);
|
2017-01-23 03:16:21 +00:00
|
|
|
}
|
|
|
|
|
2018-04-25 15:24:27 +00:00
|
|
|
const docCreated = input.match(
|
|
|
|
/<!--\s*introduced_in\s*=\s*v([0-9]+)\.([0-9]+)\.[0-9]+\s*-->/);
|
|
|
|
if (docCreated) {
|
|
|
|
HTML = HTML.replace('__ALTDOCS__', altDocs(filename, docCreated));
|
|
|
|
} else {
|
|
|
|
console.error(`Failed to add alternative version links to ${filename}`);
|
|
|
|
HTML = HTML.replace('__ALTDOCS__', '');
|
2017-01-23 03:16:21 +00:00
|
|
|
}
|
|
|
|
|
2018-04-25 15:24:27 +00:00
|
|
|
// Content insertion has to be the last thing we do with the lexed tokens,
|
|
|
|
// because it's destructive.
|
|
|
|
HTML = HTML.replace('__CONTENT__', marked.parser(lexed));
|
2017-01-23 03:16:21 +00:00
|
|
|
|
2018-04-25 15:24:27 +00:00
|
|
|
cb(null, HTML);
|
2017-01-23 03:16:21 +00:00
|
|
|
}
|
|
|
|
|
2018-03-30 10:38:45 +00:00
|
|
|
// Handle general body-text replacements.
|
|
|
|
// For example, link man page references to the actual page.
|
2018-04-25 15:24:27 +00:00
|
|
|
function preprocessText(lexed) {
|
|
|
|
lexed.forEach((token) => {
|
|
|
|
if (token.type === 'table') {
|
|
|
|
if (token.header) {
|
|
|
|
token.header = token.header.map(replaceInText);
|
2017-04-19 17:22:52 +00:00
|
|
|
}
|
|
|
|
|
2018-04-25 15:24:27 +00:00
|
|
|
if (token.cells) {
|
|
|
|
token.cells.forEach((row, i) => {
|
|
|
|
token.cells[i] = row.map(replaceInText);
|
2017-04-19 17:22:52 +00:00
|
|
|
});
|
|
|
|
}
|
2018-04-25 15:24:27 +00:00
|
|
|
} else if (token.text && token.type !== 'code') {
|
|
|
|
token.text = replaceInText(token.text);
|
2016-02-04 12:11:17 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2012-02-27 18:59:01 +00:00
|
|
|
|
2018-04-25 15:24:27 +00:00
|
|
|
// Replace placeholders in text tokens.
|
|
|
|
function replaceInText(text) {
|
|
|
|
if (text === '') return text;
|
|
|
|
return linkJsTypeDocs(linkManPages(text));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Syscalls which appear in the docs, but which only exist in BSD / macOS.
|
|
|
|
const BSD_ONLY_SYSCALLS = new Set(['lchmod']);
|
|
|
|
const MAN_PAGE = /(^|\s)([a-z.]+)\((\d)([a-z]?)\)/gm;
|
|
|
|
|
|
|
|
// Handle references to man pages, eg "open(2)" or "lchmod(2)".
|
|
|
|
// Returns modified text, with such refs replaced with HTML links, for example
|
|
|
|
// '<a href="http://man7.org/linux/man-pages/man2/open.2.html">open(2)</a>'.
|
|
|
|
function linkManPages(text) {
|
|
|
|
return text.replace(
|
|
|
|
MAN_PAGE, (match, beginning, name, number, optionalCharacter) => {
|
|
|
|
// Name consists of lowercase letters,
|
|
|
|
// number is a single digit with an optional lowercase letter.
|
|
|
|
const displayAs = `${name}(${number}${optionalCharacter})`;
|
|
|
|
|
|
|
|
if (BSD_ONLY_SYSCALLS.has(name)) {
|
|
|
|
return `${beginning}<a href="https://www.freebsd.org/cgi/man.cgi` +
|
|
|
|
`?query=${name}&sektion=${number}">${displayAs}</a>`;
|
|
|
|
}
|
|
|
|
return `${beginning}<a href="http://man7.org/linux/man-pages/man${number}` +
|
|
|
|
`/${name}.${number}${optionalCharacter}.html">${displayAs}</a>`;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
const TYPE_SIGNATURE = /\{[^}]+\}/g;
|
|
|
|
function linkJsTypeDocs(text) {
|
|
|
|
const parts = text.split('`');
|
|
|
|
|
|
|
|
// Handle types, for example the source Markdown might say
|
|
|
|
// "This argument should be a {number} or {string}".
|
|
|
|
for (let i = 0; i < parts.length; i += 2) {
|
|
|
|
const typeMatches = parts[i].match(TYPE_SIGNATURE);
|
|
|
|
if (typeMatches) {
|
|
|
|
typeMatches.forEach((typeMatch) => {
|
|
|
|
parts[i] = parts[i].replace(typeMatch, typeParser.toLink(typeMatch));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return parts.join('`');
|
|
|
|
}
|
|
|
|
|
2018-05-04 09:16:38 +00:00
|
|
|
// Preprocess stability blockquotes and YAML blocks.
|
2018-05-14 22:23:55 +00:00
|
|
|
function preprocessElements(lexed, filename) {
|
2018-04-25 15:24:27 +00:00
|
|
|
const STABILITY_RE = /(.*:)\s*(\d)([\s\S]*)/;
|
|
|
|
let state = null;
|
2016-09-02 20:27:01 +00:00
|
|
|
let headingIndex = -1;
|
|
|
|
let heading = null;
|
|
|
|
|
2018-04-25 15:24:27 +00:00
|
|
|
lexed.forEach((token, index) => {
|
|
|
|
if (token.type === 'heading') {
|
2018-05-04 09:16:38 +00:00
|
|
|
headingIndex = index;
|
2018-04-25 15:24:27 +00:00
|
|
|
heading = token;
|
2018-05-04 09:16:38 +00:00
|
|
|
}
|
2018-04-25 15:24:27 +00:00
|
|
|
if (token.type === 'html' && common.isYAMLBlock(token.text)) {
|
|
|
|
token.text = parseYAML(token.text);
|
2018-05-04 09:16:38 +00:00
|
|
|
}
|
2018-04-25 15:24:27 +00:00
|
|
|
if (token.type === 'blockquote_start') {
|
2016-07-15 22:35:38 +00:00
|
|
|
state = 'MAYBE_STABILITY_BQ';
|
2018-04-25 15:24:27 +00:00
|
|
|
lexed[index] = { type: 'space' };
|
2012-12-28 01:32:53 +00:00
|
|
|
}
|
2018-04-25 15:24:27 +00:00
|
|
|
if (token.type === 'blockquote_end' && state === 'MAYBE_STABILITY_BQ') {
|
2018-05-04 09:16:38 +00:00
|
|
|
state = null;
|
2018-04-25 15:24:27 +00:00
|
|
|
lexed[index] = { type: 'space' };
|
2016-07-15 22:35:38 +00:00
|
|
|
}
|
2018-04-25 15:24:27 +00:00
|
|
|
if (token.type === 'paragraph' && state === 'MAYBE_STABILITY_BQ') {
|
|
|
|
if (token.text.includes('Stability:')) {
|
|
|
|
const [, prefix, number, explication] = token.text.match(STABILITY_RE);
|
2016-09-02 20:27:01 +00:00
|
|
|
const isStabilityIndex =
|
2018-03-30 10:38:45 +00:00
|
|
|
index - 2 === headingIndex || // General.
|
|
|
|
index - 3 === headingIndex; // With api_metadata block.
|
2016-09-02 20:27:01 +00:00
|
|
|
|
|
|
|
if (heading && isStabilityIndex) {
|
2018-04-25 15:24:27 +00:00
|
|
|
heading.stability = number;
|
2016-09-02 20:27:01 +00:00
|
|
|
headingIndex = -1;
|
|
|
|
heading = null;
|
|
|
|
}
|
2018-05-14 22:23:55 +00:00
|
|
|
|
|
|
|
// Do not link to the section we are already in.
|
|
|
|
const noLinking = filename === 'documentation' &&
|
|
|
|
heading !== null && heading.text === 'Stability Index';
|
2018-04-25 15:24:27 +00:00
|
|
|
token.text = `<div class="api_stability api_stability_${number}">` +
|
2018-05-14 22:23:55 +00:00
|
|
|
(noLinking ? '' :
|
|
|
|
'<a href="documentation.html#documentation_stability_index">') +
|
|
|
|
`${prefix} ${number}${noLinking ? '' : '</a>'}${explication}</div>`
|
2018-04-25 15:24:27 +00:00
|
|
|
.replace(/\n/g, ' ');
|
2018-05-14 22:23:55 +00:00
|
|
|
|
2018-04-25 15:24:27 +00:00
|
|
|
lexed[index] = { type: 'html', text: token.text };
|
2016-07-15 22:35:38 +00:00
|
|
|
} else if (state === 'MAYBE_STABILITY_BQ') {
|
2018-05-04 09:16:38 +00:00
|
|
|
state = null;
|
2018-04-25 15:24:27 +00:00
|
|
|
lexed[index - 1] = { type: 'blockquote_start' };
|
2012-02-27 18:59:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-11-14 02:40:38 +00:00
|
|
|
function parseYAML(text) {
|
|
|
|
const meta = common.extractAndParseYAML(text);
|
2018-04-25 15:24:27 +00:00
|
|
|
let html = '<div class="api_metadata">\n';
|
2015-11-14 02:40:38 +00:00
|
|
|
|
2017-01-10 19:43:57 +00:00
|
|
|
const added = { description: '' };
|
|
|
|
const deprecated = { description: '' };
|
|
|
|
|
2016-04-30 23:51:38 +00:00
|
|
|
if (meta.added) {
|
2017-01-10 19:43:57 +00:00
|
|
|
added.version = meta.added.join(', ');
|
|
|
|
added.description = `<span>Added in: ${added.version}</span>`;
|
2016-04-30 23:51:38 +00:00
|
|
|
}
|
2015-11-14 02:40:38 +00:00
|
|
|
|
2016-04-30 23:51:38 +00:00
|
|
|
if (meta.deprecated) {
|
2017-01-10 19:43:57 +00:00
|
|
|
deprecated.version = meta.deprecated.join(', ');
|
|
|
|
deprecated.description =
|
|
|
|
`<span>Deprecated since: ${deprecated.version}</span>`;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (meta.changes.length > 0) {
|
2018-04-25 15:24:27 +00:00
|
|
|
if (added.description) meta.changes.push(added);
|
|
|
|
if (deprecated.description) meta.changes.push(deprecated);
|
2017-01-10 19:43:57 +00:00
|
|
|
|
2018-04-25 15:24:27 +00:00
|
|
|
meta.changes.sort((a, b) => versionSort(a.version, b.version));
|
2017-01-10 19:43:57 +00:00
|
|
|
|
2018-04-25 15:24:27 +00:00
|
|
|
html += '<details class="changelog"><summary>History</summary>\n' +
|
|
|
|
'<table>\n<tr><th>Version</th><th>Changes</th></tr>\n';
|
2017-01-10 19:43:57 +00:00
|
|
|
|
2018-04-25 15:24:27 +00:00
|
|
|
meta.changes.forEach((change) => {
|
|
|
|
html += `<tr><td>${change.version}</td>\n` +
|
|
|
|
`<td>${marked(change.description)}</td></tr>\n`;
|
2017-01-10 19:43:57 +00:00
|
|
|
});
|
|
|
|
|
2018-04-25 15:24:27 +00:00
|
|
|
html += '</table>\n</details>\n';
|
2017-01-10 19:43:57 +00:00
|
|
|
} else {
|
2018-04-25 15:24:27 +00:00
|
|
|
html += `${added.description}${deprecated.description}\n`;
|
2013-12-25 15:59:25 +00:00
|
|
|
}
|
|
|
|
|
2018-04-25 15:24:27 +00:00
|
|
|
html += '</div>';
|
|
|
|
return html;
|
2012-02-27 18:59:01 +00:00
|
|
|
}
|
|
|
|
|
2018-04-25 15:24:27 +00:00
|
|
|
const numberRe = /^\d*/;
|
|
|
|
function versionSort(a, b) {
|
|
|
|
a = a.trim();
|
|
|
|
b = b.trim();
|
|
|
|
let i = 0; // Common prefix length.
|
|
|
|
while (i < a.length && i < b.length && a[i] === b[i]) i++;
|
|
|
|
a = a.substr(i);
|
|
|
|
b = b.substr(i);
|
|
|
|
return +b.match(numberRe)[0] - +a.match(numberRe)[0];
|
2017-11-05 17:36:50 +00:00
|
|
|
}
|
2012-02-27 18:59:01 +00:00
|
|
|
|
2018-04-25 15:24:27 +00:00
|
|
|
function buildToc(lexed, filename) {
|
2016-05-23 22:06:09 +00:00
|
|
|
const startIncludeRefRE = /^\s*<!-- \[start-include:(.+)\] -->\s*$/;
|
2018-04-25 15:24:27 +00:00
|
|
|
const endIncludeRefRE = /^\s*<!-- \[end-include:.+\] -->\s*$/;
|
2016-05-23 22:06:09 +00:00
|
|
|
const realFilenames = [filename];
|
2018-04-25 15:24:27 +00:00
|
|
|
const idCounters = Object.create(null);
|
|
|
|
let toc = '';
|
|
|
|
let depth = 0;
|
|
|
|
|
|
|
|
lexed.forEach((token) => {
|
|
|
|
// Keep track of the current filename along comment wrappers of inclusions.
|
|
|
|
if (token.type === 'html') {
|
|
|
|
const [, includedFileName] = token.text.match(startIncludeRefRE) || [];
|
|
|
|
if (includedFileName !== undefined)
|
|
|
|
realFilenames.unshift(includedFileName);
|
|
|
|
else if (endIncludeRefRE.test(token.text))
|
2016-05-23 22:06:09 +00:00
|
|
|
realFilenames.shift();
|
|
|
|
}
|
|
|
|
|
2018-04-25 15:24:27 +00:00
|
|
|
if (token.type !== 'heading') return;
|
|
|
|
|
|
|
|
if (token.depth - depth > 1) {
|
|
|
|
throw new Error(`Inappropriate heading level:\n${JSON.stringify(token)}`);
|
2012-02-27 18:59:01 +00:00
|
|
|
}
|
|
|
|
|
2018-04-25 15:24:27 +00:00
|
|
|
depth = token.depth;
|
2016-05-23 22:06:09 +00:00
|
|
|
const realFilename = path.basename(realFilenames[0], '.md');
|
2018-04-25 15:24:27 +00:00
|
|
|
const headingText = token.text.trim();
|
|
|
|
const id = getId(`${realFilename}_${headingText}`, idCounters);
|
2018-05-14 22:23:55 +00:00
|
|
|
|
|
|
|
const hasStability = token.stability !== undefined;
|
2018-04-25 15:24:27 +00:00
|
|
|
toc += ' '.repeat((depth - 1) * 2) +
|
2018-05-14 22:23:55 +00:00
|
|
|
(hasStability ? `* <span class="stability_${token.stability}">` : '* ') +
|
|
|
|
`<a href="#${id}">${token.text}</a>${hasStability ? '</span>' : ''}\n`;
|
|
|
|
|
2018-04-25 15:24:27 +00:00
|
|
|
token.text += `<span><a class="mark" href="#${id}" id="${id}">#</a></span>`;
|
|
|
|
if (realFilename === 'errors' && headingText.startsWith('ERR_')) {
|
|
|
|
token.text += `<span><a class="mark" href="#${headingText}" ` +
|
|
|
|
`id="${headingText}">#</a></span>`;
|
2017-11-05 17:36:50 +00:00
|
|
|
}
|
2012-02-27 18:59:01 +00:00
|
|
|
});
|
|
|
|
|
2018-04-25 15:24:27 +00:00
|
|
|
return marked(toc);
|
2012-02-27 18:59:01 +00:00
|
|
|
}
|
|
|
|
|
2018-04-25 15:24:27 +00:00
|
|
|
const notAlphaNumerics = /[^a-z0-9]+/g;
|
|
|
|
const edgeUnderscores = /^_+|_+$/g;
|
|
|
|
const notAlphaStart = /^[^a-z]/;
|
|
|
|
function getId(text, idCounters) {
|
|
|
|
text = text.toLowerCase()
|
|
|
|
.replace(notAlphaNumerics, '_')
|
|
|
|
.replace(edgeUnderscores, '')
|
|
|
|
.replace(notAlphaStart, '_$&');
|
|
|
|
if (idCounters[text] !== undefined) {
|
|
|
|
return `${text}_${++idCounters[text]}`;
|
2012-02-27 18:59:01 +00:00
|
|
|
}
|
2018-04-25 15:24:27 +00:00
|
|
|
idCounters[text] = 0;
|
2012-02-27 18:59:01 +00:00
|
|
|
return text;
|
|
|
|
}
|
2017-01-10 19:43:57 +00:00
|
|
|
|
2018-04-25 15:24:27 +00:00
|
|
|
function altDocs(filename, docCreated) {
|
|
|
|
const [, docCreatedMajor, docCreatedMinor] = docCreated.map(Number);
|
|
|
|
const host = 'https://nodejs.org';
|
|
|
|
const versions = [
|
|
|
|
{ num: '10.x' },
|
|
|
|
{ num: '9.x' },
|
|
|
|
{ num: '8.x', lts: true },
|
|
|
|
{ num: '7.x' },
|
|
|
|
{ num: '6.x', lts: true },
|
|
|
|
{ num: '5.x' },
|
|
|
|
{ num: '4.x', lts: true },
|
|
|
|
{ num: '0.12.x' },
|
|
|
|
{ num: '0.10.x' }
|
|
|
|
];
|
|
|
|
|
|
|
|
const getHref = (versionNum) =>
|
|
|
|
`${host}/docs/latest-v${versionNum}/api/${filename}.html`;
|
|
|
|
|
|
|
|
const wrapInListItem = (version) =>
|
|
|
|
`<li><a href="${getHref(version.num)}">${version.num}` +
|
|
|
|
`${version.lts ? ' <b>LTS</b>' : ''}</a></li>`;
|
|
|
|
|
|
|
|
function isDocInVersion(version) {
|
|
|
|
const [versionMajor, versionMinor] = version.num.split('.').map(Number);
|
|
|
|
if (docCreatedMajor > versionMajor) return false;
|
|
|
|
if (docCreatedMajor < versionMajor) return true;
|
|
|
|
return docCreatedMinor <= versionMinor;
|
|
|
|
}
|
|
|
|
|
|
|
|
const list = versions.filter(isDocInVersion).map(wrapInListItem).join('\n');
|
|
|
|
|
|
|
|
return list ? `
|
|
|
|
<li class="version-picker">
|
|
|
|
<a href="#">View another version <span>▼</span></a>
|
|
|
|
<ol class="version-picker">${list}</ol>
|
|
|
|
</li>
|
|
|
|
` : '';
|
2017-01-10 19:43:57 +00:00
|
|
|
}
|