From e260ae0e28f5c2b41412a3a5687cefcfbcb8c5fe Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?netop=3A//=E3=82=A6=E3=82=A8=E3=83=8F?= <paul@webb.page>
Date: Wed, 10 Jul 2019 12:34:19 -0500
Subject: [PATCH 1/5] In progress

---
 app/data/redirects.json      |   6 +-
 app/dist/scripts/api.js      |   4 ++
 app/modules/relative-date.js |   5 +-
 app/sass/pages/_api.scss     |  19 ++++--
 app/views/api.js             | 121 ++++++++++++++++++++++++++++-------
 app/views/redirect.js        |   2 +-
 6 files changed, 122 insertions(+), 35 deletions(-)

diff --git a/app/data/redirects.json b/app/data/redirects.json
index 49d9145..e180629 100644
--- a/app/data/redirects.json
+++ b/app/data/redirects.json
@@ -9,11 +9,11 @@
   "/resources/schema": "/spec#metadata",
   "/resources/signing-claim": "/resources/claim-signing",
   "/resources/uri": "/spec#urls",
-  "/resources/video-lbrysdk": "https://spee.ch/967f99344308f1e90f0620d91b6c93e4dfb240e0/lbrynet-dev-setup",
+  "/resources/video-lbryandroid": "https://spee.ch/e781060bc708247f07afebc02d5f75cfba8e2c4b/video-2018-10-15053403",
   "/resources/video-lbrycrd": "https://spee.ch/5803b66dca7707584b36fe6b644f278fc39d1adf/intro-to-LBRYcrd",
   "/resources/video-lbrydesktop": "https://spee.ch/7da73fc508ffc4ff8b2711e3c3950110430b0c5f/LBRYAppDesign",
-  "/resources/video-lbryandroid": "https://spee.ch/e781060bc708247f07afebc02d5f75cfba8e2c4b/video-2018-10-15053403",
-  "/tour": "/playground",
+  "/resources/video-lbrysdk": "https://spee.ch/967f99344308f1e90f0620d91b6c93e4dfb240e0/lbrynet-dev-setup",
   "/specification": "/spec",
+  "/tour": "/playground",
   "/whitepaper": "/spec"
 }
diff --git a/app/dist/scripts/api.js b/app/dist/scripts/api.js
index bd279d0..d0a6feb 100644
--- a/app/dist/scripts/api.js
+++ b/app/dist/scripts/api.js
@@ -60,6 +60,10 @@ handleApiLanguageToggles("python");
 
 //  H E L P E R S
 
+function changeDocumentationVersion(value) {
+  console.log(value);
+}
+
 function handleApiLanguageToggles(language) {
   if (!document.getElementById(`toggle-${language}`))
     return;
diff --git a/app/modules/relative-date.js b/app/modules/relative-date.js
index 2d38cf9..e37647c 100644
--- a/app/modules/relative-date.js
+++ b/app/modules/relative-date.js
@@ -4,7 +4,7 @@
 
 //  P R O G R A M
 
-const relativeDate = (undefined => {
+const relativeDate = (() => {
   const SECOND = 1000;
   const MINUTE = 60 * SECOND;
   const HOUR = 60 * MINUTE;
@@ -53,6 +53,5 @@ const relativeDate = (undefined => {
 
 //  E X P O R T
 
-if (typeof module !== "undefined" && module.exports) {
+if (typeof module !== "undefined" && module.exports)
   module.exports = exports = relativeDate;
-}
diff --git a/app/sass/pages/_api.scss b/app/sass/pages/_api.scss
index c131e6f..afc5ae7 100644
--- a/app/sass/pages/_api.scss
+++ b/app/sass/pages/_api.scss
@@ -55,18 +55,27 @@
   }
 }
 
-.api-toc__search {
-  position: relative;
-}
-
+.api-toc__select,
 .api-toc__search-field {
   width: 100%;
-  padding: 0.25rem calc(2rem + 4px) 0.25rem 0.75rem;
 
   border-top: none;
   border-right: none;
   border-bottom: 1px solid $lbry-gray-1;
   border-left: none;
+}
+
+.api-toc__select {
+  background-position-x: 95%;
+}
+
+.api-toc__search {
+  position: relative;
+}
+
+.api-toc__search-field {
+  padding: 0.25rem calc(2rem + 4px) 0.25rem 0.75rem;
+
   border-radius: 0; // TODO: Put this rule in components on inputs
   font-size: 0.8rem;
   line-height: 2rem;
diff --git a/app/views/api.js b/app/views/api.js
index 934f84e..796ec42 100644
--- a/app/views/api.js
+++ b/app/views/api.js
@@ -7,6 +7,7 @@
 import asyncHtml from "choo-async/html";
 import dedent from "dedent";
 import got from "got";
+import Octokit from "@octokit/rest";
 
 //  U T I L S
 
@@ -14,31 +15,45 @@ import headerBlockchain from "~component/api/header-blockchain";
 import headerSdk from "~component/api/header-sdk";
 import redirects from "~data/redirects.json";
 
-const blockchainApi = "https://raw.githubusercontent.com/lbryio/lbrycrd/master/contrib/devtools/generated/api_v1.json";
 const cache = new Map();
-const sdkApi = "https://raw.githubusercontent.com/lbryio/lbry-sdk/master/lbry/docs/api.json";
+const filePathBlockchain = "/contrib/devtools/generated/api_v1.json";
+const filePathSdk = "/lbry/docs/api.json";
+const rawGitHubBase = "https://raw.githubusercontent.com/lbryio/";
+
+if (!process.env.GITHUB_OAUTH_TOKEN) // No point in rendering this page
+  throw new Error("Missing GitHub token");
+
+const octokit = new Octokit({
+  auth: `token ${process.env.GITHUB_OAUTH_TOKEN}`
+});
 
 
 
 //  E X P O R T
 
 export default async(state) => {
-  // below is evil, I just inherited it -- Jeremy
-  const apilabel = state.params.wildcard === "sdk" ?
-    "SDK" :
-    state.params.wildcard.charAt(0).toLocaleUpperCase() + state.params.wildcard.substring(1);
-
   state.lbry = {
-    title: apilabel + " API Documentation",
-    description: "See API documentation, signatures, and sample calls for the LBRY " + apilabel + " APIs."
+    title: "API Documentation",
+    description: "See API documentation, signatures, and sample calls for the LBRY APIs."
   };
 
+  const { wildcard } = state.params;
+
+  const repository = wildcard === "sdk" ?
+    "lbry-sdk" :
+    "lbrycrd";
+
+  const tags = await getTags(repository);
+
   try {
-    const apiResponse = await parseApiFile(state.params.wildcard);
+    const apiResponse = await parseApiFile({ repo: repository, tag: tags[0] });
 
     return asyncHtml`
       <div class="__slate">
         <aside class="api-toc">
+          <select class="api-toc__select" onchange="changeDocumentationVersion(value);">
+            ${renderVersionSelector(wildcard, tags)}
+          </select>
           <div class="api-toc__search">
             <input class="api-toc__search-field" id="input-search" placeholder="Search" type="search"/>
             <div class="api-toc__search-clear" id="clear-search" title="Clear search query">&times;</div>
@@ -46,18 +61,18 @@ export default async(state) => {
           </div>
 
           <ul class="api-toc__commands" id="toc" role="navigation">
-            ${apilabel === "SDK" ? createSdkSidebar(apiResponse) : createApiSidebar(apiResponse)}
+            ${wildcard === "sdk" ? createSdkSidebar(apiResponse) : createApiSidebar(apiResponse)}
           </ul>
         </aside>
         <section class="api-content">
           <div class="api-documentation" id="toc-content">
             <div></div>
             <nav class="api-content__items">
-              ${renderToggles(apilabel === "SDK")}
+              ${renderCodeLanguageToggles(wildcard)}
             </nav>
 
-            ${createApiHeader(state.params.wildcard)}
-            ${apilabel === "SDK" ? createSdkContent(apiResponse) : createApiContent(apiResponse)}
+            ${createApiHeader(wildcard)}
+            ${wildcard === "sdk" ? createSdkContent(apiResponse) : createApiContent(apiResponse)}
           </div>
         </section>
       </div>
@@ -220,24 +235,70 @@ function createSdkSidebar(apiDetails) {
   return apiSidebar;
 }
 
-async function parseApiFile(urlSlug) {
-  let apiFileLink;
+async function getTags(repositoryName) {
+  const { data } = await octokit.repos.listTags({
+    owner: "lbryio",
+    repo: repositoryName
+  });
+
+  const tags = [];
+
+  // NOTE:
+  // The versioning in our repos do not make sense so extra
+  // exclusion code is needed to make this work.
+  //
+  // Documentation is only available after specific versions.
 
   switch(true) {
-    case (urlSlug === "blockchain"):
-      apiFileLink = blockchainApi;
+    case repositoryName === "lbry-sdk":
+      data.forEach(tag => {
+        if (
+          tag.name >= "v0.38.0" &&
+          tag.name !== "v0.38.0rc7" &&
+          tag.name !== "v0.38.0rc6" &&
+          tag.name !== "v0.38.0rc5" &&
+          tag.name !== "v0.38.0rc4" &&
+          tag.name !== "v0.38.0rc3" &&
+          tag.name !== "v0.38.0rc2" &&
+          tag.name !== "v0.38.0rc1"
+        ) tags.push(tag.name);
+      });
       break;
 
-    case (urlSlug === "sdk"):
-      apiFileLink = sdkApi;
+    case repositoryName === "lbrycrd":
+      data.forEach(tag => {
+        if (
+          tag.name >= "v0.17.1.0" &&
+          tag.name !== "v0.3.16" &&
+          tag.name !== "v0.3.15" &&
+          tag.name !== "v0.3-osx" &&
+          tag.name !== "v0.2-alpha"
+        ) tags.push(tag.name);
+      });
       break;
 
     default:
       break;
   }
 
-  if (!apiFileLink)
-    return Promise.reject(new Error("Failed to fetch API docs"));
+  return tags;
+}
+
+async function parseApiFile({ repo, tag }) {
+  let apiFileLink = `${rawGitHubBase}${repo}/${tag}`;
+
+  switch(true) {
+    case (repo === "lbrycrd"):
+      apiFileLink = `${apiFileLink}${filePathBlockchain}`;
+      break;
+
+    case (repo === "lbry-sdk"):
+      apiFileLink = `${apiFileLink}${filePathSdk}`;
+      break;
+
+    default:
+      return Promise.reject(new Error("Failed to fetch API docs"));
+  }
 
   const response = await got(apiFileLink, { cache, json: true });
 
@@ -307,7 +368,21 @@ function renderReturns(args) {
   return returnContent;
 }
 
-function renderToggles(onSdkPage) {
+function renderVersionSelector(pageSlug, versions) {
+  const options = [
+    "<option disabled>Select a version</option>"
+  ];
+
+  versions.forEach(version => {
+    options.push(`<option value="${pageSlug}-${version}">${version}</option>`);
+  });
+
+  return options;
+}
+
+function renderCodeLanguageToggles(pageSlug) {
+  const onSdkPage = pageSlug === "sdk";
+
   return [
     "<button class='api-content__item menu' id='toggle-menu'>menu</button>",
     !onSdkPage ? "<button class='api-content__item' id='toggle-cli' type='button'>cli</button>" : "",
diff --git a/app/views/redirect.js b/app/views/redirect.js
index 43b520e..a5e8053 100644
--- a/app/views/redirect.js
+++ b/app/views/redirect.js
@@ -37,7 +37,7 @@ export default (state, emit) => { // eslint-disable-line
     const customMetadata = {};
 
     for (const key in markdownFileDetails.attributes.meta) {
-      if (markdownFileDetails.attributes.meta.hasOwnProperty(key)) {
+      if (Object.prototype.hasOwnProperty.call(markdownFileDetails.attributes.meta, key)) {
         customMetadata[Object.keys(markdownFileDetails.attributes.meta[key])[0]] =
           markdownFileDetails.attributes.meta[key][Object.keys(markdownFileDetails.attributes.meta[key])[0]];
       }

From 4b14cc1a734f92a17893a95d321086bbe825fec5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?netop=3A//=E3=82=A6=E3=82=A8=E3=83=8F?= <paul@webb.page>
Date: Wed, 10 Jul 2019 16:48:45 -0500
Subject: [PATCH 2/5] Almost done

---
 app/components/api/header-blockchain.js |   4 +-
 app/components/api/header-sdk.js        |   4 +-
 app/dist/scripts/api.js                 | 112 +++++++++++++-----------
 app/dist/scripts/app.js                 |  47 ++++++----
 app/dist/scripts/sockets.js             |  22 ++++-
 app/index.js                            |  15 +++-
 app/sockets.js                          |  20 ++++-
 app/views/api.js                        |  73 ++++++++++-----
 8 files changed, 202 insertions(+), 95 deletions(-)

diff --git a/app/components/api/header-blockchain.js b/app/components/api/header-blockchain.js
index a27c5ef..e7b1f27 100644
--- a/app/components/api/header-blockchain.js
+++ b/app/components/api/header-blockchain.js
@@ -10,9 +10,9 @@ import html from "choo/html";
 
 //  E X P O R T
 
-export default () => html`
+export default version => html`
   <div class="api-content__body">
-    <h2>lbrycrd APIs</h2>
+    <h2>lbrycrd ${version}</h2>
     <p>Methods and signatures provided by the <a href="/glossary#lbrycrd">lbrycrd</a> blockchain daemon are documented below. To build, download, or run lbrycrd, see the project <a href="https://github.com/lbryio/lbrycrd/blob/master/README.md">README</a>.</p>
   </div>
 
diff --git a/app/components/api/header-sdk.js b/app/components/api/header-sdk.js
index bdf28e1..c4008ed 100644
--- a/app/components/api/header-sdk.js
+++ b/app/components/api/header-sdk.js
@@ -10,9 +10,9 @@ import html from "choo/html";
 
 //  E X P O R T
 
-export default () => html`
+export default version => html`
   <div class="api-content__body">
-    <h2>lbry-sdk APIs</h2>
+    <h2>lbry-sdk ${version}</h2>
     <p>Methods and signatures provided by the <a href="/glossary#lbry-sdk">lbry-sdk</a> daemon are documented below. To build, download, or run the daemon, see the project <a href="https://github.com/lbryio/lbry-sdk/blob/master/README.md">README</a>.</p>
   </div>
 
diff --git a/app/dist/scripts/api.js b/app/dist/scripts/api.js
index d0a6feb..fd805d7 100644
--- a/app/dist/scripts/api.js
+++ b/app/dist/scripts/api.js
@@ -1,67 +1,37 @@
-"use strict"; /* global document, Jets, window */
+"use strict"; /* global document, Jets, send, window */
 
 
 
+// NOTE:
+// We declare `contentTag` and `jets` with `var` here so
+// when a visitor toggles the API page version, they are
+// not incorrectly declared multiple times via `let || const`
+
 // Initiate search functionality
-const contentTag = window.location.pathname.split("/").pop() === "sdk" ?
+if (!contentTag)
+  var contentTag;
+
+contentTag = window.location.pathname.split("/").pop() === "sdk" ?
   ".api-toc__section" :
   "#toc";
 
-let jets = new Jets({
+if (!jets)
+  var jets;
+
+jets = new Jets({
   contentTag,
   searchTag: "#input-search"
 });
 
-// Reset search on page load
-document.getElementById("input-search").value = "";
-
-// Activate search
-document.getElementById("input-search").addEventListener("keyup", () => {
-  if (document.getElementById("input-search").value)
-    document.querySelector(".api-toc__search-clear").classList.add("active");
-  else
-    document.querySelector(".api-toc__search-clear").classList.remove("active");
-});
-
-// Cancel search
-document.querySelector(".api-toc__search-clear").addEventListener("click", () => {
-  document.getElementById("input-search").value = "";
-  document.querySelector(".api-toc__search-clear").classList.remove("active");
-
-  jets.destroy();
-  reinitJets();
-});
-
-// Handle menu toggle for mobile
-if (document.getElementById("toggle-menu")) {
-  document.getElementById("toggle-menu").addEventListener("click", () => {
-    document.querySelector("body").classList.toggle("disable-scrolling");
-    document.querySelector(".api-toc").classList.toggle("active");
-  });
-
-  // Handle menu toggle when clicking on commands
-  document.querySelectorAll(".api-toc__command a").forEach(command => {
-    command.addEventListener("click", () => {
-      document.querySelector("body").classList.remove("disable-scrolling");
-      document.querySelector(".api-toc").classList.remove("active");
-    });
-  });
-}
-
-
-
-// Code toggles
-handleApiLanguageToggles("cli");
-handleApiLanguageToggles("curl");
-handleApiLanguageToggles("lbrynet");
-handleApiLanguageToggles("python");
-
 
 
 //  H E L P E R S
 
-function changeDocumentationVersion(value) {
-  console.log(value);
+function changeDocumentationVersion(desiredVersion) { // eslint-disable-line no-unused-vars
+  send({
+    message: "view different documentation version",
+    version: desiredVersion
+  });
 }
 
 function handleApiLanguageToggles(language) {
@@ -81,6 +51,50 @@ function handleApiLanguageToggles(language) {
   });
 }
 
+function initializeApiFunctionality() { // eslint-disable-line no-unused-vars
+  // Reset search on page load
+  document.getElementById("input-search").value = "";
+
+  // Activate search
+  document.getElementById("input-search").addEventListener("keyup", () => {
+    if (document.getElementById("input-search").value)
+      document.querySelector(".api-toc__search-clear").classList.add("active");
+    else
+      document.querySelector(".api-toc__search-clear").classList.remove("active");
+  });
+
+  // Cancel search
+  document.querySelector(".api-toc__search-clear").addEventListener("click", () => {
+    document.getElementById("input-search").value = "";
+    document.querySelector(".api-toc__search-clear").classList.remove("active");
+
+    jets.destroy();
+    reinitJets();
+  });
+
+  // Handle menu toggle for mobile
+  if (document.getElementById("toggle-menu")) {
+    document.getElementById("toggle-menu").addEventListener("click", () => {
+      document.querySelector("body").classList.toggle("disable-scrolling");
+      document.querySelector(".api-toc").classList.toggle("active");
+    });
+
+    // Handle menu toggle when clicking on commands
+    document.querySelectorAll(".api-toc__command a").forEach(command => {
+      command.addEventListener("click", () => {
+        document.querySelector("body").classList.remove("disable-scrolling");
+        document.querySelector(".api-toc").classList.remove("active");
+      });
+    });
+  }
+
+  // Code toggles
+  handleApiLanguageToggles("cli");
+  handleApiLanguageToggles("curl");
+  handleApiLanguageToggles("lbrynet");
+  handleApiLanguageToggles("python");
+}
+
 function reinitJets() {
   jets = new Jets({
     contentTag,
diff --git a/app/dist/scripts/app.js b/app/dist/scripts/app.js
index 6768844..7f89af4 100755
--- a/app/dist/scripts/app.js
+++ b/app/dist/scripts/app.js
@@ -4,6 +4,7 @@
 
 document.addEventListener("DOMContentLoaded", () => {
   scrollToElementOnLoad();
+  initializeSmoothScroll();
 
   // Automatically open external links in new tabs
   document.querySelectorAll("a[href^=http]").forEach(anchor => {
@@ -39,25 +40,27 @@ if (
 
 
 
-// Smooth scroll
-document.querySelectorAll("a[href^='#']").forEach(anchor => {
-  if (anchor.classList.contains("no-smooth")) // Ignore smooth scroll functionality
-    return;
+function initializeSmoothScroll() {
+  // Smooth scroll
+  document.querySelectorAll("a[href^='#']").forEach(anchor => {
+    if (anchor.classList.contains("no-smooth")) // Ignore smooth scroll functionality
+      return;
 
-  anchor.addEventListener("click", event => {
-    event.preventDefault();
+    anchor.addEventListener("click", event => {
+      event.preventDefault();
 
-    const element = event.target.href.split("#").pop()
-      .toLowerCase();
-    let elementOffset;
+      const element = event.target.href.split("#").pop()
+        .toLowerCase();
+      let elementOffset;
 
-    if (document.getElementById(element)) {
-      elementOffset = document.getElementById(element).offsetTop - 150;
-      window.scroll({ top: elementOffset, behavior: "smooth" });
-      history.pushState({}, "", `#${element}`); // Add hash to URL bar
-    }
+      if (document.getElementById(element)) {
+        elementOffset = document.getElementById(element).offsetTop - 150;
+        window.scroll({ top: elementOffset, behavior: "smooth" });
+        history.pushState({}, "", `#${element}`); // Add hash to URL bar
+      }
+    });
   });
-});
+}
 
 // Newsletter
 document.getElementById("emailAddress").addEventListener("keyup", event => {
@@ -88,6 +91,20 @@ document.querySelector("[data-action='subscribe to newsletter']").onclick = () =
 
 //  H E L P E R S
 
+function runScriptsInDynamicallyInsertedHTML(element, elementHTML) { // eslint-disable-line no-unused-vars
+  element.innerHTML = elementHTML;
+
+  Array.from(element.querySelectorAll("script")).forEach(oldScript => {
+    const newScript = document.createElement("script");
+
+    Array.from(oldScript.attributes)
+      .forEach(attr => newScript.setAttribute(attr.name, attr.value));
+
+    newScript.appendChild(document.createTextNode(oldScript.innerHTML));
+    oldScript.parentNode.replaceChild(newScript, oldScript);
+  });
+}
+
 function scrollToElementOnLoad() {
   if (window.location.href.includes("#")) {
     setTimeout(() => { // give page time to breathe
diff --git a/app/dist/scripts/sockets.js b/app/dist/scripts/sockets.js
index 6d6d6cf..34f315a 100644
--- a/app/dist/scripts/sockets.js
+++ b/app/dist/scripts/sockets.js
@@ -1,4 +1,4 @@
-"use strict"; /* global document, location, WebSocket, window */
+"use strict"; /* global document, initializeSmoothScroll, location, runScriptsInDynamicallyInsertedHTML, WebSocket, window */
 
 
 
@@ -44,6 +44,26 @@ function initializeWebSocketConnection() {
         window.location.href = data.url;
         break;
 
+      case data.message === "replace html":
+        // create placeholder
+        var placeholder = document.createElement("div");
+        placeholder.setAttribute("id", "__placeholder"); // eslint-disable-line padding-line-between-statements
+        document.querySelector(data.selector).insertAdjacentElement("afterend", placeholder);
+
+        // remove original element
+        document.querySelector(data.selector).remove();
+
+        // add new element and remove placeholder
+        document.getElementById("__placeholder").insertAdjacentHTML("afterend", data.html);
+        document.getElementById("__placeholder").remove();
+
+        // make our scripts work
+        runScriptsInDynamicallyInsertedHTML(document.querySelector(data.selector), document.querySelector(data.selector).innerHTML);
+
+        // make smooth scroll work on our new content
+        initializeSmoothScroll();
+        break;
+
       case data.message === "show result":
         if (!data.example)
           return;
diff --git a/app/index.js b/app/index.js
index 123a776..9ac81d5 100755
--- a/app/index.js
+++ b/app/index.js
@@ -22,7 +22,20 @@ import redirects from "~data/redirects.json";
 const server = fastify({
   logger: {
     level: "warn",
-    prettyPrint: process.env.NODE_ENV === "development" ? true : false
+    prettyPrint: process.env.NODE_ENV === "development" ? true : false,
+    redact: ["req.headers.authorization"],
+    serializers: {
+      req(req) {
+        return {
+          headers: req.headers,
+          hostname: req.hostname,
+          method: req.method,
+          remoteAddress: req.ip,
+          remotePort: req.connection.remotePort,
+          url: req.url
+        };
+      }
+    }
   }
 });
 
diff --git a/app/sockets.js b/app/sockets.js
index f5cc29f..be2d2da 100644
--- a/app/sockets.js
+++ b/app/sockets.js
@@ -9,6 +9,7 @@ import html from "choo/html";
 
 //  U T I L S
 
+import apiPage from "~view/api";
 import fetchMetadata from "~helper/fetch-metadata";
 import { generateGitHubFeed } from "~helper/github";
 import messageSlack from "~helper/slack";
@@ -23,7 +24,7 @@ const githubAppSecret = process.env.GITHUB_APP_SECRET;
 
 //  P R O G R A M
 
-export default (socket, action) => {
+export default async(socket, action) => {
   if (typeof socket !== "object" && typeof action !== "object")
     return;
 
@@ -88,6 +89,21 @@ export default (socket, action) => {
       newsletterSubscribe(action, socket);
       break;
 
+    case action.message === "view different documentation version":
+      send(socket, {
+        element: "div",
+        html: await apiPage({
+          params: {
+            wildcard: action.version.split("-")[0]
+          },
+          tag: action.version.split("-")[1]
+        }),
+        message: "replace html",
+        parentElement: "main",
+        selector: ".__slate"
+      });
+      break;
+
     default:
       break;
   }
@@ -434,7 +450,7 @@ async function verifyGitHubToken(data, socket) {
   const code = data.code;
 
   try {
-    let result = await got.post(`https://github.com/login/oauth/access_token?client_id=${githubAppId}&client_secret=${githubAppSecret}&code=${code}`, { json: true });
+    const result = await got.post(`https://github.com/login/oauth/access_token?client_id=${githubAppId}&client_secret=${githubAppSecret}&code=${code}`, { json: true });
 
     const response = {
       address: data.address,
diff --git a/app/views/api.js b/app/views/api.js
index 796ec42..5521d6b 100644
--- a/app/views/api.js
+++ b/app/views/api.js
@@ -32,28 +32,30 @@ const octokit = new Octokit({
 //  E X P O R T
 
 export default async(state) => {
-  state.lbry = {
-    title: "API Documentation",
-    description: "See API documentation, signatures, and sample calls for the LBRY APIs."
-  };
-
+  const { tag } = state;
   const { wildcard } = state.params;
 
   const repository = wildcard === "sdk" ?
     "lbry-sdk" :
     "lbrycrd";
 
+  state.lbry = {
+    title: tag ? tag + " API Documentation" : "API Documentation",
+    description: "See API documentation, signatures, and sample calls for the LBRY APIs."
+  };
+
   const tags = await getTags(repository);
 
   try {
-    const apiResponse = await parseApiFile({ repo: repository, tag: tags[0] });
+    const apiResponse = await parseApiFile({ repo: repository, tag: tag ? tag : tags[0] });
 
     return asyncHtml`
       <div class="__slate">
         <aside class="api-toc">
           <select class="api-toc__select" onchange="changeDocumentationVersion(value);">
-            ${renderVersionSelector(wildcard, tags)}
+            ${renderVersionSelector(wildcard, tags, tag)}
           </select>
+
           <div class="api-toc__search">
             <input class="api-toc__search-field" id="input-search" placeholder="Search" type="search"/>
             <div class="api-toc__search-clear" id="clear-search" title="Clear search query">&times;</div>
@@ -64,28 +66,32 @@ export default async(state) => {
             ${wildcard === "sdk" ? createSdkSidebar(apiResponse) : createApiSidebar(apiResponse)}
           </ul>
         </aside>
+
         <section class="api-content">
           <div class="api-documentation" id="toc-content">
             <div></div>
+
             <nav class="api-content__items">
               ${renderCodeLanguageToggles(wildcard)}
             </nav>
 
-            ${createApiHeader(wildcard)}
+            ${createApiHeader(wildcard, tag ? tag : tags[0])}
             ${wildcard === "sdk" ? createSdkContent(apiResponse) : createApiContent(apiResponse)}
           </div>
         </section>
+
+        <script src="/assets/scripts/plugins/jets.js"></script>
+        <script src="/assets/scripts/api.js"></script>
+
+        <script>
+          initializeApiFunctionality();
+
+          if (window.location.pathname === "/api/blockchain")
+            document.getElementById("toggle-cli").click();
+          else
+            document.getElementById("toggle-curl").click();
+        </script>
       </div>
-
-      <script src="/assets/scripts/plugins/jets.js"></script>
-      <script src="/assets/scripts/api.js"></script>
-
-      <script>
-        if (window.location.pathname === "/api/blockchain")
-          document.getElementById("toggle-cli").click();
-        else
-          document.getElementById("toggle-curl").click();
-      </script>
     `;
   }
 
@@ -149,13 +155,13 @@ function createApiContent(apiDetails) {
   return apiContent;
 }
 
-function createApiHeader(slug) {
+function createApiHeader(slug, apiVersion) {
   switch(slug) {
     case "blockchain":
-      return headerBlockchain();
+      return headerBlockchain(apiVersion);
 
     case "sdk":
-      return headerSdk();
+      return headerSdk(apiVersion);
 
     default:
       break;
@@ -368,15 +374,36 @@ function renderReturns(args) {
   return returnContent;
 }
 
-function renderVersionSelector(pageSlug, versions) {
+function renderVersionSelector(pageSlug, versions, desiredTag) {
   const options = [
     "<option disabled>Select a version</option>"
   ];
 
+  let optionIndex = 0;
+
+  // console.log("————————");
+  // console.log(desiredTag);
+  // console.log("————————");
+
   versions.forEach(version => {
-    options.push(`<option value="${pageSlug}-${version}">${version}</option>`);
+    optionIndex++;
+    let selectedOption = false;
+
+    if (desiredTag && desiredTag === version)
+      selectedOption = true;
+    else if (optionIndex === 1)
+      selectedOption = true;
+
+    // if (selectedOption === true)
+    //   console.log(pageSlug, version);
+
+    options.push(
+      `<option value="${pageSlug}-${version}"${selectedOption ? " selected" : ""}>${version}</option>`
+    );
   });
 
+  // console.log(options);
+
   return options;
 }
 

From a6b7340e9d4022ff99406f34b0e7b32b3af09602 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?netop=3A//=E3=82=A6=E3=82=A8=E3=83=8F?= <paul@webb.page>
Date: Thu, 11 Jul 2019 16:20:50 -0500
Subject: [PATCH 3/5] Done

---
 app/dist/scripts/api.js | 9 +++++++++
 app/index.js            | 5 +++++
 app/sockets.js          | 1 +
 app/views/api.js        | 9 ---------
 4 files changed, 15 insertions(+), 9 deletions(-)

diff --git a/app/dist/scripts/api.js b/app/dist/scripts/api.js
index fd805d7..461ea2b 100644
--- a/app/dist/scripts/api.js
+++ b/app/dist/scripts/api.js
@@ -93,6 +93,15 @@ function initializeApiFunctionality() { // eslint-disable-line no-unused-vars
   handleApiLanguageToggles("curl");
   handleApiLanguageToggles("lbrynet");
   handleApiLanguageToggles("python");
+
+  // Ensure version selector shows correct version, even on page reloads
+  const currentValue = document.querySelector(".api-content__body h2").textContent.split(/\s/g).pop();
+  const { children } = document.querySelector(".api-toc__select");
+
+  for (const child of children) {
+    if (currentValue === child.text)
+      document.querySelector(".api-toc__select").selectedIndex = child.index;
+  }
 }
 
 function reinitJets() {
diff --git a/app/index.js b/app/index.js
index 9ac81d5..de4b60d 100755
--- a/app/index.js
+++ b/app/index.js
@@ -26,6 +26,11 @@ const server = fastify({
     redact: ["req.headers.authorization"],
     serializers: {
       req(req) {
+        // TODO: Figure out how to trigger this to test
+        // It is annoying to see in development
+        if (req.err && req.err.message.includes("Premature close"))
+          return {};
+
         return {
           headers: req.headers,
           hostname: req.hostname,
diff --git a/app/sockets.js b/app/sockets.js
index be2d2da..94a7cb3 100644
--- a/app/sockets.js
+++ b/app/sockets.js
@@ -89,6 +89,7 @@ export default async(socket, action) => {
       newsletterSubscribe(action, socket);
       break;
 
+
     case action.message === "view different documentation version":
       send(socket, {
         element: "div",
diff --git a/app/views/api.js b/app/views/api.js
index 5521d6b..4ec9166 100644
--- a/app/views/api.js
+++ b/app/views/api.js
@@ -381,10 +381,6 @@ function renderVersionSelector(pageSlug, versions, desiredTag) {
 
   let optionIndex = 0;
 
-  // console.log("————————");
-  // console.log(desiredTag);
-  // console.log("————————");
-
   versions.forEach(version => {
     optionIndex++;
     let selectedOption = false;
@@ -394,16 +390,11 @@ function renderVersionSelector(pageSlug, versions, desiredTag) {
     else if (optionIndex === 1)
       selectedOption = true;
 
-    // if (selectedOption === true)
-    //   console.log(pageSlug, version);
-
     options.push(
       `<option value="${pageSlug}-${version}"${selectedOption ? " selected" : ""}>${version}</option>`
     );
   });
 
-  // console.log(options);
-
   return options;
 }
 

From 39bf3b56bcfcf1641db257de9ff810480c0b476b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?netop=3A//=E3=82=A6=E3=82=A8=E3=83=8F?= <paul@webb.page>
Date: Thu, 11 Jul 2019 17:09:36 -0500
Subject: [PATCH 4/5] Linting

---
 app/components/client/devprogram-scripts.js |  3 +-
 app/components/client/playground-scripts.js | 10 +++---
 app/components/head.js                      |  8 -----
 app/helpers/fetch-metadata.js               | 16 +++------
 app/helpers/github.js                       | 39 ++++++++++++++-------
 app/index.js                                |  2 +-
 app/modules/markdown-it-sup.js              | 24 +++++++------
 app/modules/relative-date.js                | 31 ++++++++--------
 app/sockets.js                              |  3 +-
 app/views/api.js                            | 11 +++---
 package.json                                | 14 ++++----
 11 files changed, 81 insertions(+), 80 deletions(-)

diff --git a/app/components/client/devprogram-scripts.js b/app/components/client/devprogram-scripts.js
index 3f5afc4..0dc1f05 100644
--- a/app/components/client/devprogram-scripts.js
+++ b/app/components/client/devprogram-scripts.js
@@ -56,9 +56,10 @@ function syncWithApi(data) { // eslint-disable-line no-unused-vars
   const address = data.address;
   const code = data.code;
 
-  if (code === null)
+  if (code === null) {
     document.querySelector("developer-program").innerHTML =
       "<p><strong>There was an issue with accessing GitHub's API. Please try again later.</strong></p>";
+  }
 
   fetch(`https://api.lbry.com/reward/new?github_token=${code}&reward_type=github_developer&wallet_address=${address}`)
     .then(response => response.json())
diff --git a/app/components/client/playground-scripts.js b/app/components/client/playground-scripts.js
index 0cab01f..3ff07e9 100644
--- a/app/components/client/playground-scripts.js
+++ b/app/components/client/playground-scripts.js
@@ -249,11 +249,11 @@ curl --header <span class="token string">"Content-Type: application/json"</span>
 }
 
 const handleExamples = debounce(event => {
-  let exampleNumber;
+  const exampleNumber = parseInt(document.querySelector(".playground-navigation__example.active").dataset.example || 0);
   const data = event.dataset;
 
-  if (!parseInt(document.querySelector(".playground-navigation__example.active").dataset.example)) return;
-  exampleNumber = parseInt(document.querySelector(".playground-navigation__example.active").dataset.example);
+  if (!exampleNumber || exampleNumber === 0)
+    return;
 
   switch(data.action) {
     case "choose claim":
@@ -398,9 +398,9 @@ function updateCanvas(imageSource) {
   if (imageSource) {
     ctx.drawImage(imageSource, 0, 0, canvasWidth, canvasHeight);
     img.src = imageSource.src;
-  } else {
+  } else
     ctx.drawImage(img, 0, 0, canvasWidth, canvasHeight);
-  }
+
 
   positionCanvasText(ctx, canvasHeight, canvasWidth);
 }
diff --git a/app/components/head.js b/app/components/head.js
index 463a025..773f2e4 100644
--- a/app/components/head.js
+++ b/app/components/head.js
@@ -68,11 +68,3 @@ export default (state, emit) => {
     <script src="/assets/scripts/sockets.js"></script>
   `;
 };
-
-
-
-//  H E L P E R
-
-String.prototype.capitalize = function() {
-  return this.charAt(0).toUpperCase() + this.slice(1);
-};
diff --git a/app/helpers/fetch-metadata.js b/app/helpers/fetch-metadata.js
index a6fcced..044403d 100644
--- a/app/helpers/fetch-metadata.js
+++ b/app/helpers/fetch-metadata.js
@@ -55,10 +55,10 @@ export default async(data, socket) => {
   let dataDetails = "";
   let explorerNotice = "";
 
-  if (data.example === 1 && !data.claim || !data.method) return;
+  if (data.example === 1 && (!data.claim || !data.method)) return;
   if (data.example === 2 && !data.data) return;
   if (data.example === 2) dataDetails = data.data; // file upload
-  if (data.example === 3 && !data.claim || !data.method) return;
+  if (data.example === 3 && (!data.claim || !data.method)) return;
 
   const claimAddress = data.claim;
   const resolveMethod = data.method;
@@ -155,9 +155,7 @@ export default async(data, socket) => {
             message: "show result",
             selector: `#example${data.example}-result`
           });
-        }
-
-        catch(memePublishError) {
+        } catch(memePublishError) {
           send(socket, {
             details: "Meme publish failed",
             message: "notification",
@@ -174,9 +172,7 @@ export default async(data, socket) => {
 
           return;
         }
-      }
-
-      catch(imageUploadError) {
+      } catch(imageUploadError) {
         send(socket, {
           details: "Image upload failed",
           message: "notification",
@@ -259,9 +255,7 @@ export default async(data, socket) => {
     }
 
     return response.body.result[Object.keys(response.body.result)[0]].claim;
-  }
-
-  catch(error) {
+  } catch(error) {
     messageSlack({
       message: "```" + error + "```",
       pretext: "_Someone is going through the Playground and the daemon is not running_",
diff --git a/app/helpers/github.js b/app/helpers/github.js
index 51e0be8..29d6155 100644
--- a/app/helpers/github.js
+++ b/app/helpers/github.js
@@ -16,15 +16,15 @@ import relativeDate from "~module/relative-date";
 
 let octokit;
 
-String.prototype.escape = function() {
-  const tagsToReplace = {
-    "&": "&amp;",
-    "<": "&lt;",
-    ">": "&gt;"
-  };
+// String.prototype.escape = function() {
+//   const tagsToReplace = {
+//     "&": "&amp;",
+//     "<": "&lt;",
+//     ">": "&gt;"
+//   };
 
-  return this.replace(/[&<>]/g, tag => tagsToReplace[tag] || tag);
-};
+//   return this.replace(/[&<>]/g, tag => tagsToReplace[tag] || tag);
+// };
 
 //  R E D I S
 
@@ -142,7 +142,7 @@ function generateEvent(event) {
             rel="noopener noreferrer"
             target="_blank"
             title="View this comment on GitHub"
-          >${event.payload.issue.title.escape()}</a></em> in
+          >${escapeSpecialCharacters(event.payload.issue.title)}</a></em> in
         `;
       } else {
         return `
@@ -153,7 +153,7 @@ function generateEvent(event) {
             rel="noopener noreferrer"
             target="_blank"
             title="View this comment on GitHub"
-          >${event.payload.issue.title.escape()}</a></em> in
+          >${escapeSpecialCharacters(event.payload.issue.title)}</a></em> in
         `;
       }
 
@@ -171,7 +171,7 @@ function generateEvent(event) {
           rel="noopener noreferrer"
           target="_blank"
           title="View this issue on GitHub"
-        >${event.payload.issue.title.escape()}</a></em> in
+        >${escapeSpecialCharacters(event.payload.issue.title)}</a></em> in
       `;
 
     case "PullRequestEvent":
@@ -188,7 +188,7 @@ function generateEvent(event) {
           rel="noopener noreferrer"
           target="_blank"
           title="View this pull request on GitHub"
-        >${event.payload.pull_request.title.escape()}</a></em> in
+        >${escapeSpecialCharacters(event.payload.pull_request.title)}</a></em> in
       `;
 
     case "PullRequestReviewCommentEvent":
@@ -205,7 +205,7 @@ function generateEvent(event) {
           rel="noopener noreferrer"
           target="_blank"
           title="View this comment on GitHub"
-        >${event.payload.pull_request.title.escape()}</a></em> in
+        >${escapeSpecialCharacters(event.payload.pull_request.title)}</a></em> in
       `;
 
     case "PushEvent":
@@ -346,6 +346,9 @@ function updateGithubFeed() {
       const eventString = JSON.stringify(item);
 
       client.zrank("events", eventString, (err, reply) => {
+        if (err)
+          return;
+
         if (reply === null)
           client.zadd("events", item.id, eventString, callback);
         else
@@ -366,6 +369,16 @@ function updateGithubFeed() {
 
 //  H E L P E R
 
+function escapeSpecialCharacters(contentToEscape) {
+  const tagsToReplace = {
+    "&": "&amp;",
+    "<": "&lt;",
+    ">": "&gt;"
+  };
+
+  return contentToEscape.replace(/[&<>]/g, tag => tagsToReplace[tag] || tag);
+}
+
 function refToBranch(ref) {
   if (ref)
     return ref.replace("refs/heads/", "");
diff --git a/app/index.js b/app/index.js
index de4b60d..a788ffc 100755
--- a/app/index.js
+++ b/app/index.js
@@ -22,7 +22,7 @@ import redirects from "~data/redirects.json";
 const server = fastify({
   logger: {
     level: "warn",
-    prettyPrint: process.env.NODE_ENV === "development" ? true : false,
+    prettyPrint: process.env.NODE_ENV === "development",
     redact: ["req.headers.authorization"],
     serializers: {
       req(req) {
diff --git a/app/modules/markdown-it-sup.js b/app/modules/markdown-it-sup.js
index 5835136..ac2c580 100644
--- a/app/modules/markdown-it-sup.js
+++ b/app/modules/markdown-it-sup.js
@@ -16,16 +16,20 @@ function superscript(state, silent) {
   const max = state.posMax;
   const start = state.pos;
   let found;
-  let content;
   let token;
 
-  if (state.src.charCodeAt(start) !== 0x5E/* ^ */) return false;
-  if (silent) return false; // do not run pairs in validation mode
-  if (start + 2 >= max) return false;
+  if (state.src.charCodeAt(start) !== 0x5E/* ^ */)
+    return false;
+
+  if (silent)
+    return false; // do not run pairs in validation mode
+
+  if (start + 2 >= max)
+    return false;
 
   state.pos = start + 1;
 
-  while (state.pos < max) {
+  while(state.pos < max) {
     if (state.src.charCodeAt(state.pos) === 0x5E/* ^ */) {
       found = true;
       break;
@@ -39,7 +43,7 @@ function superscript(state, silent) {
     return false;
   }
 
-  content = state.src.slice(start + 1, state.pos);
+  const content = state.src.slice(start + 1, state.pos);
 
   // do not allow unescaped spaces/newlines inside
   if (content.match(/(^|[^\\])(\\\\)*\s/)) {
@@ -59,16 +63,14 @@ function superscript(state, silent) {
 
   if (content.match(regexForIds)) {
     const theLink = supText.match(regexForIds)[0].replace("(#", "").replace(")", "");
-
-    token.attrPush([ "id", theLink ]);
+    token.attrPush(["id", theLink]); // eslint-disable-line padding-line-between-statements
   }
 
   token = state.push("text", "", 0);
 
   if (content.match(regexForIds)) {
     const theText = supText.match(regexForTextBeforeLink)[0];
-
-    token.content = theText;
+    token.content = theText; // eslint-disable-line padding-line-between-statements
   } else token.content = supText;
 
   token = state.push("sup_close", "sup", -1);
@@ -84,6 +86,6 @@ function superscript(state, silent) {
 
 //  E X P O R T
 
-module.exports = exports = function sup_plugin(md) {
+module.exports = exports = function sup_plugin(md) { // eslint-disable-line camelcase
   md.inline.ruler.after("emphasis", "sup", superscript);
 };
diff --git a/app/modules/relative-date.js b/app/modules/relative-date.js
index e37647c..7a981f7 100644
--- a/app/modules/relative-date.js
+++ b/app/modules/relative-date.js
@@ -14,23 +14,23 @@ const relativeDate = (() => {
   const MONTH = YEAR / 12;
 
   const formats = [
-    [ 0.7 * MINUTE, "just now" ],
-    [ 1.5 * MINUTE, "a minute ago" ],
-    [ 60 * MINUTE, "minutes ago", MINUTE ],
-    [ 1.5 * HOUR, "an hour ago" ],
-    [ DAY, "hours ago", HOUR ],
-    [ 2 * DAY, "yesterday" ],
-    [ 7 * DAY, "days ago", DAY ],
-    [ 1.5 * WEEK, "a week ago" ],
-    [ MONTH, "weeks ago", WEEK ],
-    [ 1.5 * MONTH, "a month ago" ],
-    [ YEAR, "months ago", MONTH ],
-    [ 1.5 * YEAR, "a year ago" ],
-    [ Number.MAX_VALUE, "years ago", YEAR ]
+    [0.7 * MINUTE, "just now"],
+    [1.5 * MINUTE, "a minute ago"],
+    [60 * MINUTE, "minutes ago", MINUTE],
+    [1.5 * HOUR, "an hour ago"],
+    [DAY, "hours ago", HOUR],
+    [2 * DAY, "yesterday"],
+    [7 * DAY, "days ago", DAY],
+    [1.5 * WEEK, "a week ago"],
+    [MONTH, "weeks ago", WEEK],
+    [1.5 * MONTH, "a month ago"],
+    [YEAR, "months ago", MONTH],
+    [1.5 * YEAR, "a year ago"],
+    [Number.MAX_VALUE, "years ago", YEAR]
   ];
 
   function relativeDate(input, reference) {
-    !reference && (reference = (new Date).getTime());
+    !reference && (reference = (new Date()).getTime());
     reference instanceof Date && (reference = reference.getTime());
     input instanceof Date && (input = input.getTime());
 
@@ -40,9 +40,8 @@ const relativeDate = (() => {
     for (let i = -1; ++i < len;) {
       const format = formats[i];
 
-      if (delta < format[0]) {
+      if (delta < format[0])
         return format[2] === undefined ? format[1] : Math.round(delta / format[2]) + " " + format[1];
-      }
     }
   }
 
diff --git a/app/sockets.js b/app/sockets.js
index 94a7cb3..52fb9fc 100644
--- a/app/sockets.js
+++ b/app/sockets.js
@@ -124,12 +124,13 @@ function generateContent(exampleNumber, displayTrendingContent) {
       const renderedContentCollection = [];
       const trendingContentData = response.data;
 
-      for (const data of trendingContentData)
+      for (const data of trendingContentData) {
         rawContentCollection.push(fetchMetadata({
           claim: data.url,
           example: exampleNumber,
           method: "resolve"
         }));
+      }
 
       Promise.all(rawContentCollection).then(collection => {
         for (const part of collection) {
diff --git a/app/views/api.js b/app/views/api.js
index 4ec9166..1f5de32 100644
--- a/app/views/api.js
+++ b/app/views/api.js
@@ -45,9 +45,10 @@ export default async(state) => {
   };
 
   const tags = await getTags(repository);
+  const currentTag = tag.length ? tag : tags[0];
 
   try {
-    const apiResponse = await parseApiFile({ repo: repository, tag: tag ? tag : tags[0] });
+    const apiResponse = await parseApiFile({ repo: repository, tag: currentTag });
 
     return asyncHtml`
       <div class="__slate">
@@ -75,7 +76,7 @@ export default async(state) => {
               ${renderCodeLanguageToggles(wildcard)}
             </nav>
 
-            ${createApiHeader(wildcard, tag ? tag : tags[0])}
+            ${createApiHeader(wildcard, currentTag)}
             ${wildcard === "sdk" ? createSdkContent(apiResponse) : createApiContent(apiResponse)}
           </div>
         </section>
@@ -93,9 +94,7 @@ export default async(state) => {
         </script>
       </div>
     `;
-  }
-
-  catch(error) {
+  } catch(error) {
     const redirectUrl = redirects[state.href];
 
     return asyncHtml`
@@ -326,7 +325,7 @@ function renderArguments(args) {
       <li class="api-content__body-argument">
         <div class="left">
           <strong>${arg.name}</strong><br/>
-          ${arg.is_required === true ? "" : "<span>optional</span>" }<span>${arg.type}</span>
+          ${arg.is_required === true ? "" : "<span>optional</span>"}<span>${arg.type}</span>
         </div>
 
         <div class="right">${typeof arg.description === "string" ? arg.description.replace(/</g, "&lt;").replace(/>/g, "&gt;") : ""}</div>
diff --git a/package.json b/package.json
index 249d171..00e6262 100755
--- a/package.json
+++ b/package.json
@@ -12,7 +12,7 @@
   "dependencies": {
     "@babel/polyfill": "^7.4.4",
     "@inc/fastify-ws": "^1.1.0",
-    "@octokit/rest": "^16.28.2",
+    "@octokit/rest": "^16.28.3",
     "@slack/client": "^5.0.1",
     "async": "^3.1.0",
     "async-es": "^3.1.0",
@@ -38,7 +38,7 @@
     "graceful-fs": "^4.2.0",
     "link-module-alias": "^1.2.0",
     "make-promises-safe": "^5.0.0",
-    "markdown-it": "^8.4.2",
+    "markdown-it": "^9.0.0",
     "markdown-it-anchor": "^5.2.4",
     "prismjs": "^1.15.0",
     "redis": "^2.8.0",
@@ -49,20 +49,20 @@
   "description": "Documentation for the LBRY protocol and associated projects",
   "devDependencies": {
     "@babel/cli": "^7.5.0",
-    "@babel/core": "^7.5.0",
+    "@babel/core": "^7.5.4",
     "@babel/plugin-external-helpers": "7.2.0",
     "@babel/plugin-proposal-class-properties": "7.5.0",
     "@babel/plugin-proposal-decorators": "7.4.4",
-    "@babel/plugin-proposal-export-namespace-from": "7.2.0",
+    "@babel/plugin-proposal-export-namespace-from": "7.5.2",
     "@babel/plugin-proposal-function-sent": "7.5.0",
     "@babel/plugin-proposal-json-strings": "7.2.0",
     "@babel/plugin-proposal-numeric-separator": "7.2.0",
     "@babel/plugin-proposal-throw-expressions": "7.2.0",
     "@babel/plugin-syntax-dynamic-import": "7.2.0",
     "@babel/plugin-syntax-import-meta": "7.2.0",
-    "@babel/preset-env": "^7.5.0",
+    "@babel/preset-env": "^7.5.4",
     "@babel/register": "^7.4.4",
-    "@inc/eslint-config": "^1.1.3",
+    "@inc/eslint-config": "^2019.7.11",
     "@inc/sasslint-config": "^2019.6.22",
     "@lbry/color": "^1.1.1",
     "@lbry/components": "^2.7.4",
@@ -74,7 +74,7 @@
     "sass": "^1.22.3",
     "sass-lint": "^1.13.1",
     "snazzy": "^8.0.0",
-    "standardx": "^3.0.1",
+    "standardx": "^4.0.0",
     "updates": "^8.2.1"
   },
   "engines": {

From 4045de7c2c3fa4a49eb864436c76d7e99662bc7e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?netop=3A//=E3=82=A6=E3=82=A8=E3=83=8F?= <paul@webb.page>
Date: Thu, 11 Jul 2019 17:25:55 -0500
Subject: [PATCH 5/5] Removed commented code

---
 app/helpers/github.js | 10 ----------
 app/index.js          |  5 -----
 2 files changed, 15 deletions(-)

diff --git a/app/helpers/github.js b/app/helpers/github.js
index 29d6155..a75efe8 100644
--- a/app/helpers/github.js
+++ b/app/helpers/github.js
@@ -16,16 +16,6 @@ import relativeDate from "~module/relative-date";
 
 let octokit;
 
-// String.prototype.escape = function() {
-//   const tagsToReplace = {
-//     "&": "&amp;",
-//     "<": "&lt;",
-//     ">": "&gt;"
-//   };
-
-//   return this.replace(/[&<>]/g, tag => tagsToReplace[tag] || tag);
-// };
-
 //  R E D I S
 
 let client;
diff --git a/app/index.js b/app/index.js
index a788ffc..ff68d0f 100755
--- a/app/index.js
+++ b/app/index.js
@@ -26,11 +26,6 @@ const server = fastify({
     redact: ["req.headers.authorization"],
     serializers: {
       req(req) {
-        // TODO: Figure out how to trigger this to test
-        // It is annoying to see in development
-        if (req.err && req.err.message.includes("Premature close"))
-          return {};
-
         return {
           headers: req.headers,
           hostname: req.hostname,