Add dropdown menu for opening article in other apps
This commit is contained in:
parent
ffd709d2f9
commit
96254b38be
@ -52,7 +52,7 @@ pre code {
|
|||||||
main {
|
main {
|
||||||
display: block;
|
display: block;
|
||||||
max-width: 728px;
|
max-width: 728px;
|
||||||
margin: 12rem auto;
|
margin: 12rem auto 24rem auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
main header {
|
main header {
|
||||||
@ -64,26 +64,65 @@ main header h1 {
|
|||||||
margin-bottom: 1.6rem;
|
margin-bottom: 1.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
p.meta {
|
main header .meta {
|
||||||
display: flex;
|
display: flex;
|
||||||
column-gap: 1rem;
|
column-gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
p.meta .content {
|
main header .meta .content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
line-height: 1.6rem;
|
line-height: 1.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
p.meta .name a {
|
main header .meta .name a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.article-list .item {
|
main .article-list .item {
|
||||||
margin-bottom: 3rem;
|
margin-bottom: 3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.article-list .item h3 {
|
main .article-list .item h3 {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
main article footer {
|
||||||
|
margin-top: 5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dropdown menu */
|
||||||
|
|
||||||
|
.dropdown {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-button {
|
||||||
|
font-family: "Merriweather", serif;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
padding: 0.6rem 1rem;
|
||||||
|
border: 1px solid;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-content {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
min-width: 160px;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-content a {
|
||||||
|
padding: 0.6rem 1rem;
|
||||||
|
text-decoration: none;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown:hover .dropdown-content {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
@ -25,10 +25,35 @@ pre code {
|
|||||||
color: #ccc;
|
color: #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
p.meta .date {
|
main header .meta .date {
|
||||||
color: #888;
|
color: #888;
|
||||||
}
|
}
|
||||||
|
|
||||||
p.meta .name a {
|
main header .meta .name a {
|
||||||
color: #3b3a38;
|
color: #3b3a38;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Dropdown menu */
|
||||||
|
|
||||||
|
.dropdown-button {
|
||||||
|
background-color: #f5f2eb;
|
||||||
|
color: #3b3a38;
|
||||||
|
border-color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-content {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-content a {
|
||||||
|
color: #3b3a38 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-content a:hover {
|
||||||
|
background-color: #f1f1f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown:hover .dropdown-button {
|
||||||
|
background-color: #fff;
|
||||||
|
border-color: #fff;
|
||||||
|
}
|
||||||
|
51
html.ts
51
html.ts
@ -2,7 +2,7 @@ import { localizeDate } from "./dates.ts";
|
|||||||
import Article from "./models/article.ts";
|
import Article from "./models/article.ts";
|
||||||
import Profile from "./models/profile.ts";
|
import Profile from "./models/profile.ts";
|
||||||
|
|
||||||
export function htmlLayout(title: string, body: string, profile: Profile) {
|
function htmlLayout(title: string, body: string, profile: Profile): string {
|
||||||
return `
|
return `
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
@ -22,29 +22,34 @@ export function htmlLayout(title: string, body: string, profile: Profile) {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function articleHtml(article: Article, profile: Profile) {
|
export function articleHtml(article: Article, profile: Profile): string {
|
||||||
const publishedAtFormatted = localizeDate(article.publishedAt);
|
const publishedAtFormatted = localizeDate(article.publishedAt);
|
||||||
|
|
||||||
const body = `
|
const body = `
|
||||||
<main>
|
<main>
|
||||||
<header>
|
<header>
|
||||||
<h1>${article.title}</h1>
|
<h1>${article.title}</h1>
|
||||||
<p class="meta">
|
<div class="meta">
|
||||||
<img class="avatar" src="${profile.picture}" alt="User Avatar" />
|
<img class="avatar" src="${profile.picture}" alt="User Avatar" />
|
||||||
<span class="content">
|
<div class="content">
|
||||||
<span class="name"><a href="/@${profile.username}">${profile.name}</a></span>
|
<span class="name"><a href="/@${profile.username}">${profile.name}</a></span>
|
||||||
<span class="date">${publishedAtFormatted}</span>
|
<span class="date">${publishedAtFormatted}</span>
|
||||||
</span>
|
</div>
|
||||||
</p>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
${article.html}
|
<article>
|
||||||
|
${article.html}
|
||||||
|
<footer>
|
||||||
|
${openWithNostrAppHtml(article.naddr)}
|
||||||
|
</footer>
|
||||||
|
</article>
|
||||||
</main>
|
</main>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
return htmlLayout(article.title, body, profile);
|
return htmlLayout(article.title, body, profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
function articleListItemHtml(article: Article) {
|
function articleListItemHtml(article: Article): string {
|
||||||
const formattedDate = localizeDate(article.publishedAt);
|
const formattedDate = localizeDate(article.publishedAt);
|
||||||
|
|
||||||
return `
|
return `
|
||||||
@ -55,7 +60,7 @@ function articleListItemHtml(article: Article) {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function articleListHtml(articles: Article[]) {
|
export function articleListHtml(articles: Article[]): string {
|
||||||
if (articles.length === 0) return "";
|
if (articles.length === 0) return "";
|
||||||
let html = "";
|
let html = "";
|
||||||
|
|
||||||
@ -71,7 +76,7 @@ export function articleListHtml(articles: Article[]) {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function profilePageHtml(profile: Profile, articles: Article[]) {
|
export function profilePageHtml(profile: Profile, articles: Article[]): string {
|
||||||
const title = `${profile.name} on Nostr`;
|
const title = `${profile.name} on Nostr`;
|
||||||
|
|
||||||
const body = `
|
const body = `
|
||||||
@ -91,3 +96,29 @@ export function profilePageHtml(profile: Profile, articles: Article[]) {
|
|||||||
|
|
||||||
return htmlLayout(title, body, profile);
|
return htmlLayout(title, body, profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openWithNostrAppHtml(bech32Id): string {
|
||||||
|
let linksHtml = "";
|
||||||
|
const links = [
|
||||||
|
{ title: "Nostr Link", href: `nostr:${bech32Id}` },
|
||||||
|
{ 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 links) {
|
||||||
|
linksHtml += `<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">
|
||||||
|
${linksHtml}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
@ -16,7 +16,7 @@ export default class Article {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get url(): string {
|
get url(): string {
|
||||||
return `${config.base_url}/${this.naddr}`
|
return `${config.base_url}/${this.naddr}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
get title(): string {
|
get title(): string {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user