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`
-

lbrycrd APIs

+

lbrycrd ${version}

Methods and signatures provided by the lbrycrd blockchain daemon are documented below. To build, download, or run lbrycrd, see the project README.

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`
-

lbry-sdk APIs

+

lbry-sdk ${version}

Methods and signatures provided by the lbry-sdk daemon are documented below. To build, download, or run the daemon, see the project README.

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 = "

There was an issue with accessing GitHub's API. Please try again later.

"; + } 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 "Content-Type: application/json" } 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) => { `; }; - - - -// H E L P E R - -String.prototype.capitalize = function() { - return this.charAt(0).toUpperCase() + this.slice(1); -}; 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..461ea2b 100644 --- a/app/dist/scripts/api.js +++ b/app/dist/scripts/api.js @@ -1,65 +1,39 @@ -"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(desiredVersion) { // eslint-disable-line no-unused-vars + send({ + message: "view different documentation version", + version: desiredVersion + }); +} + function handleApiLanguageToggles(language) { if (!document.getElementById(`toggle-${language}`)) return; @@ -77,6 +51,59 @@ 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"); + + // 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() { 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/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..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 = { - "&": "&", - "<": "<", - ">": ">" - }; - - return this.replace(/[&<>]/g, tag => tagsToReplace[tag] || tag); -}; - // R E D I S let client; @@ -142,7 +132,7 @@ function generateEvent(event) { rel="noopener noreferrer" target="_blank" title="View this comment on GitHub" - >${event.payload.issue.title.escape()} in + >${escapeSpecialCharacters(event.payload.issue.title)} in `; } else { return ` @@ -153,7 +143,7 @@ function generateEvent(event) { rel="noopener noreferrer" target="_blank" title="View this comment on GitHub" - >${event.payload.issue.title.escape()} in + >${escapeSpecialCharacters(event.payload.issue.title)} in `; } @@ -171,7 +161,7 @@ function generateEvent(event) { rel="noopener noreferrer" target="_blank" title="View this issue on GitHub" - >${event.payload.issue.title.escape()} in + >${escapeSpecialCharacters(event.payload.issue.title)} in `; case "PullRequestEvent": @@ -188,7 +178,7 @@ function generateEvent(event) { rel="noopener noreferrer" target="_blank" title="View this pull request on GitHub" - >${event.payload.pull_request.title.escape()} in + >${escapeSpecialCharacters(event.payload.pull_request.title)} in `; case "PullRequestReviewCommentEvent": @@ -205,7 +195,7 @@ function generateEvent(event) { rel="noopener noreferrer" target="_blank" title="View this comment on GitHub" - >${event.payload.pull_request.title.escape()} in + >${escapeSpecialCharacters(event.payload.pull_request.title)} in `; case "PushEvent": @@ -346,6 +336,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 +359,16 @@ function updateGithubFeed() { // H E L P E R +function escapeSpecialCharacters(contentToEscape) { + const tagsToReplace = { + "&": "&", + "<": "<", + ">": ">" + }; + + 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 123a776..ff68d0f 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", + 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/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 2d38cf9..7a981f7 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; @@ -14,23 +14,23 @@ const relativeDate = (undefined => { 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 = (undefined => { 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]; - } } } @@ -53,6 +52,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/sockets.js b/app/sockets.js index f5cc29f..52fb9fc 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,22 @@ 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; } @@ -107,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) { @@ -434,7 +452,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 934f84e..1f5de32 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,48 @@ 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); + const { tag } = state; + const { wildcard } = state.params; + + const repository = wildcard === "sdk" ? + "lbry-sdk" : + "lbrycrd"; state.lbry = { - title: apilabel + " API Documentation", - description: "See API documentation, signatures, and sample calls for the LBRY " + apilabel + " APIs." + title: tag ? tag + " API Documentation" : "API Documentation", + description: "See API documentation, signatures, and sample calls for the LBRY APIs." }; + const tags = await getTags(repository); + const currentTag = tag.length ? tag : tags[0]; + try { - const apiResponse = await parseApiFile(state.params.wildcard); + const apiResponse = await parseApiFile({ repo: repository, tag: currentTag }); return asyncHtml`
+
+ - ${createApiHeader(state.params.wildcard)} - ${apilabel === "SDK" ? createSdkContent(apiResponse) : createApiContent(apiResponse)} + ${createApiHeader(wildcard, currentTag)} + ${wildcard === "sdk" ? createSdkContent(apiResponse) : createApiContent(apiResponse)}
+ + + + +
- - - - - `; - } - - catch(error) { + } catch(error) { const redirectUrl = redirects[state.href]; return asyncHtml` @@ -134,13 +154,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; @@ -220,24 +240,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 }); @@ -259,7 +325,7 @@ function renderArguments(args) {
  • ${arg.name}
    - ${arg.is_required === true ? "" : "optional" }${arg.type} + ${arg.is_required === true ? "" : "optional"}${arg.type}
    ${typeof arg.description === "string" ? arg.description.replace(//g, ">") : ""}
    @@ -307,7 +373,33 @@ function renderReturns(args) { return returnContent; } -function renderToggles(onSdkPage) { +function renderVersionSelector(pageSlug, versions, desiredTag) { + const options = [ + "" + ]; + + let optionIndex = 0; + + versions.forEach(version => { + optionIndex++; + let selectedOption = false; + + if (desiredTag && desiredTag === version) + selectedOption = true; + else if (optionIndex === 1) + selectedOption = true; + + options.push( + `` + ); + }); + + return options; +} + +function renderCodeLanguageToggles(pageSlug) { + const onSdkPage = pageSlug === "sdk"; + return [ "", !onSdkPage ? "" : "", 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]]; } 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": {