completely refactor website generator and add experimental fast nav system

This commit is contained in:
hippoz 2022-05-15 18:45:48 +03:00
parent 4fbdace446
commit 797c47b4a0
18 changed files with 414 additions and 591 deletions

100
build.js
View file

@ -1,100 +0,0 @@
const { promises: fs } = require("fs");
const path = require("path");
const { html: beautifyHtml } = require('js-beautify');
const buildConfig = require("./buildconfig");
// source: https://stackoverflow.com/a/41407246
const escapeCodes = {
reset: "\x1b[0m",
bright: "\x1b[1m",
dim: "\x1b[2m",
underscore: "\x1b[4m",
blink: "\x1b[5m",
reverse: "\x1b[7m",
hidden: "\x1b[8m",
fgBlack: "\x1b[30m",
fgRed: "\x1b[31m",
fgGreen: "\x1b[32m",
fgYellow: "\x1b[33m",
fgBlue: "\x1b[34m",
fgMagenta: "\x1b[35m",
fgCyan: "\x1b[36m",
fgWhite: "\x1b[37m",
bgBlack: "\x1b[40m",
bgRed: "\x1b[41m",
bgGreen: "\x1b[42m",
bgYellow: "\x1b[43m",
bgBlue: "\x1b[44m",
bgMagenta: "\x1b[45m",
bgCyan: "\x1b[46m",
bgWhite: "\x1b[47m",
};
if (!buildConfig.allowSpecialCharacters) {
for (i in escapeCodes) {
escapeCodes[i] = "";
}
}
const specialCharacter = (c) => {
if (buildConfig.allowSpecialCharacters) {
return c;
}
return "";
}
const processPageOutput = async (out) => {
if (typeof out === "function")
out = await out();
if (typeof out !== "string")
throw new Error("got non-string page output (maybe one of the pages isn't exporting a string?)");
if (buildConfig.postProcessing.trimOutput) {
out = out.trim();
}
if (buildConfig.postProcessing.beautifyOutput) {
out = beautifyHtml(out, buildConfig.postProcessing.beautifyOutputOptions);
}
return out;
};
const findAllPages = async (directory) =>
(await fs.readdir(directory))
.filter(
f => f.endsWith(buildConfig.pageExtension)
)
.map(f => directory + "/" + f); // TODO: hack
const buildPage = async (pagePath) => {
const processed = await processPageOutput(require(pagePath));
console.log(`${escapeCodes.fgGreen}${specialCharacter("→")}${escapeCodes.reset} Built ${escapeCodes.fgBlue}${path.parse(path.basename(pagePath, buildConfig.pageExtension)).name}${escapeCodes.reset}`);
return processed
};
const buildAllPages = async (directory) =>
await Promise.all((await findAllPages(directory))
.map(
async p => [p, (await buildPage(p))]
));
const exportAllPages = async (sourcePath, outputPath) => {
const pages = await buildAllPages(sourcePath);
for (const [pagePath, pageContent] of pages) {
const pageName = path.parse(path.basename(pagePath, buildConfig.pageExtension)).name;
await fs.writeFile(path.join(outputPath, pageName + ".html"), pageContent);
}
return pages.length;
};
const main = async () => {
const startTime = new Date();
const builtPages = await exportAllPages(buildConfig.sourceDirectory, buildConfig.outputDirectory);
console.log(`${escapeCodes.fgGreen}${specialCharacter("✓")} Done! ${escapeCodes.fgBlue}Built ${builtPages} pages in ${((new Date()) - startTime)}ms.${escapeCodes.reset}`);
};
main();

View file

@ -1,14 +0,0 @@
module.exports = {
sourceDirectory: "./src",
outputDirectory: "./out",
pageExtension: ".page.js",
postProcessing: {
trimOutput: false,
beautifyOutput: true,
beautifyOutputOptions: {
indent_size: 2,
preserve_newlines: false
}
},
allowSpecialCharacters: true
};

View file

@ -1,105 +0,0 @@
window.modules.register("GatewayClient", () => {
const messageSchema = { t: "number", d: "object" };
const messageTypes = {
HELLO: 0,
YOO: 1,
READY: 2,
EVENT: 3
};
class GatewayClient {
constructor(gatewayPath) {
this.gatewayPath = gatewayPath;
this.ws = null;
this.token = null;
this.user = null;
this.onEvent = (e) => {};
this.onConnected = () => {};
this.onDisconnected = () => {};
}
connect(token) {
if (!token)
token = this.token;
console.log("gateway: connecting");
this.ws = new WebSocket(this.gatewayPath);
this.ws.addEventListener("message", ({ data }) => {
if (typeof data !== "string") {
console.warn("gateway: got non-string data from server, ignoring...");
return;
}
let message;
try {
message = JSON.parse(data);
} catch(e) {
console.warn("gateway: got invalid JSON from server (failed to parse), ignoring...");
return;
}
if (!this._checkMessageSchema(message)) {
console.warn("gateway: got invalid JSON from server (does not match schema), ignoring...");
return;
}
switch (message.t) {
case messageTypes.HELLO: {
console.log("gateway: HELLO");
this.ws.send(JSON.stringify({
t: messageTypes.YOO,
d: {
token
}
}));
break;
}
case messageTypes.READY: {
console.log("gateway: READY");
this.user = message.d.user;
this.onConnected();
break;
}
case messageTypes.EVENT: {
this.onEvent(message.d);
break;
}
default: {
console.warn("gateway: got invalid JSON from server (invalid type), ignoring...");
return;
}
}
});
this.ws.addEventListener("open", () => {
console.log("gateway: open");
});
this.ws.addEventListener("close", ({ code }) => {
console.log("gateway: closed");
this.onDisconnected(code);
if (code === 4001) {
console.log(`gateway: disconnect code is ${code} (bad auth), will not attempt reconnect`);
return;
}
setTimeout(() => {
console.log("gateway: reconnecting");
this.connect(token);
}, 4000);
});
}
_checkMessageSchema(message) {
for (const [key, value] of Object.entries(message)) {
if (!messageSchema[key])
return false;
if (typeof value !== messageSchema[key])
return false;
}
return true;
}
}
return GatewayClient;
});

View file

@ -1,21 +0,0 @@
window.modules.register("$app", () => {
const App = window.modules.require("App");
if (!window._APP_ENV)
throw new Error("$app: could not find window._APP_ENV");
if (!App)
throw new Error("$app: require('App') returned undefined");
const initialLoading = document.getElementById("initial-loading");
if (initialLoading) {
initialLoading.parentElement.removeChild(initialLoading);
}
const appMountElement = document.createElement("div");
document.body.appendChild(appMountElement);
const app = new App(appMountElement);
app.mount();
return app;
});

View file

@ -1,185 +0,0 @@
window.modules.register("AuthPromptRoute", () => {
class AuthPromptRoute {
constructor() {
this.element = null;
}
mountOn(target) {
if (this.element)
return; // Already mounted
this.element = document.createRange().createContextualFragment(`
<div>
<input type="password" id="code-input">
<button id="continue-button">enter</button>
</div>
`).children[0];
this.element.querySelector("#continue-button").addEventListener("click", () => {
if (this.onPasswordSubmitted)
this.onPasswordSubmitted(this.element.querySelector("#code-input").value);
});
target.appendChild(this.element);
}
unmount() {
if (!this.element)
return; // Already unmounted
this.element.parentElement.removeChild(this.element);
this.element = null;
}
}
return AuthPromptRoute;
});
window.modules.register("MainChatView", () => {
class MainChatView {
constructor() {
this.element = null;
}
mountOn(target) {
if (this.element)
return; // Already mounted
this.element = document.createRange().createContextualFragment(`
<div>
<div id="messages-container" style="width: 300px; height: 250px; overflow: auto;"></div>
<br>
<input type="text" id="message-input">
<button id="message-submit">send</button>
</div>
`).children[0];
const textInput = this.element.querySelector("#message-input");
this.element.querySelector("#message-submit").addEventListener("click", () => {
const message = textInput.value;
if (this.onSendMessage)
this.onSendMessage(message);
textInput.value = "";
});
target.appendChild(this.element);
}
appendMessage(messageObject) {
const { author, content } = messageObject;
if (!this.element)
return;
const usernameLetters = author.username
.split("");
let authorUsernameNumber = 150;
usernameLetters.forEach(l => {
authorUsernameNumber += l.charCodeAt(0);
});
const messageElement = document.createRange().createContextualFragment(`
<div>
<b>[author could not be loaded] </b>
<span>[content could not be loaded]</span>
</div>
`).children[0];
messageElement.querySelector("b").innerText = `User ${authorUsernameNumber} `;
messageElement.querySelector("span").innerText = content;
const container = this.element.querySelector("#messages-container");
container.appendChild(messageElement);
container.scrollTop = container.scrollHeight;
}
unmount() {
if (!this.element)
return; // Already unmounted
this.element.parentElement.removeChild(this.element);
this.element = null;
}
}
return MainChatView;
});
window.modules.register("App", () => {
const AuthPromptRoute = window.modules.require("AuthPromptRoute");
const MainChatView = window.modules.require("MainChatView");
const GatewayClient = window.modules.require("GatewayClient");
class App {
constructor(mountElement) {
this.mountElement = mountElement;
this.currentMountedElement = null;
this.authPromptRoute = new AuthPromptRoute();
this.mainChatView = new MainChatView();
this.gatewayClient = new GatewayClient(window._APP_ENV.gatewayBase);
}
_mountElement(element) {
if (this.currentMountedElement) {
this.currentMountedElement.unmount();
}
this.currentMountedElement = element;
this.currentMountedElement.mountOn(this.mountElement);
}
_unmountAll() {
if (this.currentMountedElement) {
this.currentMountedElement.unmount();
}
}
mount() {
let serverId;
let channelId;
let token;
this._mountElement(this.authPromptRoute);
this.gatewayClient.onEvent = (e) => {
if (e.eventType === "MESSAGE_CREATE" && e.message && e.message.guild_id === serverId && e.message.channel_id === channelId) {
this.mainChatView.appendMessage(e.message);
}
};
this.gatewayClient.onConnected = () => {
this._mountElement(this.mainChatView);
};
this.gatewayClient.onDisconnected = () => {
this._mountElement(this.authPromptRoute);
};
this.authPromptRoute.onPasswordSubmitted = (auth) => {
const parts = auth.split(",,");
if (parts.length !== 3)
return;
const [userToken, userServerId, userChannelId] = parts;
serverId = userServerId;
channelId = userChannelId;
token = userToken
this.gatewayClient.connect(token);
};
this.mainChatView.onSendMessage = async (message) => {
if (typeof message === "string" && message.trim() === "")
return;
await fetch(`${window._APP_ENV.apiBase}/guilds/${serverId}/channels/${channelId}/messages/create`, {
method: "POST",
body: JSON.stringify({
content: message
}),
headers: {
"content-type": "application/json",
"authorization": token
}
});
};
}
}
return App;
});

View file

@ -1,23 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>.</title>
<script>
window._APP_ENV = {
gatewayBase: "wss://kittycatnetwork.hippoz.xyz/services/thebridge/gateway/",
apiBase: "https://kittycatnetwork.hippoz.xyz/services/thebridge/api/v1"
};
</script>
</head>
<body>
<p id="initial-loading">loading</p>
<script src="moduleruntime.js"></script>
<script src="GatewayClient.js"></script>
<script src="components.js"></script>
<script src="app.js"></script>
</body>
</html>

View file

@ -1,26 +0,0 @@
const modules = {
_cache: {},
_registry: {},
require(moduleName) {
if (this._cache[moduleName]) {
return this._cache[moduleName];
}
if (this._registry[moduleName]) {
const loaderFunction = this._registry[moduleName];
this._cache[moduleName] = loaderFunction(1);
return this._cache[moduleName];
}
return null;
},
register(moduleName, loaderFunction) {
this._registry[moduleName] = loaderFunction;
this._cache[moduleName] = loaderFunction(0);
},
registerLazy(moduleName, loaderFunction) {
this._registry[moduleName] = loaderFunction;
}
};
window.modules = modules;

26
out/res/fastnav.js Normal file
View file

@ -0,0 +1,26 @@
const map = {"projects.html":"<div class=\"card inner-card\"><div><b>📘 waffle</b><a class=\"button-default float-right\" href=https://git.hippoz.xyz/hippoz/waffle>view >></a></div><p class=\"grayed-out\">Mysterious! This project has no description.</p></div><div class=\"card inner-card\"><div><b>📘 brainlet-react</b><a class=\"button-default float-right\" href=https://git.hippoz.xyz/hippoz/brainlet-react>view >></a></div><p>a react frontend for waffle (brainlet)</p></div><div class=\"card inner-card\"><div><b>📘 raven</b><a class=\"button-default float-right\" href=https://git.hippoz.xyz/hippoz/raven>view >></a></div><p>Simple experimental low-level UI library written in C++.</p></div><div class=\"card inner-card\"><div><b>📘 bridgecord</b><a class=\"button-default float-right\" href=https://git.hippoz.xyz/hippoz/bridgecord>view >></a></div><p>bridge bot api for discord servers</p></div><div class=\"card inner-card\"><div><b>📘 rice</b><a class=\"button-default float-right\" href=https://git.hippoz.xyz/hippoz/rice>view >></a></div><p>my personal forks of various software and my dotfiles</p></div><div class=\"card inner-card\"><div><b>📘 hippOS</b><a class=\"button-default float-right\" href=https://git.hippoz.xyz/hippoz/hippOS>view >></a></div><p>simple osdev testing</p></div><div class=\"card inner-card\"><div><b>📘 homepage</b><a class=\"button-default float-right\" href=https://git.hippoz.xyz/hippoz/homepage>view >></a></div><p class=\"grayed-out\">Mysterious! This project has no description.</p></div><div class=\"card inner-card\"><div><b>📘 brainlet</b><a class=\"button-default float-right\" href=https://git.hippoz.xyz/hippoz/brainlet>view >></a></div><p>🎤🐢 An open source, self-hosted alternative to Discord</p></div><div class=\"card inner-card\"><div><b>📘 portal</b><a class=\"button-default float-right\" href=https://git.hippoz.xyz/hippoz/portal>view >></a></div><p>waffletv's interface app</p></div><div class=\"card inner-card\"><div><b>📘 capybara</b><a class=\"button-default float-right\" href=https://git.hippoz.xyz/hippoz/capybara>view >></a></div><p>simple and fast remote touchpad</p></div><div class=\"card inner-card\"><div><b>📘 rotcpu</b><a class=\"button-default float-right\" href=https://git.hippoz.xyz/hippoz/rotcpu>view >></a></div><p class=\"grayed-out\">Mysterious! This project has no description.</p></div><div class=\"card inner-card\"><div><b>📘 quad-j</b><a class=\"button-default float-right\" href=https://git.hippoz.xyz/hippoz/quad-j>view >></a></div><p>a simple upload server</p></div><div class=\"card inner-card\"><div><b>📘 blocklists</b><a class=\"button-default float-right\" href=https://git.hippoz.xyz/hippoz/blocklists>view >></a></div><p>Some pi-hole blocklists</p></div><div class=\"card inner-card\"><div><b>📘 wormhole</b><a class=\"button-default float-right\" href=https://git.hippoz.xyz/hippoz/wormhole>view >></a></div><p>A simple websocket abstraction.</p></div><div class=\"card inner-card\"><div><b>📘 mcli</b><a class=\"button-default float-right\" href=https://git.hippoz.xyz/hippoz/mcli>view >></a></div><p>a command-line minecraft launcher written in rust</p></div><div class=\"card inner-card\"><div><b>📘 must</b><a class=\"button-default float-right\" href=https://git.hippoz.xyz/hippoz/must>view >></a></div><p>Helper program for deploying protocol associations needed for running Roblox on Linux</p></div><div class=\"card inner-card\"><div><b>🍴 brainlet-client</b><a class=\"button-default float-right\" href=https://git.hippoz.xyz/hippoz/brainlet-client>view >></a></div><p class=\"grayed-out\">Mysterious! This project has no description.</p></div><div class=\"card inner-card\"><div><b>📘 luapage</b><a class=\"button-default float-right\" href=https://git.hippoz.xyz/hippoz/luapage>view >></a></div><p class=\"grayed-out\">Mysterious! This project has no description.</p></div><div class=\"card inner-card\"><div><b>📘 page-simplify</b><a class=\"button-default float-right\" href=https://git.hippoz.xyz/hippoz/page-simplify>view >></a></div><p>turn pages into pure html</p></div>","index.html":"<h2>hippoz's website</h2><p>i think</p>"};
const content = document.getElementById("content");
const load = (page) => content.innerHTML = map[page.substring(1, page.length) || "_notfound"];
const navigate = (page) => {
history.pushState(null, "", page);
load(page);
};
const populate = () => {
const elements = document.querySelectorAll("[href]");
elements.forEach(element => {
const existingLink = element.getAttribute("href");
if (map[existingLink]) {
element.setAttribute("href", "#");
element.onclick = () => navigate(existingLink);
}
});
};
const main = () => {
populate();
window.onpopstate = () => {load(location.pathname); console.log("a");};
};
main();

29
src/assets/fastnav.js Normal file
View file

@ -0,0 +1,29 @@
const map = "{{NAV_DATA}}";
const content = document.getElementById("content");
const load = (page) => {
content.innerHTML = map[page.substring(1, page.length) || "_notfound"];
console.log("a");
};
const navigate = (page) => {
history.pushState(null, "", page);
load(page);
};
const populate = () => {
const elements = document.querySelectorAll("[href]");
elements.forEach(element => {
const existingLink = element.getAttribute("href");
if (map[existingLink]) {
element.setAttribute("href", "");
element.onclick = () => navigate(existingLink);
}
});
};
const main = () => {
populate();
window.onpopstate = () => load(location.pathname);
};
main();

View file

@ -1,13 +0,0 @@
const Page = require("./components/Page.component");
module.exports = Page({
title: "hippoz blog",
description: "hippoz blog",
name: "blog",
})(`
<h2>hippoz's blog</h2>
<p>i think</p>
<br>
<br>
<p>no articles yet</p>
`);

View file

@ -1,2 +0,0 @@
module.exports = ({ link, text, selected=false }) =>
`<a href="${link}" class="button-default${selected ? " button-selected" : ""}">${text}</a>`;

View file

@ -1,21 +0,0 @@
const { navigationLinks, navigationBrandingText } = require("../env");
const LinkButtonComponent = require("./LinkButton.component");
module.exports = ({ pageName }) => `
<div class="card layout-card noselect">
<div class="float-left">
<b class="navigation-branding-text">${navigationBrandingText}</b>
</div>
<div class="align-right">
${
navigationLinks.map(
({link, text, name}) =>
LinkButtonComponent({
link,
text,
selected: name === pageName
})
).join("")
}
</div>
</div>`;

View file

@ -1,21 +0,0 @@
const NavigationComponent = require("./Navigation.component");
module.exports = ({ title="page", description="a page", name="page" }) => (content) =>
`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="${description}">
<title>${title}</title>
<link rel="stylesheet" href="res/style.css">
</head>
<body>
${NavigationComponent({
pageName: name
})}
<div class="card layout-card">
${content}
</div>
</body>
</html>`;

25
src/env/index.js vendored
View file

@ -1,25 +0,0 @@
const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args));
module.exports = {
navigationLinks: [
{link: "index.html", text: "home", name: "home"},
{link: "blog.html", text: "blog", name: "blog"},
{link: "projects.html", text: "projects", name: "projects"},
{link: "https://git.hippoz.xyz", text: "git", name: "__ext_git"},
],
repositoryList: {
gitTargetUsername: "hippoz",
repositoryFetchUrl: `https://git.hippoz.xyz/api/v1/users/hippoz/repos?page=1&limit=100`,
_cache: null,
fetchProjects: async function() {
if (this._cache) return this._cache;
const response = await fetch(this.repositoryFetchUrl);
const json = await response.json();
this._cache = json;
return json;
}
},
navigationBrandingText: "hippoz."
}

162
src/index.js Normal file
View file

@ -0,0 +1,162 @@
const fs = require("node:fs/promises");
const path = require("node:path");
const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args));
const { html: beautifyHtml } = require('js-beautify');
const data = {
outputDirectory: "./out",
navigationLinks: [
{link: "index.html", text: "home", name: "home"},
{link: "projects.html", text: "projects", name: "projects"},
{link: "https://git.hippoz.xyz", text: "git", name: "__ext_git"},
],
postProcessing: {
beautifyOutput: true,
beautifyOutputOptions: {
indent_size: 2,
preserve_newlines: false
},
minifyOutputOptions: {
indent_size: 0,
preserve_newlines: false,
end_with_newline: false,
eol: ""
}
},
repositoryList: {
gitTargetUsername: "hippoz",
repositoryFetchUrl: `https://git.hippoz.xyz/api/v1/users/hippoz/repos?page=1&limit=100`,
_cache: null,
fetchProjects: async function() {
if (this._cache) return this._cache;
const response = await fetch(this.repositoryFetchUrl);
const json = await response.json();
this._cache = json;
return json;
}
},
navigationBrandingText: "hippoz."
};
const linkButton = ({ link, text, selected=false }) => `
<a href="${link}" class="button-default${selected ? " button-selected" : ""}">${text}</a>
`;
const navigation = ({currentName}) => `
<div class="card layout-card noselect">
<div class="float-left">
<b class="navigation-branding-text">${data.navigationBrandingText}</b>
</div>
<div class="align-right">
${data.navigationLinks.map(e => linkButton({ ...e, selected: currentName === e.name })).join("")}
</div>
</div>
`;
const makePage = ({ name, description, title }) => content => [`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="${description}">
<title>${title}</title>
<link rel="stylesheet" href="res/style.css">
</head>
<body>
${navigation({currentName: name})}
<section class="card layout-card" id="content">
${content}
</section>
<script src="res/fastnav.js"></script>
</body>
</html>
`, content];
const indexPage = () => makePage({
title: "hippoz",
description: "hippoz website homepage",
name: "home",
})(`
<h2>hippoz's website</h2>
<p>i think</p>
`);
const projectsPage = async () => makePage({
title: "hippoz",
description: "hippoz projects",
name: "projects",
})(`
${
(await data.repositoryList.fetchProjects())
.sort((a, b) => b.size - a.size) // biggest projects first
.filter(a => !a.archived)
.map(
repo =>`
<div class="card inner-card">
<div>
<b>${repo.fork ? "🍴" : "📘"} ${repo.name}</b>
<a class="button-default float-right" href=${repo.html_url}>view >></a>
</div>
${repo.description ? `<p>${repo.description}</p>` : `<p class="grayed-out">Mysterious! This project has no description.</p>`}
</div>`
)
.join("\n")
}
`);
const pages = {
"projects.html": projectsPage,
"index.html": indexPage
};
let renderedPageCache = null;
const renderPages = async () => {
if (renderedPageCache) return renderedPageCache;
renderedPageCache = {};
for (const [pageName, builder] of Object.entries(pages)) {
renderedPageCache[pageName] = await builder();
}
return renderedPageCache;
};
const deployNavScript = async () => {
let script = await fs.readFile("./src/assets/fastnav.js");
if (script) script = script.toString();
const renderedPages = await renderPages();
let navMap = {};
for (let [pageName, content] of Object.entries(renderedPages)) {
if (data.postProcessing.beautifyOutput) {
content = beautifyHtml(content[1], data.postProcessing.minifyOutputOptions);
}
navMap[pageName] = content;
}
const content = script.replace(`"{{NAV_DATA}}"`, JSON.stringify(navMap));
await fs.writeFile("./out/res/fastnav.js", content);
};
const deployPages = async () => {
const renderedPages = await renderPages();
for (let [pageName, content] of Object.entries(renderedPages)) {
if (data.postProcessing.beautifyOutput) {
content = beautifyHtml(content[0], data.postProcessing.beautifyOutputOptions);
}
await fs.writeFile(path.join(data.outputDirectory, pageName), content);
}
};
const deployAll = async () => {
await deployNavScript();
await deployPages();
};
deployAll();

View file

@ -1,10 +0,0 @@
const Page = require("./components/Page.component");
module.exports = Page({
title: "hippoz",
description: "hippoz website homepage",
name: "home",
})(`
<h2>hippoz's website</h2>
<p>i think</p>
`);

View file

@ -1,25 +0,0 @@
const { repositoryList } = require("./env");
const Page = require("./components/Page.component");
module.exports = async () => Page({
title: "hippoz",
description: "hippoz website homepage",
name: "projects",
})(`
${
(await repositoryList.fetchProjects())
.sort((a, b) => b.size - a.size) // biggest projects first
.filter(a => !a.archived)
.map(
repo =>`
<div class="card inner-card">
<div>
<b>${repo.fork ? "🍴" : "📘"} ${repo.name}</b>
<a class="button-default float-right" href=${repo.html_url}>view >></a>
</div>
${repo.description ? `<p>${repo.description}</p>` : `<p class="grayed-out">Mysterious! This project has no description.</p>`}
</div>`
)
.join("\n")
}
`);

197
yarn.lock Normal file
View file

@ -0,0 +1,197 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
abbrev@1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
balanced-match@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
dependencies:
balanced-match "^1.0.0"
concat-map "0.0.1"
commander@^2.19.0:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
config-chain@^1.1.13:
version "1.1.13"
resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4"
integrity sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==
dependencies:
ini "^1.3.4"
proto-list "~1.2.1"
data-uri-to-buffer@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz#b5db46aea50f6176428ac05b73be39a57701a64b"
integrity sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==
editorconfig@^0.15.3:
version "0.15.3"
resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-0.15.3.tgz#bef84c4e75fb8dcb0ce5cee8efd51c15999befc5"
integrity sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==
dependencies:
commander "^2.19.0"
lru-cache "^4.1.5"
semver "^5.6.0"
sigmund "^1.0.1"
fetch-blob@^3.1.2, fetch-blob@^3.1.4:
version "3.1.5"
resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.1.5.tgz#0077bf5f3fcdbd9d75a0b5362f77dbb743489863"
integrity sha512-N64ZpKqoLejlrwkIAnb9iLSA3Vx/kjgzpcDhygcqJ2KKjky8nCgUQ+dzXtbrLaWZGZNmNfQTsiQ0weZ1svglHg==
dependencies:
node-domexception "^1.0.0"
web-streams-polyfill "^3.0.3"
formdata-polyfill@^4.0.10:
version "4.0.10"
resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423"
integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==
dependencies:
fetch-blob "^3.1.2"
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
glob@^7.1.3:
version "7.2.2"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.2.tgz#29deb38e1ef90f132d5958abe9c3ee8e87f3c318"
integrity sha512-NzDgHDiJwKYByLrL5lONmQFpK/2G78SMMfo+E9CuGlX4IkvfKDsiQSNPwAYxEy+e6p7ZQ3uslSLlwlJcqezBmQ==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.1.1"
once "^1.3.0"
path-is-absolute "^1.0.0"
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
dependencies:
once "^1.3.0"
wrappy "1"
inherits@2:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
ini@^1.3.4:
version "1.3.8"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
js-beautify@^1.14.0:
version "1.14.3"
resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.14.3.tgz#3dd11c949178de7f3bdf3f6f752778d3bed95150"
integrity sha512-f1ra8PHtOEu/70EBnmiUlV8nJePS58y9qKjl4JHfYWlFH6bo7ogZBz//FAZp7jDuXtYnGYKymZPlrg2I/9Zo4g==
dependencies:
config-chain "^1.1.13"
editorconfig "^0.15.3"
glob "^7.1.3"
nopt "^5.0.0"
lru-cache@^4.1.5:
version "4.1.5"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==
dependencies:
pseudomap "^1.0.2"
yallist "^2.1.2"
minimatch@^3.1.1:
version "3.1.2"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
dependencies:
brace-expansion "^1.1.7"
node-domexception@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5"
integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==
node-fetch@^3.0.0:
version "3.2.4"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.4.tgz#3fbca2d8838111048232de54cb532bd3cf134947"
integrity sha512-WvYJRN7mMyOLurFR2YpysQGuwYrJN+qrrpHjJDuKMcSPdfFccRUla/kng2mz6HWSBxJcqPbvatS6Gb4RhOzCJw==
dependencies:
data-uri-to-buffer "^4.0.0"
fetch-blob "^3.1.4"
formdata-polyfill "^4.0.10"
nopt@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88"
integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==
dependencies:
abbrev "1"
once@^1.3.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
dependencies:
wrappy "1"
path-is-absolute@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
proto-list@~1.2.1:
version "1.2.4"
resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=
pseudomap@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM=
semver@^5.6.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
sigmund@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590"
integrity sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=
web-streams-polyfill@^3.0.3:
version "3.2.1"
resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6"
integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
yallist@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=