Versionable APIs #283

Merged
NetOpWibby merged 5 commits from versionable-apis into master 2019-07-12 00:27:36 +02:00
8 changed files with 202 additions and 95 deletions
Showing only changes of commit 4b14cc1a73 - Show all commits

View file

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

View file

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

View file

@ -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,

View file

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

View file

@ -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;

View file

@ -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
};
}
}
}
});

View file

@ -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,

View file

@ -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;
}