diff --git a/app/components/client/tour-scripts.js b/app/components/client/tour-scripts.js
index e066cfd..1fd3f10 100644
--- a/app/components/client/tour-scripts.js
+++ b/app/components/client/tour-scripts.js
@@ -13,30 +13,41 @@ if (window.location.href.search && window.location.href.split("?url=")[1]) { //
-$("body").on("click", "[data-action]", event => {
- event.preventDefault();
+document.querySelector("body").addEventListener("click", event => {
+ if (event.target.dataset.action) {
+ event.preventDefault();
+ document.querySelector(".tour").classList.add("waiting");
+ handleExamples(event.target);
+ }
- $(".tour").addClass("waiting");
+ if (
+ event.explicitOriginalTarget.classList &&
+ event.explicitOriginalTarget.classList[0] === "tour__content__meme__canvas__thumbnail"
+ ) {
+ for (const thumbnail of document.querySelectorAll(".tour__content__meme__canvas__thumbnail")) {
+ thumbnail.classList.remove("selected");
+ }
- setTimeout(() => {
- handleExamples(event);
- $(".tour").removeClass("waiting");
- }, 2500); // "rate-limit" to allow example divs time to populate
+ event.explicitOriginalTarget.classList.add("selected");
+ updateCanvas(event.explicitOriginalTarget);
+ }
});
-$("body").on("click", ".tour__content__meme__canvas__thumbnail", event => {
- $(".tour__content__meme__canvas__thumbnail").removeClass("selected");
-
- event.currentTarget.className += " selected";
- updateCanvas(event.currentTarget);
-});
-
-$("#fetch-claim-uri").on("keyup", event => {
+document.getElementById("fetch-claim-uri").addEventListener("keyup", event => {
const key = event.keyCode ? event.keyCode : event.which;
- if (key === 13 && $("#fetch-claim-uri").val()) fetchMetadata(1, $("#fetch-claim-uri").val());
+
+ if (
+ key === 13 &&
+ document.getElementById("fetch-claim-uri").value.length > 0
+ ) fetchMetadata(1, document.getElementById("fetch-claim-uri").value);
});
-$("body").on("keyup", "#meme-top-line, #meme-bottom-line", () => updateCanvas());
+document.querySelector("body").addEventListener("keyup", event => {
+ if (
+ event.target.id === "meme-top-line" ||
+ event.target.id === "meme-bottom-line"
+ ) updateCanvas();
+});
@@ -94,17 +105,18 @@ function debounce(func, wait, immediate) {
}
function initializeTour() {
- $(".tour").addClass("waiting");
- $("#fetch-claim-uri").val("").focus(); // reset
- $(".tour__sidebar__example:nth-child(1)").addClass("active");
+ document.querySelector(".tour").classList.add("waiting");
+ document.querySelector("#fetch-claim-uri").value = "";
+ document.querySelector("#fetch-claim-uri").focus();
+ document.querySelector(".tour__navigation__example:nth-child(1)").classList.add("active");
send(JSON.stringify({
"message": "landed on tour"
}));
setTimeout(() => {
- $(".tour").removeClass("waiting");
- }, 2500);
+ document.querySelector(".tour__navigation__example:nth-child(1)").click();
+ }, 300);
}
@@ -200,10 +212,10 @@ function getMemeInfo() { // TODO: Error handling
const handleExamples = debounce(event => {
let exampleNumber;
- const data = event.currentTarget.dataset;
+ const data = event.dataset;
- if (!parseInt($(".tour__sidebar__example.active")[0].dataset.example)) return;
- exampleNumber = parseInt($(".tour__sidebar__example.active")[0].dataset.example);
+ if (!parseInt($(".tour__navigation__example.active")[0].dataset.example)) return;
+ exampleNumber = parseInt($(".tour__navigation__example.active")[0].dataset.example);
switch(data.action) {
case "choose claim":
@@ -224,8 +236,8 @@ const handleExamples = debounce(event => {
$("#tour-url button").text("Resolve");
if ($("#tour-url")[0].style.display === "none") $("#tour-url").show();
- $(".tour__sidebar__example").removeClass("active");
- $(".tour__sidebar__example:nth-child(1)").addClass("active");
+ $(".tour__navigation__example").removeClass("active");
+ $(".tour__navigation__example:nth-child(1)").addClass("active");
$("#tour-loader").empty().show();
$("#tour-results").empty().show();
@@ -244,8 +256,8 @@ const handleExamples = debounce(event => {
$("#fetch-claim-uri").val(""); // reset URL bar
$("#tour-url").hide();
- $(".tour__sidebar__example").removeClass("active");
- $(".tour__sidebar__example:nth-child(2)").addClass("active");
+ $(".tour__navigation__example").removeClass("active");
+ $(".tour__navigation__example:nth-child(2)").addClass("active");
$("#tour-loader").empty().show();
$("#tour-results").empty().show();
@@ -266,8 +278,8 @@ const handleExamples = debounce(event => {
// $("#tour-url").after("
In the LBRY app, you can financially support your favorite creators by donating LBRY Coin (LBC). In this example, we are donating LBC in your stead.
");
if ($("#tour-url")[0].style.display === "none") $("#tour-url").show();
- $(".tour__sidebar__example").removeClass("active");
- $(".tour__sidebar__example:nth-child(3)").addClass("active");
+ $(".tour__navigation__example").removeClass("active");
+ $(".tour__navigation__example:nth-child(3)").addClass("active");
$("#tour-loader").empty().show();
$("#tour-results").empty().show();
diff --git a/app/components/head.js b/app/components/head.js
index 97c34d1..9695bb3 100644
--- a/app/components/head.js
+++ b/app/components/head.js
@@ -39,30 +39,29 @@ module.exports = exports = (state, emit) => {
-
-
-
-
-
+
+
+
+
+
+
+
-
+
-
-
-
+
+
+
-
-
+
-
-
`;
};
diff --git a/app/components/navigation.js b/app/components/navigation.js
index 00b505b..9de7ee9 100644
--- a/app/components/navigation.js
+++ b/app/components/navigation.js
@@ -30,9 +30,9 @@ export default class Navigation extends Nanocomponent {
url: "/overview"
},
{
- name: "Tour",
- title: "Take a Tour",
- url: "/tour"
+ name: "Playground",
+ title: "Experience LBRY",
+ url: "/playground"
},
{
name: "Resources",
diff --git a/app/components/playground.js b/app/components/playground.js
new file mode 100644
index 0000000..a7400bc
--- /dev/null
+++ b/app/components/playground.js
@@ -0,0 +1,79 @@
+"use strict";
+
+
+
+// P A C K A G E S
+
+import dedent from "dedent";
+import html from "choo/html";
+import raw from "choo/html/raw";
+
+
+
+// E X P O R T
+
+export default function () {
+ return dedent`
+
+ `;
+}
+
+
+
+// H E L P E R S
+
+function example1() {
+ return html`
+
+ lbry://
+ Resolve
+
+
+
+
+
+
+ `;
+}
+
+function navigation() { // TODO: Save tutorial position to localStorage
+ return dedent`
+
+ Resolve
+ Get details of media (aka, "claim" metadata)
+
+
+
+ Publish
+ Create a meme and upload it to the LBRY blockchain
+
+
+
+ Support
+ Support creators on LBRY with a tip, on us!
+
+ `;
+}
diff --git a/app/dist/media/images/og-image.png b/app/dist/media/images/og-image.png
new file mode 100644
index 0000000..e9d3247
Binary files /dev/null and b/app/dist/media/images/og-image.png differ
diff --git a/app/dist/scripts/sockets.js b/app/dist/scripts/sockets.js
index adcebe9..6e901dc 100644
--- a/app/dist/scripts/sockets.js
+++ b/app/dist/scripts/sockets.js
@@ -1,34 +1,60 @@
-/* global $, log, ws */ "use strict";
+"use strict";
-// const log = console.log; // eslint-disable-line
+document.addEventListener("DOMContentLoaded", () => {
+ initializeWebSocketConnection();
+ setInterval(checkWebSocketConnection, 5000);
+});
-ws.onmessage = socket => {
- const data = JSON.parse(socket.data);
+let ws = null;
- switch (true) {
- case data.message === "updated html":
- $(data.selector).html(data.html);
- $("#emailMessage").val("");
- break;
+function checkWebSocketConnection() {
+ if (!ws || ws.readyState === 3) initializeWebSocketConnection();
+}
- case data.message === "notification": // TODO: Make work with appending so multiple notifications can be sent
- $("#flash-container").html(`${data.details}
`);
+function initializeWebSocketConnection() {
+ ws = new WebSocket(location.origin.replace(/^http/, "ws"));
- setTimeout(() => {
- $("#flash-container").html("");
- }, 2100);
+ ws.onopen = () => {
+ console.log("WebSocket connection established"); // eslint-disable-line
+ };
- break;
+ ws.onmessage = socket => {
+ const data = JSON.parse(socket.data);
- default:
- log(data);
- break;
- }
-};
+ switch (true) {
+ case data.message === "updated html":
+ document.querySelector(data.selector).innerHTML = data.html;
+ document.getElementById("emailAddress").value = "";
+ document.getElementById("emailMessage").innerHTML = "";
+ if (document.getElementById("temp-loader")) document.getElementById("temp-loader").style.display = "none";
+ document.querySelector(".tour").classList.remove("waiting");
+ break;
+
+ case data.message === "notification": // TODO: Make work with appending so multiple notifications can be sent
+ document.getElementById("flash-container").innerHTML =
+ `${data.details}
`;
+
+ setTimeout(() => {
+ document.getElementById("flash-container").innerHTML = "";
+ }, 2100);
+
+ break;
+
+ default:
+ console.log(data); // eslint-disable-line
+ break;
+ }
+ };
+
+ ws.onclose = () => {
+ console.log("WebSocket connection lost"); // eslint-disable-line
+ checkWebSocketConnection(); // reconnect now
+ };
+}
function send(msg) { // eslint-disable-line
socketReady(ws, () => ws.send(msg));
@@ -36,11 +62,11 @@ function send(msg) { // eslint-disable-line
function socketReady(socket, callback) {
setTimeout(() => {
- if (socket.readyState === 1) {
+ if (socket && socket.readyState === 1) {
if (callback !== undefined) callback();
return;
- } else {
- socketReady(socket, callback);
}
+
+ return socketReady(socket, callback);
}, 5);
}
diff --git a/app/helpers/fetch-metadata.js b/app/helpers/fetch-metadata.js
index 13c714b..2ea38ed 100644
--- a/app/helpers/fetch-metadata.js
+++ b/app/helpers/fetch-metadata.js
@@ -12,11 +12,11 @@ const stringifyObject = require("stringify-object");
// V A R I A B L E S
-const randomString = local("/app/helpers/random-string");
+const randomString = local("app/helpers/random-string");
const loadLanguages = require("prismjs/components/");
-const logSlackError = local("/app/helpers/slack");
-const publishMeme = local("/app/helpers/publish-meme");
-const uploadImage = local("/app/helpers/upload-image");
+const logSlackError = local("app/helpers/slack");
+const publishMeme = local("app/helpers/publish-meme");
+const uploadImage = local("app/helpers/upload-image");
loadLanguages(["json"]);
@@ -126,7 +126,6 @@ module.exports = exports = (data, socket) => {
Response
${explorerNotice}
${renderedCode}
-
`),
"message": "updated html",
"selector": `#example${data.example}-result`
@@ -195,7 +194,6 @@ module.exports = exports = (data, socket) => {
Response
${explorerNotice}
${renderedCode}
-
`),
"message": "updated html",
"selector": `#example${data.example}-result`
diff --git a/app/helpers/github.js b/app/helpers/github.js
index ab9e2dd..da4f721 100644
--- a/app/helpers/github.js
+++ b/app/helpers/github.js
@@ -2,6 +2,46 @@
+// P A C K A G E S
+
+const async = require("async");
+const color = require("colorette");
+const local = require("app-root-path").require;
+const octokit = require("@octokit/rest")();
+const redis = require("redis");
+
+// V A R I A B L E S
+
+const logSlackError = local("app/helpers/slack");
+const relativeDate = local("app/modules/relative-date");
+let client;
+
+// R E D I S
+
+if (typeof process.env.GITHUB_OAUTH_TOKEN !== "undefined") {
+ octokit.authenticate({
+ type: "oauth",
+ token: process.env.GITHUB_OAUTH_TOKEN
+ });
+} else process.stdout.write(`${color.red("[missing]")} GitHub token`);
+
+if (typeof process.env.REDISCLOUD_URL !== "undefined") {
+ client = redis.createClient(process.env.REDISCLOUD_URL);
+
+ client.on("error", redisError => {
+ process.env.NODE_ENV === "development" ?
+ process.stdout.write(`\n${color.yellow("Unable to connect to Redis client.")}\nYou may be missing an .env file or your connection was reset.`) :
+ logSlackError(
+ "\n" +
+ "> *REDIS ERROR:* ```" + JSON.parse(JSON.stringify(redisError)) + "```" + "\n" +
+ "> _Cause: Someone is trying to run LBRY.tech locally without environment variables OR Heroku is busted_\n"
+ )
+ ;
+ });
+} else process.stdout.write(`${color.red("[missing]")} Redis client URL`);
+
+
+
// P R O G R A M
function generateEvent(event) {
@@ -84,6 +124,44 @@ function generateEvent(event) {
}
}
+function generateGitHubFeed(displayGitHubFeed) {
+ if (typeof process.env.REDISCLOUD_URL !== "undefined") {
+ client.zrevrange("events", 0, 9, (err, reply) => {
+ if (err) return; // TODO: Render a div with nice error message
+
+ const events = [];
+ const renderedEvents = [];
+
+ reply.forEach(item => events.push(JSON.parse(item)));
+
+ for (const event of events) {
+ renderedEvents.push(`
+
+
+
+
+
+
+ ${generateEvent(event)}
+ ${event.repo.name}
+ ${relativeDate(new Date(event.created_at))}
+
+
+ `);
+ }
+
+ updateGithubFeed(); // TODO: Update `.last-updated` every minute
+
+ displayGitHubFeed(`
+ GitHub
+ Last updated: ${new Date().format("YYYY-MM-DD").replace(/-/g, "·")} at ${new Date().add(-4, "hours").format("UTC:H:mm:ss A").toLowerCase()} EST
+
+ ${renderedEvents.join("")}
+ `);
+ });
+ }
+}
+
function generateUrl(type, event) {
switch (type) {
case "actor":
@@ -115,6 +193,29 @@ function generateUrl(type, event) {
}
}
+function updateGithubFeed() {
+ octokit.activity.getEventsForOrg({
+ org: "lbryio",
+ per_page: 20,
+ page: 1
+ }).then(({ data }) => {
+ async.eachSeries(data, (item, callback) => {
+ const eventString = JSON.stringify(item);
+
+ client.zrank("events", eventString, (err, reply) => {
+ if (reply === null) client.zadd("events", item.id, eventString, callback);
+ else callback();
+ });
+ }, () => client.zremrangebyrank("events", 0, -51)); // Keep the latest 50 events
+ }).catch(err => {
+ logSlackError(
+ "\n" +
+ "> *GITHUB FEED ERROR:* ```" + JSON.parse(JSON.stringify(err)) + "```" + "\n" +
+ "> _Cause: GitHub feed refresh_\n"
+ );
+ });
+}
+
// H E L P E R
@@ -129,5 +230,7 @@ function refToBranch(ref) {
module.exports = exports = {
generateEvent,
- generateUrl
+ generateGitHubFeed,
+ generateUrl,
+ updateGithubFeed
};
diff --git a/app/index.js b/app/index.js
index 138becd..c3c0aec 100755
--- a/app/index.js
+++ b/app/index.js
@@ -1,4 +1,2 @@
-// "use strict"; require("make-promises-safe"); const app = require("./server.js"); // eslint-disable-line
-
"use strict"; require("@babel/register"); require("@babel/polyfill");
module.exports = exports = require("./client.js");
diff --git a/app/sass/bundle.scss b/app/sass/bundle.scss
index cb36abf..0d14ce4 100755
--- a/app/sass/bundle.scss
+++ b/app/sass/bundle.scss
@@ -21,6 +21,7 @@
"partials/navigation",
"partials/mission-statement",
"partials/modal",
+ "partials/pre",
"layout",
diff --git a/app/sass/init/_extends.scss b/app/sass/init/_extends.scss
index af6cb4d..d27d41e 100644
--- a/app/sass/init/_extends.scss
+++ b/app/sass/init/_extends.scss
@@ -137,6 +137,8 @@
.__loading {
width: 100%; height: 10rem;
+
+ cursor: wait;
position: relative;
&::before {
@@ -149,12 +151,14 @@
border-top-color: $teal;
border-width: 6px;
content: "";
+ cursor: wait;
position: absolute;
}
&::after {
top: 7rem; left: 0;
+ cursor: wait;
font-size: 1rem;
position: absolute;
text-align: center;
diff --git a/app/sass/init/_markdown.scss b/app/sass/init/_markdown.scss
index 9d8b174..9b12971 100644
--- a/app/sass/init/_markdown.scss
+++ b/app/sass/init/_markdown.scss
@@ -179,36 +179,7 @@
}
pre {
- margin-bottom: 2rem; padding: 2rem;
-
- border-radius: 3px;
font-size: 1rem;
- line-height: 1.33;
- overflow-x: auto;
- overflow-y: hidden;
-
- &:not([class]),
- &.language-text {
- background-color: #27283e;
- color: $white;
- }
-
- &.language-yaml {
- background-color: #27273f;
- color: #ffe066;
-
- .atrule {
- color: #f083ac;
- }
-
- .important {
- color: #ffa94d;
- }
-
- .punctuation {
- color: $white;
- }
- }
}
h2, h3, h4, h5 {
diff --git a/app/sass/pages/_api.scss b/app/sass/pages/_api.scss
index 1769906..e57ca6e 100644
--- a/app/sass/pages/_api.scss
+++ b/app/sass/pages/_api.scss
@@ -147,6 +147,10 @@
margin-bottom: 1rem;
}
+ pre {
+ font-size: 0.8rem;
+ }
+
table {
border: 1px solid rgba($white, 0.1);
border-radius: 0.3rem;
@@ -171,16 +175,6 @@
tr:nth-child(even) {
background-color: rgba($white, 0.1);
}
-
- pre {
- margin-bottom: 2rem; padding: 1rem;
-
- border-radius: 0.3rem;
- font-size: 0.8rem;
- line-height: 1.33;
- overflow-x: auto;
- overflow-y: hidden;
- }
}
.api__content__body {
diff --git a/app/sass/pages/_tour.scss b/app/sass/pages/_tour.scss
index f009ce0..36b175a 100644
--- a/app/sass/pages/_tour.scss
+++ b/app/sass/pages/_tour.scss
@@ -31,46 +31,47 @@
/**
- * Tour | Sidebar
+ * Tour | Navigation
*
- * @class .tour__sidebar
+ * @class .tour__navigation
*
- * @class .tour__sidebar__example
+ * @class .tour__navigation__example
* @selector {::before}
* @selector {:last-of-type}
* @state {.active}
* @state {:hover}
*/
-.tour__sidebar {
- width: 250px; height: 100%;
+.tour__navigation {
+ width: 100%;
- float: left;
list-style-type: none;
- padding-top: 1rem;
- padding-right: 1rem;
- vertical-align: top;
+ padding-bottom: 1rem;
+ padding-top: 1.5rem;
+
+ &::after {
+ @include clearfix;
+ }
}
-.tour__sidebar__example {
+.tour__navigation__example {
cursor: pointer;
+ float: left;
position: relative;
+ text-align: center;
+ width: 33.333333%;
&::before {
- width: 1rem; height: 1rem;
- top: 0.5rem; left: 0;
+ width: 100%; height: 2.5rem;
+ top: -0.6rem; left: 0;
- border: 1px solid;
- border-radius: 50%;
- content: attr(data-example);
- font-size: 0.8rem;
+ content: "example " attr(data-example);
+ font-size: 0.6rem;
+ font-style: italic;
line-height: 1.1;
position: absolute;
text-align: center;
- }
-
- &:not(:last-of-type) {
- margin-bottom: 1.5rem;
+ text-transform: uppercase;
}
&:not(.active) {
@@ -93,6 +94,20 @@
}
}
+ &.completed {
+ &::after {
+ width: 100%; height: 100%;
+ top: 0; left: 0;
+
+ background-color: rgba($white, 0.7);
+ content: "✓";
+ font-size: 3rem;
+ line-height: 0.85;
+ position: absolute;
+ z-index: 10;
+ }
+ }
+
&::before,
button,
span {
@@ -103,7 +118,6 @@
background-color: transparent;
font-size: 1.25rem;
font-weight: 600;
- padding-left: 1.3rem;
}
span {
@@ -139,12 +153,10 @@
*/
.tour__content {
- width: calc(100% - 250px); height: 100%; min-height: 500px;
-
- border-left: 1px solid rgba($black, 0.05);
- float: right;
- padding: 1rem 0 1rem 1rem;
- vertical-align: top;
+ border-top: 1px solid rgba($black, 0.05);
+ overflow-y: visible;
+ padding-bottom: 1rem;
+ padding-top: 1rem;
.loader {
@extend .__loading;
@@ -153,6 +165,10 @@
content: "Processing request";
}
}
+
+ pre {
+ font-size: 1rem;
+ }
}
.tour__content__meme {
@@ -167,6 +183,7 @@
.tour__content__meme__canvas {
float: left;
margin-right: 2%;
+ position: relative;
width: 48%;
canvas {
@@ -202,7 +219,7 @@
.tour__content__meme__editor {
float: right;
- width: 48%;
+ width: 50%;
h2.__metadata {
margin-top: 3rem;
@@ -315,10 +332,11 @@
}
.tour__content__trends {
+ min-width: 0; min-height: 0;
+
display: grid;
- grid-gap: 2%;
- grid-template-columns: 32% 32% 32%;
- overflow-y: auto;
+ grid-gap: 1rem;
+ grid-template: repeat(1, 1fr) / repeat(3, 1fr);
position: relative;
&:empty {
@@ -332,7 +350,7 @@
.tour__content__trend {
img {
- width: 100%; height: 175px;
+ width: 100%; height: 213px;
cursor: pointer;
display: block;
@@ -428,3 +446,20 @@
width: 3.5rem;
}
}
+
+
+
+/**
+ * Tour | Description
+ *
+ * @class .tour__description
+ */
+
+.tour__description {
+ background-color: rgba($black, 0.05);
+ cursor: default;
+ font-size: 1rem;
+ line-height: 1.33;
+ padding: 1rem;
+ text-align: center;
+}
diff --git a/app/sass/partials/_github-feed.scss b/app/sass/partials/_github-feed.scss
index 70fe8bc..46d1dbd 100644
--- a/app/sass/partials/_github-feed.scss
+++ b/app/sass/partials/_github-feed.scss
@@ -50,7 +50,6 @@
letter-spacing: 0.1rem;
line-height: 1;
text-transform: uppercase;
- width: 100%;
@media (min-width: 1301px) {
top: 2.15rem; left: 0;
@@ -58,6 +57,7 @@
color: rgba($black, 0.045);
font-size: 4rem;
position: absolute;
+ width: calc(100% - (1rem + 5%));
}
@media (max-width: 1300px) {
diff --git a/app/sass/partials/_pre.scss b/app/sass/partials/_pre.scss
new file mode 100644
index 0000000..41bb560
--- /dev/null
+++ b/app/sass/partials/_pre.scss
@@ -0,0 +1,30 @@
+pre {
+ margin-bottom: 2rem; padding: 2rem;
+
+ line-height: 1.33;
+ overflow-x: auto;
+ overflow-y: hidden;
+
+ &:not([class]),
+ &.language-text {
+ background-color: #27283e;
+ color: $white;
+ }
+
+ &.language-yaml {
+ background-color: #27273f;
+ color: #ffe066;
+
+ .atrule {
+ color: #f083ac;
+ }
+
+ .important {
+ color: #ffa94d;
+ }
+
+ .punctuation {
+ color: $white;
+ }
+ }
+}
diff --git a/app/sockets.js b/app/sockets.js
new file mode 100644
index 0000000..1dbe2e0
--- /dev/null
+++ b/app/sockets.js
@@ -0,0 +1,394 @@
+"use strict";
+
+
+
+// P A C K A G E S
+
+const html = require("choo/html");
+const local = require("app-root-path").require;
+const request = require("request-promise-native");
+
+// V A R I A B L E S
+
+const fetchMetadata = local("app/helpers/fetch-metadata");
+const { generateGitHubFeed } = local("app/helpers/github");
+const logSlackError = local("app/helpers/slack");
+
+
+
+// P R O G R A M
+
+module.exports = exports = (socket, action) => {
+ if (typeof socket !== "object" && typeof action !== "object") return;
+
+ switch(true) {
+ case (action.message === "fetch metadata"):
+ fetchMetadata(action, socket);
+ break;
+
+ case (action.message === "landed on homepage"):
+ generateGitHubFeed(result => {
+ socket.send(JSON.stringify({
+ "html": result,
+ "message": "updated html",
+ "selector": "#github-feed"
+ }));
+ });
+ break;
+
+ case (action.message === "landed on tour"):
+ generateContent(1, result => {
+ socket.send(JSON.stringify({
+ "html": result,
+ "message": "updated html",
+ "selector": "#tour-loader"
+ }));
+ });
+ break;
+
+ case (action.message === "request for tour, example 1"):
+ generateContent(1, result => {
+ socket.send(JSON.stringify({
+ "html": result,
+ "message": "updated html",
+ "selector": "#tour-loader"
+ }));
+ });
+ break;
+
+ case (action.message === "request for tour, example 2"):
+ generateMemeCreator(socket);
+ break;
+
+ case (action.message === "request for tour, example 3"):
+ generateContent(3, result => {
+ socket.send(JSON.stringify({
+ "html": result,
+ "message": "updated html",
+ "selector": "#tour-loader"
+ }));
+ });
+ break;
+
+ case (action.message === "subscribe"):
+ newsletterSubscribe(action, socket);
+ break;
+
+ default:
+ process.stdout.write(action);
+ break;
+ }
+};
+
+
+
+// H E L P E R S
+
+function generateMemeCreator(socket) {
+ const images = [
+ {
+ alt: "Carl Sagan",
+ src: "/assets/media/images/carlsagan2.jpg"
+ },
+ {
+ alt: "Doge",
+ src: "/assets/media/images/doge-meme.jpg"
+ },
+ {
+ alt: "LBRY Logo With Green Background",
+ src: "/assets/media/images/lbry-green.png"
+ }
+ ];
+
+ const memePlaceholderData = {
+ bottomLine: {
+ placeholder: "Top line",
+ value: "that I made"
+ },
+ description: {
+ placeholder: "Description",
+ value: "Check out this image I published to LBRY via lbry.tech"
+ },
+ topLine: {
+ placeholder: "Top line",
+ value: "This is an example meme"
+ },
+ title: {
+ placeholder: "Title",
+ value: "Dank Meme Supreme da Cheese"
+ }
+ };
+
+ const renderedImages = [];
+
+ for (const image of images) {
+ renderedImages.push(` `);
+ }
+
+ const memeCreator = html`
+
+
+
Unfortunately, it looks like canvas is not supported in your browser
+
+ ${renderedImages}
+
+
+
+
+
+ `;
+
+ return socket.send(JSON.stringify({
+ "html": memeCreator,
+ "message": "updated html",
+ "selector": "#tour-loader"
+ }));
+}
+
+function generateContent(exampleNumber, displayTrendingContent) {
+ if (exampleNumber === 1) {
+ return getTrendingContent().then(response => {
+ if (!response || !response.success || response.success !== true || !response.data) return "";
+
+ const rawContentCollection = [];
+ const renderedContentCollection = [];
+ const trendingContentData = response.data;
+
+ for (const data of trendingContentData) {
+ rawContentCollection.push(fetchMetadata({ claim: data.url, method: "resolve", example: exampleNumber }));
+ }
+
+ Promise.all(rawContentCollection).then(collection => {
+ for (const part of collection) {
+ try {
+ renderedContentCollection.push(`
+
+
+
+
+ ${part.value.stream.metadata.title}
+ ${part.channel_name}
+
+
+ `);
+ } catch (err) {
+ return; // TODO: Return nice error message
+ }
+ }
+
+ renderedContentCollection.push(`
+
+ `);
+
+ displayTrendingContent(renderedContentCollection.join(""));
+ });
+ });
+ }
+
+ if (exampleNumber === 3) {
+ const approvedUrls = [
+ "LBRY#3db81c073f82fd1bb670c65f526faea3b8546720",
+ "correlation-can-imply-causation#173412f5b1b7aa63a752e8832406aafd9f1ecb4e",
+ "thanos-is-the-protagonist-how-infinity#2a7f5db2678177435b1dee6c9e38e035ead450b6nyte",
+ "epic-arcade-mode-duos-nickatnyte-molt#d81bac6d49b1f92e58c37a5f633a27a45b43405e",
+ "political-correctness-a-force-for-good-a#b4668c0bd096317b44c40738c099b6618095e75f",
+ "10-secrets-hidden-inside-famous-logos#007789cc45cbb4255cf02ba77cbf84ca8e3d7561",
+ "ever-wonder-how-bitcoin-and-other#1ac47b8b3def40a25850dc726a09ce23d09e7009",
+ "bankrupt-pan-am#784b3c215a6f06b663fc1aa292bcb19f29c489bb",
+ "minecraft-in-real-life-iron-man#758dd6497cdfc401ae1f25984738d024d47b50af",
+ "ethan-shows-kyle-warframe-skyvault#8a7401b88d5ed0376d98f16808194d4dcb05b284"
+ ];
+
+ const rawContentCollection = [];
+ const renderedContentCollection = [];
+
+ for (const url of approvedUrls) {
+ rawContentCollection.push(fetchMetadata({ claim: url, method: "resolve", example: exampleNumber }));
+ }
+
+ Promise.all(rawContentCollection).then(collection => {
+ for (const part of collection) {
+ try {
+ renderedContentCollection.push(`
+
+
+
+
+ ${part.value.stream.metadata.title}
+ ${part.channel_name}
+
+
+ `);
+ } catch (err) {
+ return; // TODO: Return nice error message
+ }
+ }
+
+ renderedContentCollection.push(`
+
+ `);
+
+ displayTrendingContent(renderedContentCollection.join(""));
+ });
+ }
+}
+
+function getTrendingContent() {
+ return new Promise((resolve, reject) => { // eslint-disable-line
+ request({
+ method: "GET",
+ url: "https://api.lbry.io/file/list_trending"
+ }, (error, response, body) => {
+ if (error || !JSON.parse(body)) resolve("Issue fetching content"); // error
+ body = JSON.parse(body);
+ resolve(body);
+ });
+ });
+}
+
+function newsletterSubscribe(data, socket) {
+ const email = data.email;
+
+ if (!validateEmail(email)) return socket.send(JSON.stringify({
+ "html": "Your email is invalid",
+ "message": "updated html",
+ "selector": "#emailMessage"
+ }));
+
+ return new Promise((resolve, reject) => {
+ request({
+ method: "POST",
+ url: `https://api.lbry.io/list/subscribe?email=${email}&tag=developer`
+ }).then(body => {
+ if (!body || !JSON.parse(body)) {
+ logSlackError(
+ "\n" +
+ "> *NEWSLETTER ERROR:* ```¯\\_(ツ)_/¯ This should be an unreachable error```" + "\n" +
+ `> _Cause: ${email} interacted with the form_\n`
+ );
+
+ return resolve(socket.send(JSON.stringify({
+ "html": "Something is terribly wrong",
+ "message": "updated html",
+ "selector": "#emailMessage"
+ })));
+ }
+
+ body = JSON.parse(body);
+
+ if (!body.success) {
+ logSlackError(
+ "\n" +
+ "> *NEWSLETTER ERROR:* ```" + JSON.parse(JSON.stringify(body.error)) + "```" + "\n" +
+ `> _Cause: ${email} interacted with the form_\n`
+ );
+
+ return reject(socket.send(JSON.stringify({
+ "html": body.error,
+ "message": "updated html",
+ "selector": "#emailMessage"
+ })));
+ }
+
+ return resolve(socket.send(JSON.stringify({
+ "html": "Thank you! Please confirm subscription in your inbox.",
+ "message": "updated html",
+ "selector": "#emailMessage"
+ })));
+ }).catch(welp => {
+ if (welp.statusCode === 409) {
+ logSlackError(
+ "\n" +
+ "> *NEWSLETTER ERROR:* ```" + JSON.parse(JSON.stringify(welp.error)) + "```" + "\n" +
+ `> _Cause: ${email} interacted with the form_\n`
+ );
+
+ return resolve(socket.send(JSON.stringify({
+ "html": "You have already subscribed!",
+ "message": "updated html",
+ "selector": "#emailMessage"
+ })));
+ }
+ });
+ });
+}
+
+function validateEmail(email) {
+ const emailRegex = /^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\\.,;:\s@"]{2,})$/i;
+ return emailRegex.test(String(email));
+}
diff --git a/app/views/home.js b/app/views/home.js
index 8cd4b97..cd60c28 100644
--- a/app/views/home.js
+++ b/app/views/home.js
@@ -17,8 +17,8 @@ import linkGrid from "../components/link-grid";
const featureLinks = linkGrid([
{
description: "Learn how LBRY works with 3 easy examples",
- destination: "/tour",
- label: "Take the Tour",
+ destination: "/playground",
+ label: "Jump into Playground",
title: "New to LBRY?"
},
{
diff --git a/app/views/redirect.js b/app/views/redirect.js
index 0fa3857..b7ddce3 100644
--- a/app/views/redirect.js
+++ b/app/views/redirect.js
@@ -85,7 +85,7 @@ module.exports = exports = (state, emit) => { // eslint-disable-line
let pageScript = "";
if (path === "glossary") pageScript = "";
if (path === "overview") pageScript = "";
- if (path === "tour") pageScript = "";
+ if (path === "playground") pageScript = "";
return html`
diff --git a/documents/playground.md b/documents/playground.md
new file mode 100644
index 0000000..40dc49d
--- /dev/null
+++ b/documents/playground.md
@@ -0,0 +1,9 @@
+---
+title: Playground
+---
+
+Check out any of the interactive examples to get a feel for the LBRY protocol!
+
+LBRY (pronounced "library") is an application layer protocol, similar to HTTP. However, while HTTP links can direct you to decentralized content, the LBRY protocol *itself* is decentralized.
+
+
diff --git a/server.js b/server.js
index b175bba..909eb0a 100755
--- a/server.js
+++ b/server.js
@@ -4,10 +4,9 @@
// P A C K A G E S
-const async = require("async");
const color = require("colorette");
const cors = require("cors");
-const dedent = require("dedent");
+const local = require("app-root-path").require;
const fastify = require("fastify")({
logger: {
@@ -16,42 +15,10 @@ const fastify = require("fastify")({
}
});
-const html = require("choo/html");
-const local = require("app-root-path").require;
-const octokit = require("@octokit/rest")();
-const redis = require("redis");
-const request = require("request-promise-native");
-
// V A R I A B L E S
-const fetchMetadata = local("app/helpers/fetch-metadata");
-const github = local("app/helpers/github");
-const log = console.log; // eslint-disable-line
+const handleSocketMessages = local("app/sockets");
const logSlackError = local("app/helpers/slack");
-const relativeDate = local("app/modules/relative-date");
-let client;
-
-if (typeof process.env.GITHUB_OAUTH_TOKEN !== "undefined") {
- octokit.authenticate({
- type: "oauth",
- token: process.env.GITHUB_OAUTH_TOKEN
- });
-} else log(`${color.red("[missing]")} GitHub token`);
-
-if (typeof process.env.REDISCLOUD_URL !== "undefined") {
- client = redis.createClient(process.env.REDISCLOUD_URL);
-
- client.on("error", redisError => {
- process.env.NODE_ENV === "development" ?
- log(`\n${color.yellow("Unable to connect to Redis client.")}\nYou may be missing an .env file or your connection was reset.`) :
- logSlackError(
- "\n" +
- "> *REDIS ERROR:* ```" + JSON.parse(JSON.stringify(redisError)) + "```" + "\n" +
- "> _Cause: Someone is trying to run LBRY.tech locally without environment variables OR Heroku is busted_\n"
- )
- ;
- });
-} else log(`${color.red("[missing]")} Redis client URL`);
@@ -81,74 +48,10 @@ fastify.ready(err => {
fastify.ws.on("connection", socket => {
socket.on("message", data => {
data = JSON.parse(data);
-
- switch(data.message) {
- case "fetch metadata":
- fetchMetadata(data, socket);
- break;
-
- case "landed on homepage":
- generateGitHubFeed(result => {
- socket.send(JSON.stringify({
- "html": result,
- "message": "updated html",
- "selector": "#github-feed"
- }));
- });
-
- break;
-
- case "landed on tour":
- generateContent(1, result => {
- socket.send(JSON.stringify({
- "html": result,
- "message": "updated html",
- "selector": "#tour-loader"
- }));
- });
-
- break;
-
- case "request for tour, example 1":
- generateContent(1, result => {
- socket.send(JSON.stringify({
- "html": result,
- "message": "updated html",
- "selector": "#tour-loader"
- }));
- });
-
- break;
-
- case "request for tour, example 2":
- generateMemeCreator(socket);
- break;
-
- case "request for tour, example 3":
- generateContent(3, result => {
- socket.send(JSON.stringify({
- "html": result,
- "message": "updated html",
- "selector": "#tour-loader"
- }));
- });
-
- break;
-
- case "subscribe":
- newsletterSubscribe(data, socket);
- break;
-
- default:
- log(data);
- break;
- }
+ return handleSocketMessages(socket, data);
});
- socket.on("close", () => {
- // console.log(socket);
- return socket.terminate();
- });
+ socket.on("close", () => socket.terminate());
});
});
@@ -159,387 +62,15 @@ fastify.ready(err => {
const start = async () => {
try {
await fastify.listen(process.env.PORT || 8080, process.env.IP || "0.0.0.0");
- /*
- await fastify.listen(
- process.env.NODE_ENV === "development" ?
- 8080 :
- process.env.PORT
- );
- */
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
process.env.NODE_ENV === "development" ?
- log(`\n— ${color.green("⚡")} ${fastify.server.address().port}\n`) :
+ process.stdout.write(`\n— ${color.green("⚡")} ${fastify.server.address().port}\n`) :
logSlackError(`Server started at port \`${fastify.server.address().port}\``)
;
};
start();
-
-
-
-// H E L P E R S
-
-function generateGitHubFeed(displayGitHubFeed) {
- if (typeof process.env.REDISCLOUD_URL !== "undefined") {
- client.zrevrange("events", 0, 9, (err, reply) => {
- if (err) return; // TODO: Render a div with nice error message
-
- const events = [];
- const renderedEvents = [];
-
- reply.forEach(item => events.push(JSON.parse(item)));
-
- for (const event of events) {
- renderedEvents.push(`
-
-
-
-
-
-
- ${github.generateEvent(event)}
- ${event.repo.name}
- ${relativeDate(new Date(event.created_at))}
-
-
- `);
- }
-
- updateGithubFeed(); // TODO: Update `.last-updated` every minute
-
- displayGitHubFeed(dedent`
- GitHub
- Last updated: ${new Date().format("YYYY-MM-DD").replace(/-/g, "·")} at ${new Date().add(-4, "hours").format("UTC:H:mm:ss A").toLowerCase()} EST
-
- ${renderedEvents.join("")}
- `);
- });
- }
-}
-
-function generateMemeCreator(socket) {
- const images = [
- {
- alt: "Carl Sagan",
- src: "/assets/media/images/carlsagan2.jpg"
- },
- {
- alt: "Doge",
- src: "/assets/media/images/doge-meme.jpg"
- },
- {
- alt: "LBRY Logo With Green Background",
- src: "/assets/media/images/lbry-green.png"
- }
- ];
-
- const memePlaceholderData = {
- bottomLine: {
- placeholder: "Top line",
- value: "that I made"
- },
- description: {
- placeholder: "Description",
- value: "Check out this image I published to LBRY via lbry.tech"
- },
- topLine: {
- placeholder: "Top line",
- value: "This is an example meme"
- },
- title: {
- placeholder: "Title",
- value: "Dank Meme Supreme da Cheese"
- }
- };
-
- const renderedImages = [];
-
- for (const image of images) {
- renderedImages.push(` `);
- }
-
- const memeCreator = html`
-
-
-
Unfortunately, it looks like canvas is not supported in your browser
-
- ${renderedImages}
-
-
-
- Image Text
-
-
- Top line
-
-
-
-
- Bottom line
-
-
-
-
-
-
- Title
-
-
-
-
- Description
- ${memePlaceholderData.description.value}
-
-
-
- Language
-
- Arabic
- Chinese (Mandarin)
- English
- French
- German
- Italian
- Japanese
- Russian
- Spanish
- Not specified
-
-
-
-
- License
-
- Public Domain
- Creative Commons Attribution 4.0 International
- Creative Commons Attribution-ShareAlike 4.0 International
- Creative Commons Attribution-NoDerivatives 4.0 International
- Creative Commons Attribution-NonCommercial 4.0 International
- Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
- Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International
- None
-
-
-
-
- NSFW
-
-
-
- Submit
-
-
-
-
- `;
-
- return socket.send(JSON.stringify({
- "html": memeCreator,
- "message": "updated html",
- "selector": "#tour-loader"
- }));
-}
-
-function generateContent(exampleNumber, displayTrendingContent) {
- if (exampleNumber === 1) {
- return getTrendingContent().then(response => {
- if (!response || !response.success || response.success !== true || !response.data) return "";
-
- const rawContentCollection = [];
- const renderedContentCollection = [];
- const trendingContentData = response.data;
-
- for (const data of trendingContentData) {
- rawContentCollection.push(fetchMetadata({ claim: data.url, method: "resolve", example: exampleNumber }));
- }
-
- Promise.all(rawContentCollection).then(collection => {
- for (const part of collection) {
- if (
- !part.value.stream.metadata.nsfw &&
- part.value.stream.metadata.thumbnail &&
- part.channel_name
- ) {
- renderedContentCollection.push(`
-
-
-
-
- ${part.value.stream.metadata.title}
- ${part.channel_name}
-
-
- `);
- }
- }
-
- displayTrendingContent(renderedContentCollection.join(""));
- });
- });
- }
-
- if (exampleNumber === 3) {
- const approvedUrls = [
- "LBRY#3db81c073f82fd1bb670c65f526faea3b8546720",
- "correlation-can-imply-causation#173412f5b1b7aa63a752e8832406aafd9f1ecb4e",
- "thanos-is-the-protagonist-how-infinity#2a7f5db2678177435b1dee6c9e38e035ead450b6nyte",
- "epic-arcade-mode-duos-nickatnyte-molt#d81bac6d49b1f92e58c37a5f633a27a45b43405e",
- "political-correctness-a-force-for-good-a#b4668c0bd096317b44c40738c099b6618095e75f",
- "10-secrets-hidden-inside-famous-logos#007789cc45cbb4255cf02ba77cbf84ca8e3d7561",
- "ever-wonder-how-bitcoin-and-other#1ac47b8b3def40a25850dc726a09ce23d09e7009",
- "bankrupt-pan-am#784b3c215a6f06b663fc1aa292bcb19f29c489bb",
- "minecraft-in-real-life-iron-man#758dd6497cdfc401ae1f25984738d024d47b50af",
- "ethan-shows-kyle-warframe-skyvault#8a7401b88d5ed0376d98f16808194d4dcb05b284"
- ];
-
- const rawContentCollection = [];
- const renderedContentCollection = [];
-
- for (const url of approvedUrls) {
- rawContentCollection.push(fetchMetadata({ claim: url, method: "resolve", example: exampleNumber }));
- }
-
- Promise.all(rawContentCollection).then(collection => {
- for (const part of collection) {
- if (
- part &&
- part.value &&
- part.value.stream.metadata.thumbnail &&
- part.channel_name
- ) {
- renderedContentCollection.push(`
-
-
-
-
- ${part.value.stream.metadata.title}
- ${part.channel_name}
-
-
- `);
- }
- }
-
- displayTrendingContent(renderedContentCollection.join(""));
- });
- }
-}
-
-function getTrendingContent() {
- return new Promise((resolve, reject) => { // eslint-disable-line
- request({
- method: "GET",
- url: "https://api.lbry.io/file/list_trending"
- }, (error, response, body) => {
- if (error || !JSON.parse(body)) resolve("Issue fetching content"); // error
- body = JSON.parse(body);
- resolve(body);
- });
- });
-}
-
-function newsletterSubscribe(data, socket) {
- const email = data.email;
-
- if (!validateEmail(email)) return socket.send(JSON.stringify({
- "html": "Your email is invalid",
- "message": "updated html",
- "selector": "#emailMessage"
- }));
-
- return new Promise((resolve, reject) => {
- request({
- method: "POST",
- url: `https://api.lbry.io/list/subscribe?email=${email}&tag=developer`
- }).then(body => {
- if (!body || !JSON.parse(body)) {
- logSlackError(
- "\n" +
- "> *NEWSLETTER ERROR:* ```¯\\_(ツ)_/¯ This should be an unreachable error```" + "\n" +
- `> _Cause: ${email} interacted with the form_\n`
- );
-
- return resolve(socket.send(JSON.stringify({
- "html": "Something is terribly wrong",
- "message": "updated html",
- "selector": "#emailMessage"
- })));
- }
-
- body = JSON.parse(body);
-
- if (!body.success) {
- logSlackError(
- "\n" +
- "> *NEWSLETTER ERROR:* ```" + JSON.parse(JSON.stringify(body.error)) + "```" + "\n" +
- `> _Cause: ${email} interacted with the form_\n`
- );
-
- return reject(socket.send(JSON.stringify({
- "html": body.error,
- "message": "updated html",
- "selector": "#emailMessage"
- })));
- }
-
- return resolve(socket.send(JSON.stringify({
- "html": "Thank you! Please confirm subscription in your inbox.",
- "message": "updated html",
- "selector": "#emailMessage"
- })));
- }).catch(welp => {
- if (welp.statusCode === 409) {
- logSlackError(
- "\n" +
- "> *NEWSLETTER ERROR:* ```" + JSON.parse(JSON.stringify(welp.error)) + "```" + "\n" +
- `> _Cause: ${email} interacted with the form_\n`
- );
-
- return resolve(socket.send(JSON.stringify({
- "html": "You have already subscribed!",
- "message": "updated html",
- "selector": "#emailMessage"
- })));
- }
- });
- });
-}
-
-function updateGithubFeed() {
- octokit.activity.getEventsForOrg({
- org: "lbryio",
- per_page: 20,
- page: 1
- }).then(({ data }) => {
- async.eachSeries(data, (item, callback) => {
- const eventString = JSON.stringify(item);
-
- client.zrank("events", eventString, (err, reply) => {
- if (reply === null) client.zadd("events", item.id, eventString, callback);
- else callback();
- });
- }, () => client.zremrangebyrank("events", 0, -51)); // Keep the latest 50 events
- }).catch(err => {
- logSlackError(
- "\n" +
- "> *GITHUB FEED ERROR:* ```" + JSON.parse(JSON.stringify(err)) + "```" + "\n" +
- "> _Cause: GitHub feed refresh_\n"
- );
- });
-}
-
-function validateEmail(email) {
- const re = /^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\\.,;:\s@"]{2,})$/i;
- return re.test(String(email));
-}