move to raw html, make readme use buns html rewrite and always set to
All checks were successful
Code quality checks / biome (push) Successful in 8s
All checks were successful
Code quality checks / biome (push) Successful in 8s
lazy image load
This commit is contained in:
parent
10416dbff0
commit
2ee5f0512e
7 changed files with 33 additions and 62 deletions
|
@ -1,6 +1,6 @@
|
|||
# Discord Profile Page
|
||||
|
||||
A cool little web app that shows your Discord profile, current activity, and more. Built with Bun and EJS.
|
||||
A cool little web app that shows your Discord profile, current activity, and more. Built with Bun.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
"devDependencies": {
|
||||
"@biomejs/biome": "^1.9.4",
|
||||
"@types/bun": "^1.2.8",
|
||||
"@types/ejs": "^3.1.5",
|
||||
"globals": "^16.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
@ -20,9 +19,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@creations.works/logger": "^1.0.3",
|
||||
"ejs": "^3.1.10",
|
||||
"isomorphic-dompurify": "^2.23.0",
|
||||
"linkedom": "^0.18.9",
|
||||
"marked": "^15.0.7"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
import { resolve } from "node:path";
|
||||
import { renderFile } from "ejs";
|
||||
|
||||
export async function renderEjsTemplate(
|
||||
viewName: string | string[],
|
||||
data: EjsTemplateData,
|
||||
headers?: Record<string, string | number | boolean>,
|
||||
): Promise<Response> {
|
||||
let templatePath: string;
|
||||
|
||||
if (Array.isArray(viewName)) {
|
||||
templatePath = resolve("src", "views", ...viewName);
|
||||
} else {
|
||||
templatePath = resolve("src", "views", viewName);
|
||||
}
|
||||
|
||||
if (!templatePath.endsWith(".ejs")) {
|
||||
templatePath += ".ejs";
|
||||
}
|
||||
|
||||
const html: string = await renderFile(templatePath, data);
|
||||
|
||||
return new Response(html, {
|
||||
headers: { "Content-Type": "text/html", ...headers },
|
||||
});
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import { resolve } from "node:path";
|
||||
import { badgeApi, lanyardConfig } from "@config/environment";
|
||||
import { renderEjsTemplate } from "@helpers/ejs";
|
||||
import { file } from "bun";
|
||||
|
||||
const routeDef: RouteDef = {
|
||||
method: "GET",
|
||||
|
@ -13,17 +14,24 @@ async function handler(request: ExtendedRequest): Promise<Response> {
|
|||
.replace(/^https?:\/\//, "")
|
||||
.replace(/\/$/, "");
|
||||
|
||||
const ejsTemplateData: EjsTemplateData = {
|
||||
title: "Discord Profile",
|
||||
username: "",
|
||||
user: { id: id || lanyardConfig.userId },
|
||||
instance: instance,
|
||||
badgeApi: badgeApi,
|
||||
avatar: `https://cdn.discordapp.com/embed/avatars/${Math.floor(Math.random() * 5)}.png`,
|
||||
extraOptions: {},
|
||||
};
|
||||
const path = resolve("src", "views", "index.html");
|
||||
const bunFile = file(path);
|
||||
|
||||
return await renderEjsTemplate("index", ejsTemplateData);
|
||||
const html = new HTMLRewriter()
|
||||
.on("head", {
|
||||
element(head) {
|
||||
head.setAttribute("data-user-id", id || lanyardConfig.userId);
|
||||
head.setAttribute("data-instance-uri", instance);
|
||||
head.setAttribute("data-badge-url", badgeApi || "");
|
||||
},
|
||||
})
|
||||
.transform(await bunFile.text());
|
||||
|
||||
return new Response(html, {
|
||||
headers: {
|
||||
"Content-Type": "text/html",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export { handler, routeDef };
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { redisTtl } from "@config/environment";
|
||||
import { fetch } from "bun";
|
||||
import { redis } from "bun";
|
||||
import DOMPurify from "isomorphic-dompurify";
|
||||
import { parseHTML } from "linkedom";
|
||||
import { marked } from "marked";
|
||||
|
||||
const routeDef: RouteDef = {
|
||||
|
@ -12,6 +10,16 @@ const routeDef: RouteDef = {
|
|||
log: false,
|
||||
};
|
||||
|
||||
async function addLazyLoading(html: string): Promise<string> {
|
||||
return new HTMLRewriter()
|
||||
.on("img", {
|
||||
element(el) {
|
||||
el.setAttribute("loading", "lazy");
|
||||
},
|
||||
})
|
||||
.transform(html);
|
||||
}
|
||||
|
||||
async function fetchAndCacheReadme(url: string): Promise<string | null> {
|
||||
const cacheKey = `readme:${url}`;
|
||||
const cached = await redis.get(cacheKey);
|
||||
|
@ -33,22 +41,9 @@ async function fetchAndCacheReadme(url: string): Promise<string | null> {
|
|||
const text = await res.text();
|
||||
if (!text || text.length < 10) return null;
|
||||
|
||||
let html: string;
|
||||
if (/\.(html?|htm)$/i.test(url)) {
|
||||
html = text;
|
||||
} else {
|
||||
html = await marked.parse(text);
|
||||
}
|
||||
const html = /\.(html?|htm)$/i.test(url) ? text : await marked.parse(text);
|
||||
|
||||
const { document } = parseHTML(html);
|
||||
for (const img of Array.from(document.querySelectorAll("img"))) {
|
||||
if (!img.hasAttribute("loading")) {
|
||||
img.setAttribute("loading", "lazy");
|
||||
}
|
||||
}
|
||||
|
||||
const dirtyHtml = document.toString();
|
||||
const safe = DOMPurify.sanitize(dirtyHtml) || "";
|
||||
const safe = await addLazyLoading(html);
|
||||
|
||||
await redis.set(cacheKey, safe);
|
||||
await redis.expire(cacheKey, redisTtl);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head data-user-id="<%= user.id %>" data-instance-uri="<%= instance %>" data-badge-url="<%= badgeApi %>">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
3
types/ejs.d.ts
vendored
3
types/ejs.d.ts
vendored
|
@ -1,3 +0,0 @@
|
|||
interface EjsTemplateData {
|
||||
[key: string]: string | number | boolean | object | undefined | null;
|
||||
}
|
Loading…
Add table
Reference in a new issue