Compare commits
1 Commits
master
...
feature/ta
Author | SHA1 | Date | |
---|---|---|---|
1e081c83e5 |
@ -142,6 +142,14 @@ main article footer {
|
||||
margin-top: 5rem;
|
||||
}
|
||||
|
||||
main article footer .actions {
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
main article footer p {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.nip05 .verified,
|
||||
.nip05 .not-verified {
|
||||
margin-left: 0.3rem;
|
||||
|
@ -76,6 +76,17 @@ main.profile-page .pubkey {
|
||||
color: var(--text-color-discreet);
|
||||
}
|
||||
|
||||
main article footer a,
|
||||
main article footer a:visited {
|
||||
text-decoration: none;
|
||||
color: var(--text-color-discreet);
|
||||
}
|
||||
|
||||
main article footer a:hover,
|
||||
main article footer a:active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.nip05 .verified {
|
||||
background-color: #e8e3da;
|
||||
color: #027739;
|
||||
|
@ -19,7 +19,9 @@ const userProfileHandler = async function (ctx: Context) {
|
||||
|
||||
if (profileEvent) {
|
||||
const profile = new Profile(profileEvent, username);
|
||||
const articles = await fetchArticlesByAuthor(pubkey, 210);
|
||||
|
||||
const articles = await fetchArticlesByAuthor(pubkey, 210, ctx.state.tags);
|
||||
|
||||
const html = await profilePageHtml(profile, articles);
|
||||
generateOgProfileImage(profile);
|
||||
|
||||
|
17
html.ts
17
html.ts
@ -38,6 +38,16 @@ export function errorPageHtml(statusCode: number, title: string): string {
|
||||
return htmlLayout({ title, body });
|
||||
}
|
||||
|
||||
function articleTagsHtml(article: Article, profile: Profile): string {
|
||||
if (article.tags.length === 0) return "";
|
||||
|
||||
const tags = article.tags.map((tag) => {
|
||||
return `<a href="/@${profile.username}?tags=${tag}">${tag}</a>`;
|
||||
});
|
||||
|
||||
return `Tags: ${tags.join(", ")}</p>\n`;
|
||||
}
|
||||
|
||||
export async function articleHtml(
|
||||
article: Article,
|
||||
profile: Profile,
|
||||
@ -65,7 +75,12 @@ export async function articleHtml(
|
||||
<article>
|
||||
${await article.buildContentHtml()}
|
||||
<footer>
|
||||
${openWithNostrAppHtml(article.naddr)}
|
||||
<div class="actions">
|
||||
${openWithNostrAppHtml(article.naddr)}
|
||||
</div>
|
||||
<p class="tags">
|
||||
${articleTagsHtml(article, profile)}
|
||||
</p>
|
||||
</footer>
|
||||
</article>
|
||||
</main>
|
||||
|
@ -39,6 +39,13 @@ export default class Article {
|
||||
return tag ? tag[1] : "";
|
||||
}
|
||||
|
||||
get tags(): string[] {
|
||||
return this.event.tags
|
||||
.filter((t) => t[0] === "t")
|
||||
.filter((t) => t[1] !== "")
|
||||
.map((t) => t[1]);
|
||||
}
|
||||
|
||||
get publishedAt(): number {
|
||||
const tag = this.event.tags.find((t) => t[0] === "published_at");
|
||||
return tag ? parseInt(tag[1]) : this.event.created_at;
|
||||
|
18
nostr.ts
18
nostr.ts
@ -15,7 +15,7 @@ const relayPool = new NPool({
|
||||
eventRouter: async (_event) => [],
|
||||
});
|
||||
|
||||
async function fetchWithTimeout(filters: NostrFilter[]) {
|
||||
export async function fetchWithTimeout(filters: NostrFilter[]) {
|
||||
const timeoutPromise = new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error("relay timeout")), config.query_timeout)
|
||||
);
|
||||
@ -50,16 +50,28 @@ export async function fetchReplaceableEvent(
|
||||
}
|
||||
}
|
||||
|
||||
export function createTagList(
|
||||
articles: Article[],
|
||||
): Record<string, number> {
|
||||
return articles.flatMap((a) => a.tags).reduce((acc, tag) => {
|
||||
acc[tag] = (acc[tag] || 0) + 1;
|
||||
return acc;
|
||||
}, {} as Record<string, number>);
|
||||
}
|
||||
|
||||
export async function fetchArticlesByAuthor(
|
||||
pubkey: string,
|
||||
limit: number = 10,
|
||||
tags?: string[],
|
||||
) {
|
||||
const events = await fetchWithTimeout([{
|
||||
const filter = {
|
||||
authors: [pubkey],
|
||||
kinds: [30023],
|
||||
limit: limit,
|
||||
}]) as NostrEvent[];
|
||||
};
|
||||
if (typeof tags !== "undefined") filter["#t"] = tags;
|
||||
|
||||
const events = await fetchWithTimeout([filter]) as NostrEvent[];
|
||||
const articles = events.map((a) => new Article(a));
|
||||
|
||||
return articles
|
||||
|
@ -26,6 +26,10 @@ router.get("/:path", async (ctx) => {
|
||||
} else if (path.startsWith("npub")) {
|
||||
await npubHandler(ctx);
|
||||
} else if (path.startsWith("@") || path.startsWith("~")) {
|
||||
const tags = ctx.request.url.searchParams.get("tags");
|
||||
if (typeof tags === "string" && tags !== "") {
|
||||
ctx.state.tags = tags.split(",");
|
||||
}
|
||||
await userProfileHandler(ctx);
|
||||
} else {
|
||||
notFoundHandler(ctx);
|
||||
|
@ -47,6 +47,16 @@ describe("Article", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("#tags", () => {
|
||||
it("returns a flattened tag list", () => {
|
||||
expect(article.tags).toEqual([
|
||||
"lightning",
|
||||
"lightning network",
|
||||
"howto",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#publishedAt", () => {
|
||||
it("returns the value of the first 'published_at' tag", () => {
|
||||
expect(article.publishedAt).toEqual(1726402055);
|
||||
|
27
tests/nostr_test.ts
Normal file
27
tests/nostr_test.ts
Normal file
@ -0,0 +1,27 @@
|
||||
// import { describe, it } from "@std/testing/bdd";
|
||||
// import { stub } from "@std/testing/mock";
|
||||
// import { expect } from "@std/expect";
|
||||
// import { NostrEvent, NostrFilter } from "@nostrify/nostrify";
|
||||
// import * as nostr from "../nostr.ts";
|
||||
//
|
||||
// async function fetchWithTimeout(filters: NostrFilter[]) {
|
||||
// console.log("================")
|
||||
// const events = [];
|
||||
// const fixtures = [ "article-1.json", "article-deleted.json" ]
|
||||
// for (const filename of fixtures) {
|
||||
// const event = JSON.parse(Deno.readTextFileSync(`tests/fixtures/${filename}`));
|
||||
// events.push(event);
|
||||
// }
|
||||
// return Promise.resolve(events);
|
||||
// }
|
||||
//
|
||||
// describe("Nostr", () => {
|
||||
// describe("#fetchArticlesByAuthor", () => {
|
||||
// it("removes the anchor links for headlines", async () => {
|
||||
// stub(nostr, "fetchArticlesByAuthor");
|
||||
//
|
||||
// const articles = await nostr.fetchArticlesByAuthor("123456abcdef");
|
||||
// expect(articles.length).toEqual(2);
|
||||
// });
|
||||
// });
|
||||
// });
|
Loading…
x
Reference in New Issue
Block a user