substr/html.ts
2024-10-22 19:16:58 +02:00

159 lines
4.2 KiB
TypeScript

import { localizeDate } from "./dates.ts";
import Article from "./models/article.ts";
import Profile from "./models/profile.ts";
function htmlLayout(title: string, body: string, profile?: Profile): string {
let feedLinksHtml = "";
if (profile) {
feedLinksHtml =
`<link rel="alternate" type="application/atom+xml" href="/@${profile.username}/articles.atom" title="Articles by ${profile.name}" />`;
}
return `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>${title}</title>
${feedLinksHtml}
<link rel="stylesheet" type="text/css" href="/assets/css/layout.css" />
<link rel="stylesheet" type="text/css" href="/assets/css/themes/default-light.css" />
</head>
<body>
${body}
</body>
</html>
`;
}
export function errorPageHtml(statusCode: number, title: string): string {
const body = `
<main>
<h1>${statusCode} - ${title}</h1>
</main>
`;
return htmlLayout(title, body);
}
export function articleHtml(article: Article, profile: Profile): string {
const publishedAtFormatted = localizeDate(article.publishedAt);
const pageTitle = article.isDraft ? `Draft: ${article.title}` : article.title;
let draftLabel = ``;
if (article.isDraft) {
draftLabel = `<p class="draft-label">Draft version</p>`;
}
const body = `
<main>
<header>
${draftLabel}
<h1>${article.title}</h1>
<div class="meta">
<img class="avatar" src="${profile.picture}" alt="User Avatar" />
<div class="content">
<span class="name"><a href="/@${profile.username}">${profile.name}</a></span>
<span class="date">${publishedAtFormatted}</span>
</div>
</div>
</header>
<article>
${article.html}
<footer>
${openWithNostrAppHtml(article.naddr)}
</footer>
</article>
</main>
`;
return htmlLayout(pageTitle, body, profile);
}
function articleListItemHtml(article: Article): string {
const formattedDate = localizeDate(article.publishedAt);
return `
<div class="item">
<h3><a href="/${article.naddr}">${article.title}</a></h3>
<p class="meta">
${formattedDate}
</p>
</div>
`;
}
export function articleListHtml(articles: Article[]): string {
if (articles.length === 0) return "";
const sortedArticles = articles.sort((a, b) => b.publishedAt - a.publishedAt);
let html = "";
for (const article of sortedArticles) {
html += articleListItemHtml(article);
}
return `
<h2>Articles</h2>
<div class="article-list">
${html}
</div>
`;
}
export function profilePageHtml(profile: Profile, articles: Article[]): string {
const title = `${profile.name} on Nostr`;
const body = `
<main class="profile-page">
<header>
<img class="avatar" src="${profile.picture}" alt="User Avatar" />
<div class="bio">
<h1>${profile.name}</h1>
<p class="about">
${profile.about}
</p>
</div>
</header>
<section>
<dl>
<dt>Public key</dt>
<dd>${profile.npub}</dd>
</dl>
</section>
<section>
${articleListHtml(articles)}
</section>
</main>
`;
return htmlLayout(title, body, profile);
}
function openWithNostrAppHtml(bech32Id: string): string {
let appLinksHtml = "";
const appLinks = [
{ title: "Habla", href: `https://habla.news/a/${bech32Id}` },
{
title: "noStrudel",
href: `https://nostrudel.ninja/#/articles/${bech32Id}`,
},
{ title: "Coracle", href: `https://coracle.social/${bech32Id}` },
];
for (const link of appLinks) {
appLinksHtml += `<a href="${link.href}" target="_blank">${link.title}</a>`;
}
return `
<div class="open-with dropdown">
<button class="dropdown-button">Open with Nostr app</button>
<div class="dropdown-content">
<a href="nostr:${bech32Id}" target="_blank">🔗&nbsp; Nostr Link</a>
<h4 class="title">Apps</h4>
${appLinksHtml}
</div>
</div>
`;
}