diff --git a/lib/core/nav/SideNav.js b/lib/core/nav/SideNav.js
index 3ab9b04ec3..dfb128df34 100644
--- a/lib/core/nav/SideNav.js
+++ b/lib/core/nav/SideNav.js
@@ -78,8 +78,9 @@ class SideNav extends React.Component {
? i18n["localized-strings"][sbTitle] || sbTitle
: sbTitle;
} else {
+ const id = metadata.original_id || metadata.localized_id;
localizedString = i18n
- ? i18n["localized-strings"][metadata.localized_id] || metadata.title
+ ? i18n["localized-strings"][id] || metadata.title
: metadata.title;
}
return localizedString;
diff --git a/lib/server/generate.js b/lib/server/generate.js
index 16965dcc54..4a0979f3d7 100644
--- a/lib/server/generate.js
+++ b/lib/server/generate.js
@@ -20,8 +20,13 @@ function execute() {
const Site = require("../core/Site.js");
const siteConfig = require(CWD + "/siteConfig.js");
const translate = require("./translate.js");
+ const versionFallback = require("./versionFallback.js");
+
+ const ENABLE_TRANSLATION = fs.existsSync(CWD + "/languages.js");
+ const ENABLE_VERSIONING = fs.existsSync(CWD + "/versions.json");
+
let languages;
- if (fs.existsSync(CWD + "/languages.js")) {
+ if (ENABLE_TRANSLATION) {
languages = require(CWD + "/languages.js");
} else {
languages = [
@@ -77,114 +82,98 @@ function execute() {
readMetadata.generateDocsMetadata();
const Metadata = require("../core/metadata.js");
- let mdToHtml = {};
- for (let i = 0; i < Metadata.length; i++) {
- const metadata = Metadata[i];
- if (metadata.language !== "en") {
- continue;
+
+ const mdToHtml = {};
+ Object.keys(Metadata).forEach(id => {
+ const metadata = Metadata[id];
+ if (metadata.language !== "en" || metadata.original_id) {
+ return;
}
- mdToHtml[metadata.source] = siteConfig.baseUrl + metadata.permalink;
- }
+ let htmlLink =
+ siteConfig.baseUrl + metadata.permalink.replace("/next/", "/");
+ if (htmlLink.includes("/docs/en/")) {
+ htmlLink = htmlLink.replace("/docs/en/", "/docs/en/VERSION/");
+ } else {
+ htmlLink = htmlLink.replace("/docs/", "/docs/VERSION/");
+ }
+ mdToHtml[metadata.source] = htmlLink;
+ });
const DocsLayout = require("../core/DocsLayout.js");
fs.removeSync(CWD + "/build");
- // create html files for all English docs
- let files = glob.sync(CWD + "/../docs/**");
- files.forEach(file => {
- // console.log(file);
- let language = "en";
+ // create html files for all docs
+ Object.keys(Metadata).forEach(id => {
+ const metadata = Metadata[id];
- const extension = path.extname(file);
-
- if (extension === ".md" || extension === ".markdown") {
- const result = readMetadata.processMetadata(file);
- if (!result) {
- return;
+ let file;
+ if (metadata.original_id) {
+ if (ENABLE_TRANSLATION && metadata.language !== "en") {
+ file =
+ CWD + "/translated_docs/" + metadata.language + "/" + metadata.source;
+ } else {
+ file = CWD + "/versioned_docs/" + metadata.source;
}
-
- const metadata = result.metadata;
- let rawContent = result.rawContent;
-
- /* generate table of contents if appropriate */
- if (rawContent && rawContent.indexOf(TABLE_OF_CONTENTS_TOKEN) != -1) {
- rawContent = insertTableOfContents(rawContent);
+ } else {
+ if (metadata.language === "en") {
+ file = CWD + "/../docs/" + metadata.source;
+ } else {
+ file =
+ CWD + "/translated_docs/" + metadata.language + "/" + metadata.source;
}
-
- /* replace any links to markdown files to their website html links */
- Object.keys(mdToHtml).forEach(function(key, index) {
- rawContent = rawContent.replace(
- new RegExp(key, "g"),
- mdToHtml[key].replace("/en/", "/" + language + "/")
- );
- });
-
- const docComp = (
-
- {rawContent}
-
- );
- const str = renderToStaticMarkup(docComp);
-
- let targetFile =
- CWD + "/build/" + siteConfig.projectName + "/" + metadata.permalink;
- // console.log(targetFile);
- writeFileAndCreateFolder(targetFile, str);
}
- });
- // create html files for all non-English docs
- if (languages.length > 1) {
- files = glob.sync(CWD + "/translated_docs/**");
- files.forEach(file => {
- let language = "en";
+ if (!fs.existsSync(file)) {
+ return;
+ }
- const regexSubFolder = /translated_docs\/(.*)\/.*/;
- const match = regexSubFolder.exec(file);
- if (match) {
- language = match[1];
- }
+ let rawContent = readMetadata.extractMetadata(fs.readFileSync(file, "utf8"))
+ .rawContent;
- if (enabledLanguages.indexOf(language) === -1) {
- return;
- }
+ const language = metadata.language;
- const extension = path.extname(file);
- if (extension !== ".md" && extension !== ".markdown") {
- return;
- }
+ /* generate table of contents if appropriate */
+ if (rawContent && rawContent.indexOf(TABLE_OF_CONTENTS_TOKEN) != -1) {
+ rawContent = insertTableOfContents(rawContent);
+ }
- const result = readMetadata.processMetadata(file);
- if (!result) {
- return;
- }
+ let latestVersion;
+ if (ENABLE_VERSIONING) {
+ latestVersion = JSON.parse(
+ fs.readFileSync(CWD + "/versions.json", "utf8")
+ )[0];
+ }
- const metadata = result.metadata;
- let rawContent = result.rawContent;
-
- /* generate table of contents if appropriate */
- if (rawContent && rawContent.indexOf(TABLE_OF_CONTENTS_TOKEN) != -1) {
- rawContent = insertTableOfContents(rawContent);
- }
-
- /* replace any links to markdown files to their website html links */
- Object.keys(mdToHtml).forEach(function(key, index) {
- rawContent = rawContent.replace(new RegExp(key, "g"), mdToHtml[key]);
- });
-
- const docComp = (
-
- {rawContent}
-
+ /* replace any links to markdown files to their website html links */
+ Object.keys(mdToHtml).forEach(function(key, index) {
+ let link = mdToHtml[key];
+ link = link.replace("/en/", "/" + language + "/");
+ link = link.replace(
+ "/VERSION/",
+ metadata.version && (metadata.version !== latestVersion)
+ ? "/" + metadata.version + "/"
+ : "/"
);
- const str = renderToStaticMarkup(docComp);
- let targetFile =
- CWD + "/build/" + siteConfig.projectName + "/" + metadata.permalink;
- // console.log(targetFile);
- writeFileAndCreateFolder(targetFile, str);
+ rawContent = rawContent.replace(new RegExp(key, "g"), link);
});
- }
+
+ rawContent = rawContent.replace(
+ /\]\(assets\//g,
+ "](" + siteConfig.baseUrl + "docs/assets/"
+ );
+
+ const docComp = (
+
+ {rawContent}
+
+ );
+ const str = renderToStaticMarkup(docComp);
+ const targetFile =
+ CWD + "/build/" + siteConfig.projectName + "/" + metadata.permalink;
+
+ writeFileAndCreateFolder(targetFile, str);
+ });
/* copy docs assets if they exist */
if (fs.existsSync(CWD + "/../docs/assets")) {
diff --git a/lib/server/readCategories.js b/lib/server/readCategories.js
index 2ae0d04875..c90310992e 100644
--- a/lib/server/readCategories.js
+++ b/lib/server/readCategories.js
@@ -34,8 +34,12 @@ function readCategories(sidebar) {
for (let k = 0; k < enabledLanguages.length; ++k) {
const language = enabledLanguages[k];
- const metadatas = Metadata.filter(metadata => {
- return metadata.sidebar === sidebar && metadata.language === language;
+ const metadatas = [];
+ Object.keys(Metadata).forEach(id => {
+ const metadata = Metadata[id];
+ if (metadata.sidebar === sidebar && metadata.language === language) {
+ metadatas.push(metadata);
+ }
});
// Build a hashmap of article_id -> metadata
diff --git a/lib/server/readMetadata.js b/lib/server/readMetadata.js
index 613c8d4249..3fdd56ffd6 100644
--- a/lib/server/readMetadata.js
+++ b/lib/server/readMetadata.js
@@ -14,6 +14,10 @@ const fs = require("fs");
const os = require("os");
const glob = require("glob");
const siteConfig = require(CWD + "/siteConfig.js");
+const versionFallback = require("./versionFallback.js");
+
+const ENABLE_VERSIONING = fs.existsSync(CWD + "/versions.json");
+
let languages;
if (fs.existsSync(CWD + "/languages.js")) {
languages = require(CWD + "/languages.js");
@@ -28,7 +32,9 @@ if (fs.existsSync(CWD + "/languages.js")) {
}
function readSidebar() {
- const allSidebars = require(CWD + "/sidebars.json");
+ let allSidebars = require(CWD + "/sidebars.json");
+ Object.assign(allSidebars, versionFallback.sidebarData());
+
const order = {};
Object.keys(allSidebars).forEach(sidebar => {
@@ -91,6 +97,7 @@ function extractMetadata(content) {
return { metadata, rawContent: both.content };
}
+// process the metadata for a document found in the docs folder
function processMetadata(file) {
const result = extractMetadata(fs.readFileSync(file, "utf8"));
if (!result.metadata || !result.rawContent) {
@@ -115,6 +122,18 @@ function processMetadata(file) {
metadata.permalink = "docs/" + language + "/" + metadata.id + ".html";
}
+ if (ENABLE_VERSIONING) {
+ metadata.version = "next";
+ if (languages.length === 1 && !siteConfig.useEnglishUrl) {
+ metadata.permalink = metadata.permalink.replace("docs/", "docs/next/");
+ } else {
+ metadata.permalink = metadata.permalink.replace(
+ "docs/" + language + "/",
+ "docs/" + language + "/next/"
+ );
+ }
+ }
+
// change ids previous, next
metadata.localized_id = metadata.id;
metadata.id = language + "-" + metadata.id;
@@ -150,7 +169,7 @@ function generateDocsMetadata() {
enabledLanguages.push(lang.tag);
});
- const metadatas = [];
+ const metadatas = {};
/* metadata for english files */
let files = glob.sync(CWD + "/../docs/**");
@@ -165,7 +184,7 @@ function generateDocsMetadata() {
return;
}
let metadata = res.metadata;
- metadatas.push(metadata);
+ metadatas[metadata.id] = metadata;
}
});
@@ -190,10 +209,32 @@ function generateDocsMetadata() {
return;
}
let metadata = res.metadata;
- metadatas.push(metadata);
+ metadatas[metadata.id] = metadata;
}
});
+ versionData = versionFallback.docData();
+ versionData.forEach(metadata => {
+ const id = metadata.localized_id;
+ metadata.sidebar = order[id].sidebar;
+ metadata.category = order[id].category;
+ if (order[id].next) {
+ metadata.next_id = order[id].next.replace(
+ "version-" + metadata.version + "-",
+ ""
+ );
+ metadata.next = metadata.language + "-" + order[id].next;
+ }
+ if (order[id].previous) {
+ metadata.previous_id = order[id].previous.replace(
+ "version-" + metadata.version + "-",
+ ""
+ );
+ metadata.previous = metadata.language + "-" + order[id].previous;
+ }
+ metadatas[metadata.id] = metadata;
+ });
+
fs.writeFileSync(
__dirname + "/../core/metadata.js",
"/**\n" +
@@ -247,6 +288,7 @@ function generateBlogMetadata() {
}
module.exports = {
+ readSidebar,
extractMetadata,
processMetadata,
generateDocsMetadata,
diff --git a/lib/server/server.js b/lib/server/server.js
index 168ecc4d55..d54322655f 100644
--- a/lib/server/server.js
+++ b/lib/server/server.js
@@ -20,9 +20,11 @@ function execute(port) {
const mkdirp = require("mkdirp");
const glob = require("glob");
const translate = require("./translate.js");
+ const versionFallback = require("./versionFallback");
const CWD = process.cwd();
const ENABLE_TRANSLATION = fs.existsSync(CWD + "/languages.js");
+ const ENABLE_VERSIONING = fs.existsSync(CWD + "/versions.json");
let siteConfig = require(CWD + "/siteConfig.js");
@@ -127,49 +129,84 @@ function execute(port) {
purgeCache(CWD + "/siteConfig.js");
siteConfig = require(CWD + "/siteConfig.js");
+ let url = req.path.toString().replace(siteConfig.baseUrl, "");
+
reloadMetadata();
+
+ // links is a map from a permalink to an id
let links = {};
- for (let i = 0; i < Metadata.length; i++) {
- const metadata = Metadata[i];
- if (metadata.language === "en") {
- links[metadata.permalink] = CWD + "/../docs/" + metadata.source;
+ Object.keys(Metadata).forEach(id => {
+ const metadata = Metadata[id];
+ links[metadata.permalink] = id;
+ });
+
+ const mdToHtml = {};
+ Object.keys(Metadata).forEach(id => {
+ const metadata = Metadata[id];
+ if (metadata.language !== "en" || metadata.original_id) {
+ return;
+ }
+ let htmlLink =
+ siteConfig.baseUrl + metadata.permalink.replace("/next/", "/");
+ if (htmlLink.includes("/docs/en/")) {
+ htmlLink = htmlLink.replace("/docs/en/", "/docs/en/VERSION/");
} else {
- links[metadata.permalink] =
+ htmlLink = htmlLink.replace("/docs/", "/docs/VERSION/");
+ }
+ mdToHtml[metadata.source] = htmlLink;
+ });
+
+ const metadata = Metadata[links[url]];
+ const language = metadata.language;
+
+ let file;
+ if (metadata.original_id) {
+ if (ENABLE_TRANSLATION && metadata.language !== "en") {
+ file =
+ CWD + "/translated_docs/" + metadata.language + "/" + metadata.source;
+ } else {
+ file = CWD + "/versioned_docs/" + metadata.source;
+ }
+ } else {
+ if (metadata.language === "en") {
+ file = CWD + "/../docs/" + metadata.source;
+ } else {
+ file =
CWD + "/translated_docs/" + metadata.language + "/" + metadata.source;
}
}
- let mdToHtml = {};
- for (let i = 0; i < Metadata.length; i++) {
- const metadata = Metadata[i];
- if (metadata.language !== "en") {
- continue;
- }
- mdToHtml[metadata.source] = siteConfig.baseUrl + metadata.permalink;
- }
-
- let file = links[req.path.toString().replace(siteConfig.baseUrl, "")];
if (!fs.existsSync(file)) {
next();
return;
}
- const result = readMetadata.processMetadata(file);
- const metadata = result.metadata;
- const language = metadata.language;
- let rawContent = result.rawContent;
+ let rawContent = readMetadata.extractMetadata(fs.readFileSync(file, "utf8"))
+ .rawContent;
/* generate table of contents if appropriate */
if (rawContent && rawContent.indexOf(TABLE_OF_CONTENTS_TOKEN) !== -1) {
rawContent = insertTableOfContents(rawContent);
}
+ let latestVersion;
+ if (ENABLE_VERSIONING) {
+ latestVersion = JSON.parse(
+ fs.readFileSync(CWD + "/versions.json", "utf8")
+ )[0];
+ }
+
/* replace any links to markdown files to their website html links */
Object.keys(mdToHtml).forEach(function(key, index) {
- rawContent = rawContent.replace(
- new RegExp(key, "g"),
- mdToHtml[key].replace("/en/", "/" + language + "/")
+ let link = mdToHtml[key];
+ link = link.replace("/en/", "/" + language + "/");
+ link = link.replace(
+ "/VERSION/",
+ metadata.version && (metadata.version !== latestVersion)
+ ? "/" + metadata.version + "/"
+ : "/"
);
+ rawContent = rawContent.replace(new RegExp(key, "g"), link);
});
rawContent = rawContent.replace(
@@ -187,6 +224,7 @@ function execute(port) {
res.send(renderToStaticMarkup(docComp));
});
+
/* handle all requests for blog pages and posts */
app.get(/blog\/.*html$/, (req, res) => {
purgeCache(CWD + "/siteConfig.js");
diff --git a/lib/server/versionFallback.js b/lib/server/versionFallback.js
new file mode 100644
index 0000000000..5a8699a59e
--- /dev/null
+++ b/lib/server/versionFallback.js
@@ -0,0 +1,301 @@
+/**
+ * Copyright (c) 2017-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+const CWD = process.cwd();
+const glob = require("glob");
+const fs = require("fs");
+const path = require("path");
+const diff = require("diff");
+const assert = require("assert");
+
+const versions = require(CWD + "/versions.json");
+const siteConfig = require(CWD + "/siteConfig.js");
+
+const ENABLE_TRANSLATION = fs.existsSync(CWD + "/languages.js");
+let languages;
+if (fs.existsSync(CWD + "/languages.js")) {
+ languages = require(CWD + "/languages.js");
+} else {
+ languages = [
+ {
+ enabled: true,
+ name: "English",
+ tag: "en"
+ }
+ ];
+}
+
+/*****************************************************************/
+
+// included to prevent cyclical dependency with readMetadata.js
+
+function splitHeader(content) {
+ const lines = content.split("\n");
+ let i = 1;
+ for (; i < lines.length - 1; ++i) {
+ if (lines[i] === "---") {
+ break;
+ }
+ }
+ return {
+ header: lines.slice(1, i + 1).join("\n"),
+ content: lines.slice(i + 1).join("\n")
+ };
+}
+
+// Extract markdown metadata header
+function extractMetadata(content) {
+ const metadata = {};
+ const both = splitHeader(content);
+ const lines = both.header.split("\n");
+ for (let i = 0; i < lines.length - 1; ++i) {
+ const keyvalue = lines[i].split(":");
+ const key = keyvalue[0].trim();
+ let value = keyvalue.slice(1).join(":").trim();
+ try {
+ value = JSON.parse(value);
+ } catch (e) {}
+ metadata[key] = value;
+ }
+ return { metadata, rawContent: both.content };
+}
+
+/*****************************************************************/
+
+const versionFolder = CWD + "/versioned_docs/";
+
+// available stores doc ids of documents that are available for
+// each version
+const available = {};
+// versionFiles is used to keep track of what file to use with a
+// given version/id of a document
+const versionFiles = {};
+files = glob.sync(versionFolder + "**");
+files.forEach(file => {
+ const ext = path.extname(file);
+ if (ext !== ".md" && ext !== ".markdown") {
+ return;
+ }
+ const res = extractMetadata(fs.readFileSync(file, "utf8"));
+ const metadata = res.metadata;
+
+ if (!(metadata.original_id in available)) {
+ available[metadata.original_id] = new Set();
+ }
+ const version = metadata.id.split("-")[1];
+ available[metadata.original_id].add(version);
+
+ if (!(version in versionFiles)) {
+ versionFiles[version] = {};
+ }
+ versionFiles[version][metadata.original_id] = file;
+});
+
+// returns the version to use for a document based on its id and
+// what the requested version is
+function docVersion(id, req_version) {
+ // iterate through versions until a version less than or equal to the requested
+ // is found, then check if that verison has an available file to use
+ let requestedFound = false;
+ for (let i = 0; i < versions.length; i++) {
+ if (versions[i] === req_version) {
+ requestedFound = true;
+ }
+ if (!requestedFound) {
+ continue;
+ }
+ if (!available[id]) {
+ return null;
+ }
+ if (available[id].has(versions[i])) {
+ return versions[i];
+ }
+ }
+ return null;
+}
+
+// returns whether a given file has content that differ from the
+// document with the given id
+function diffLatestDoc(file, id) {
+ if (versions.length === 0) {
+ return true;
+ }
+
+ const latest = versions[0];
+
+ const version = docVersion(id, latest);
+ if (!version) {
+ return true;
+ }
+ const latestFile = versionFiles[version][id];
+
+ if (!latestFile || !fs.existsSync(latestFile)) {
+ return true;
+ }
+
+ const diffs = diff.diffChars(
+ extractMetadata(fs.readFileSync(latestFile, "utf8")).rawContent,
+ extractMetadata(fs.readFileSync(file, "utf8")).rawContent
+ );
+ diffs.forEach(part => {
+ if (part.added || part.removed) {
+ return true;
+ }
+ });
+ return false;
+}
+
+// return metadata for a versioned file given the file, its version (requested),
+// the version of the file to be used, and its language
+function processVersionMetadata(file, version, useVersion, language) {
+ const metadata = extractMetadata(fs.readFileSync(file, "utf8")).metadata;
+ metadata.source = "version-" + useVersion + "/" + path.basename(file);
+
+ const latestVersion = versions[0];
+
+ if (!ENABLE_TRANSLATION && !siteConfig.useEnglishUrl) {
+ metadata.permalink =
+ "docs/" +
+ (version !== latestVersion ? version + "/" : "") +
+ metadata.original_id +
+ ".html";
+ } else {
+ metadata.permalink =
+ "docs/" +
+ language +
+ "/" +
+ (version !== latestVersion ? version + "/" : "") +
+ metadata.original_id +
+ ".html";
+ }
+ metadata.id = metadata.id.replace(
+ "version-" + useVersion + "-",
+ "version-" + version + "-"
+ );
+ metadata.localized_id = metadata.id;
+ metadata.id = language + "-" + metadata.id;
+ metadata.language = language;
+ metadata.version = version;
+
+ return metadata;
+}
+
+// return all metadata of versioned documents
+function docData() {
+ allIds = new Set();
+ Object.keys(versionFiles).forEach(version => {
+ Object.keys(versionFiles[version]).forEach(id => {
+ allIds.add(id);
+ });
+ });
+
+ const metadatas = [];
+
+ languages.filter(language => language.enabled).forEach(language => {
+ versions.forEach(version => {
+ allIds.forEach(id => {
+ const useVersion = docVersion(id, version);
+ if (!useVersion) {
+ return;
+ }
+ const file = versionFiles[useVersion][id];
+
+ metadatas.push(
+ processVersionMetadata(file, version, useVersion, language.tag)
+ );
+ });
+ });
+ });
+
+ return metadatas;
+}
+
+// return the version of the sidebar to use given a requested version
+function sidebarVersion(req_version) {
+ // iterate through versions until a version less than or equal to the requested
+ // is found, then check if that verison has an available file to use
+ let requestedFound = false;
+ for (let i = 0; i < versions.length; i++) {
+ if (versions[i] === req_version) {
+ requestedFound = true;
+ }
+ if (!requestedFound) {
+ continue;
+ }
+ if (
+ fs.existsSync(
+ CWD + "/versioned_sidebars/version-" + versions[i] + "-sidebar.json"
+ )
+ ) {
+ return versions[i];
+ }
+ }
+ return null;
+}
+
+// return whether or not the current sidebar.json file differs from the
+// latest versioned one
+function diffLatestSidebar() {
+ if (versions.length === 0) {
+ return true;
+ }
+ const latest = versions[0];
+
+ const version = sidebarVersion(latest);
+ const latestSidebar =
+ CWD + "/versioned_sidebars/version-" + version + "-sidebar.json";
+ if (!fs.existsSync(latestSidebar)) {
+ return true;
+ }
+ const currentSidebar = CWD + "/sidebar.json";
+ if (!fs.existsSync(currentSidebar)) {
+ // TO DO: error message
+ }
+
+ // compare for equality between latest version sidebar with version prefixes
+ // stripped and current sidebar
+ return (
+ JSON.stringify(JSON.parse(fs.readFileSync(latestSidebar, "utf8"))).replace(
+ new RegExp("version-" + version + "-", "g"),
+ ""
+ ) !== JSON.stringify(JSON.parse(fs.readFileSync(currentSidebar, "utf8")))
+ );
+}
+
+// return all versioned sidebar data
+function sidebarData() {
+ const allSidebars = {};
+
+ for (let i = 0; i < versions.length; i++) {
+ const version = sidebarVersion(versions[i]);
+ const sidebar = JSON.parse(
+ fs
+ .readFileSync(
+ CWD + "/versioned_sidebars/version-" + version + "-sidebar.json",
+ "utf8"
+ )
+ .replace(
+ new RegExp("version-" + version + "-", "g"),
+ "version-" + versions[i] + "-"
+ )
+ );
+ Object.assign(allSidebars, sidebar);
+ }
+ return allSidebars;
+}
+
+module.exports = {
+ docVersion,
+ diffLatestDoc,
+ processVersionMetadata,
+ docData,
+ sidebarVersion,
+ diffLatestSidebar,
+ sidebarData
+};
diff --git a/lib/static/css/main.css b/lib/static/css/main.css
index 06484bf7d2..2ad07d6d2a 100644
--- a/lib/static/css/main.css
+++ b/lib/static/css/main.css
@@ -512,6 +512,13 @@ header h2 {
position: relative;
z-index: 9999;
}
+.fixedHeaderContainer header h3 {
+ text-decoration: underline;
+ font-family: "Helvetica Neue", Arial, sans-serif;
+ color: white;
+ margin-left: 10px;
+ font-size: 16px;
+}
.promoSection {
display: flex;
diff --git a/lib/version.js b/lib/version.js
new file mode 100644
index 0000000000..dba16341fb
--- /dev/null
+++ b/lib/version.js
@@ -0,0 +1,122 @@
+#!/usr/bin/env node
+
+/**
+ * Copyright (c) 2017-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+const glob = require("glob");
+const fs = require("fs-extra");
+const path = require("path");
+const mkdirp = require("mkdirp");
+const readMetadata = require("./server/readMetadata.js");
+const versionFallback = require("./server/versionFallback.js");
+
+const CWD = process.cwd();
+let versions;
+if (fs.existsSync(CWD + "/versions.json")) {
+ versions = require(CWD + "/versions.json");
+} else {
+ versions = [];
+}
+
+let version;
+
+const program = require("commander");
+program
+ .arguments("
")
+ .action(ver => {
+ version = ver;
+ })
+ .parse(process.argv);
+
+if (typeof version === "undefined") {
+ console.error(
+ "No version number specified!\nPass the version you wish to create as an argument.\nEx: 1.0.0"
+ );
+ process.exit(1);
+}
+
+if (versions.includes(version)) {
+ console.error(
+ "This verison already exists!\nSpecify a new version to create that does not already exist."
+ );
+ process.exit(1);
+}
+
+function makeHeader(metadata) {
+ let header = "---\n";
+ Object.keys(metadata).forEach(key => {
+ header += key + ": " + metadata[key] + "\n";
+ });
+ header += "---\n";
+ return header;
+}
+
+const versionFolder = CWD + "/versioned_docs/version-" + version;
+
+mkdirp.sync(versionFolder);
+
+// copy necessary files to new version, changing some of its metadata to reflect the versioning
+let files = glob.sync(CWD + "/../docs/*");
+files.forEach(file => {
+ const ext = path.extname(file);
+ if (ext !== ".md" && ext !== ".markdown") {
+ return;
+ }
+
+ const res = readMetadata.extractMetadata(fs.readFileSync(file, "utf8"));
+ let metadata = res.metadata;
+ let rawContent = res.rawContent;
+ if (!metadata.id) {
+ return;
+ }
+
+ if (!versionFallback.diffLatestDoc(file, metadata.id)) {
+ return;
+ }
+
+ metadata.original_id = metadata.id;
+ metadata.id = "version-" + version + "-" + metadata.id;
+
+ const targetFile =
+ CWD + "/versioned_docs/version-" + version + "/" + path.basename(file);
+
+ fs.writeFileSync(targetFile, makeHeader(metadata) + rawContent, "utf8");
+});
+
+// copy sidebar if necessary
+if (versionFallback.diffLatestSidebar()) {
+ mkdirp(CWD + "/versioned_sidebars");
+ const sidebar = JSON.parse(fs.readFileSync(CWD + "/sidebar.json", "utf8"));
+ const versioned = {};
+
+ Object.keys(sidebar).forEach(sb => {
+ const version_sb = "version-" + version + "-" + sb;
+ versioned[version_sb] = {};
+
+ const categories = sidebar[sb];
+ Object.keys(categories).forEach(category => {
+ versioned[version_sb][category] = [];
+
+ const ids = categories[category];
+ ids.forEach((id, index) => {
+ versioned[version_sb][category].push("version-" + version + "-" + id);
+ });
+ });
+ });
+
+ fs.writeFileSync(
+ CWD + "/versioned_sidebars/version-" + version + "-sidebar.json",
+ JSON.stringify(versioned, null, 2),
+ "utf8"
+ );
+}
+
+// update versions.json file
+versions.unshift(version);
+fs.writeFileSync(CWD + "/versions.json", JSON.stringify(versions, null, 2));
diff --git a/lib/write-translations.js b/lib/write-translations.js
index d0e4533a48..b5ce9a35d8 100755
--- a/lib/write-translations.js
+++ b/lib/write-translations.js
@@ -74,6 +74,20 @@ function execute() {
});
});
+ files = glob.sync(CWD + "/versioned_sidebars/*");
+ files.forEach(file => {
+ if (!file.endsWith("-sidebar.json")) {
+ return;
+ }
+ sidebarContent = JSON.parse(fs.readFileSync(file, "utf8"));
+ Object.keys(sidebarContent).forEach(sb => {
+ const categories = sidebarContent[sb];
+ Object.keys(categories).forEach(category => {
+ translations["localized-strings"][category] = category;
+ });
+ });
+ });
+
/* go through pages to look for text inside translate tags */
files = glob.sync(CWD + "/pages/en/**");
files.forEach(file => {
diff --git a/package.json b/package.json
index b987296c3b..da5157ec44 100644
--- a/package.json
+++ b/package.json
@@ -14,6 +14,7 @@
"babylon": "^6.17.4",
"classnames": "^2.2.5",
"commander": "^2.11.0",
+ "diff": "^3.3.0",
"express": "^4.15.3",
"fs-extra": "^3.0.1",
"glob": "^7.1.2",
@@ -30,6 +31,7 @@
"docusaurus-build": "./lib/build-files.js",
"docusaurus-publish": "./lib/publish-gh-pages.js",
"docusaurus-examples": "./lib/copy-examples.js",
- "docusaurus-write-translations": "./lib/write-translations.js"
+ "docusaurus-write-translations": "./lib/write-translations.js",
+ "docusaurus-version": "./lib/version.js"
}
}