Fix top reads

This commit is contained in:
Bojan Mojsilovic 2024-05-31 14:59:19 +02:00
parent b1ad4299eb
commit b4b51a242d
6 changed files with 319 additions and 35 deletions

View File

@ -1,4 +1,4 @@
.article {
.article, .articleShort {
position: relative;
display: flex;
flex-direction: column;
@ -44,6 +44,10 @@
font-size: 14px;
font-weight: 400;
line-height: 14px;
&::before {
content: '';
}
}
}
@ -122,3 +126,41 @@
top: -6px;
right: 8px;
}
.articleShort {
padding: 0;
.header {
.userInfo {
.userName {
font-size: 15px;
font-weight: 700;
line-height: normal;
}
}
.time {
font-size: 15px;
font-weight: 400;
line-height: normal;
}
}
.body {
.text {
.content {
.title {
font-size: 16px;
font-weight: 700;
line-height: 24px;
}
}
}
.image {
min-width: 100px;
img {
width: 100px;
object-fit: scale-down;
}
}
}
}

View File

@ -0,0 +1,59 @@
import { A } from '@solidjs/router';
import { batch, Component, createEffect, For, JSXElement, Show } from 'solid-js';
import { createStore } from 'solid-js/store';
import { Portal } from 'solid-js/web';
import { useAccountContext } from '../../contexts/AccountContext';
import { CustomZapInfo, useAppContext } from '../../contexts/AppContext';
import { useThreadContext } from '../../contexts/ThreadContext';
import { date, shortDate } from '../../lib/dates';
import { hookForDev } from '../../lib/devTools';
import { userName } from '../../stores/profile';
import { PrimalArticle, ZapOption } from '../../types/primal';
import { uuidv4 } from '../../utils';
import Avatar from '../Avatar/Avatar';
import { NoteReactionsState } from '../Note/Note';
import NoteContextTrigger from '../Note/NoteContextTrigger';
import ArticleFooter from '../Note/NoteFooter/ArticleFooter';
import NoteFooter from '../Note/NoteFooter/NoteFooter';
import NoteTopZaps from '../Note/NoteTopZaps';
import NoteTopZapsCompact from '../Note/NoteTopZapsCompact';
import VerificationCheck from '../VerificationCheck/VerificationCheck';
import styles from './ArticlePreview.module.scss';
const ArticlePreview: Component<{
id?: string,
article: PrimalArticle,
}> = (props) => {
return (
<A class={styles.articleShort} href={`/e/${props.article.noteId}`}>
<div class={styles.header}>
<div class={styles.userInfo}>
<Avatar user={props.article.user} size="micro"/>
<div class={styles.userName}>{userName(props.article.user)}</div>
</div>
<div class={styles.time}>
{date(props.article.published).label}
</div>
</div>
<div class={styles.body}>
<div class={styles.text}>
<div class={styles.content}>
<div class={styles.title}>
{props.article.title}
</div>
</div>
</div>
<Show when={props.article.image.length > 0}>
<div class={styles.image}>
<img src={props.article.image} />
</div>
</Show>
</div>
</A>
);
}
export default hookForDev(ArticlePreview);

View File

@ -60,10 +60,17 @@
.headingPicks {
@include heading();
text-transform: capitalize;
height: fit-content;
margin-bottom: 12px;
padding-bottom: 0px;
}
.readsSidebar {
.section {
margin-bottom: 28px;
}
.topic {
display: inline-block;
background-color: var(--background-input);

View File

@ -22,6 +22,7 @@ import { getArticleThread, getReadsTopics } from '../../lib/feed';
import { fetchArticles } from '../../handleNotes';
import { getParametrizedEvent, getParametrizedEvents } from '../../lib/notes';
import { decodeIdentifier } from '../../lib/keys';
import ArticleShort from '../ArticlePreview/ArticleShort';
const sidebarOptions = [
{
@ -121,29 +122,11 @@ const ReadsSidebar: Component< { id?: string } > = (props) => {
randomIndices.add(randomIndex);
}
const reads = [ ...randomIndices ].map(i => rec[i])
getPEvents(reads)
// getRecomendedArticles(reads)
const reads = [ ...randomIndices ].map(i => rec[i]);
getRecomendedArticles(reads)
}
})
const getPEvents = (ids: string[]) => {
const events = ids.reduce<EventCoordinate[]>((acc, id) => {
const d = decodeIdentifier(id);
if (!d.data || d.type !== 'naddr') return acc;
const { pubkey, identifier, kind } = d.data;
return [
...acc,
{ identifier, pubkey, kind },
]
}, []);
getParametrizedEvents(events, `reads_pe_${APP_ID}`);
}
});
const getRecomendedArticles = async (ids: string[]) => {
if (!account?.publicKey) return;
@ -156,8 +139,6 @@ const ReadsSidebar: Component< { id?: string } > = (props) => {
setIsFetching(() => false);
console.log('ARTICLES: ', articles);
setTopPicks(() => [...articles]);
};
@ -174,9 +155,11 @@ const ReadsSidebar: Component< { id?: string } > = (props) => {
<Loader />
}
>
<For each={topPicks}>
{(note) => <div>{note.title}</div>}
</For>
<div class={styles.section}>
<For each={topPicks}>
{(note) => <ArticleShort article={note} />}
</For>
</div>
</Show>
@ -190,9 +173,11 @@ const ReadsSidebar: Component< { id?: string } > = (props) => {
<Loader />
}
>
<For each={topics}>
{(topic) => <div class={styles.topic}>{topic}</div>}
</For>
<div class={styles.section}>
<For each={topics}>
{(topic) => <div class={styles.topic}>{topic}</div>}
</For>
</div>
</Show>
</Show>

View File

@ -1,12 +1,13 @@
import { nip19 } from "nostr-tools";
import { Kind } from "./constants";
import { getEvents } from "./lib/feed";
import { setLinkPreviews } from "./lib/notes";
import { decodeIdentifier } from "./lib/keys";
import { getParametrizedEvents, setLinkPreviews } from "./lib/notes";
import { updateStore, store } from "./services/StoreService";
import { subscribeTo } from "./sockets";
import { convertToArticles, convertToNotes } from "./stores/note";
import { account } from "./translations";
import { FeedPage, NostrEventContent, NostrEventType, NostrMentionContent, NostrNoteActionsContent, NostrNoteContent, NostrStatsContent, NostrUserContent, NoteActions, PrimalArticle, PrimalNote, TopZap } from "./types/primal";
import { EventCoordinate, FeedPage, NostrEventContent, NostrEventType, NostrMentionContent, NostrNoteActionsContent, NostrNoteContent, NostrStatsContent, NostrUserContent, NoteActions, PrimalArticle, PrimalNote, TopZap } from "./types/primal";
import { parseBolt11 } from "./utils";
export const fetchNotes = (pubkey: string | undefined, noteIds: string[], subId: string) => {
@ -197,6 +198,196 @@ export const fetchArticles = (pubkey: string | undefined, noteIds: string[], sub
until: 0,
}
const events = noteIds.reduce<EventCoordinate[]>((acc, id) => {
const d = decodeIdentifier(id);
if (!d.data || d.type !== 'naddr') return acc;
const { pubkey, identifier, kind } = d.data;
return [
...acc,
{ identifier, pubkey, kind },
]
}, []);
let lastNote: PrimalArticle | undefined;
const unsub = subscribeTo(subId, (type, _, content) => {
if (type === 'EOSE') {
unsub();
const notes = convertToArticles(page, page.topZaps);
resolve(notes);
return;
}
if (type === 'EVENT') {
if (!content) return;
updatePage(content);
}
});
getParametrizedEvents(events, subId);
// getEvents(pubkey, [...noteIds], subId, true);
const updatePage = (content: NostrEventContent) => {
if (content.kind === Kind.Metadata) {
const user = content as NostrUserContent;
page.users[user.pubkey] = { ...user };
return;
}
if ([Kind.LongForm, Kind.Repost].includes(content.kind)) {
const message = content as NostrNoteContent;
if (lastNote?.noteId !== nip19.noteEncode(message.id)) {
page.messages.push({...message});
}
return;
}
if (content.kind === Kind.NoteStats) {
const statistic = content as NostrStatsContent;
const stat = JSON.parse(statistic.content);
page.postStats[stat.event_id] = { ...stat };
return;
}
if (content.kind === Kind.Mentions) {
const mentionContent = content as NostrMentionContent;
const mention = JSON.parse(mentionContent.content);
if (!page.mentions) {
page.mentions = {};
}
page.mentions[mention.id] = { ...mention };
return;
}
if (content.kind === Kind.NoteActions) {
const noteActionContent = content as NostrNoteActionsContent;
const noteActions = JSON.parse(noteActionContent.content) as NoteActions;
page.noteActions[noteActions.event_id] = { ...noteActions };
return;
}
if (content.kind === Kind.LinkMetadata) {
const metadata = JSON.parse(content.content);
const data = metadata.resources[0];
if (!data) {
return;
}
const preview = {
url: data.url,
title: data.md_title,
description: data.md_description,
mediaType: data.mimetype,
contentType: data.mimetype,
images: [data.md_image],
favicons: [data.icon_url],
};
setLinkPreviews(() => ({ [data.url]: preview }));
return;
}
if (content.kind === Kind.RelayHint) {
const hints = JSON.parse(content.content);
page.relayHints = { ...page.relayHints, ...hints };
return;
}
if (content?.kind === Kind.Zap) {
const zapTag = content.tags.find(t => t[0] === 'description');
if (!zapTag) return;
const zapInfo = JSON.parse(zapTag[1] || '{}');
let amount = '0';
let bolt11Tag = content?.tags?.find(t => t[0] === 'bolt11');
if (bolt11Tag) {
try {
amount = `${parseBolt11(bolt11Tag[1]) || 0}`;
} catch (e) {
const amountTag = zapInfo.tags.find((t: string[]) => t[0] === 'amount');
amount = amountTag ? amountTag[1] : '0';
}
}
const eventId = (zapInfo.tags.find((t: string[]) => t[0] === 'e') || [])[1];
const zap: TopZap = {
id: zapInfo.id,
amount: parseInt(amount || '0'),
pubkey: zapInfo.pubkey,
message: zapInfo.content,
eventId,
};
if (page.topZaps[eventId] === undefined) {
page.topZaps[eventId] = [{ ...zap }];
return;
}
if (page.topZaps[eventId].find(i => i.id === zap.id)) {
return;
}
const newZaps = [ ...page.topZaps[eventId], { ...zap }].sort((a, b) => b.amount - a.amount);
page.topZaps[eventId] = [ ...newZaps ];
return;
}
if (content.kind === Kind.NoteQuoteStats) {
const quoteStats = JSON.parse(content.content);
// updateStore('quoteCount', () => quoteStats.count || 0);
return;
}
};
});
};
export const fetchArticleThread = (pubkey: string | undefined, noteIds: string, subId: string) => {
return new Promise<PrimalArticle[]>((resolve, reject) => {
if (!pubkey) reject('Missing pubkey');
let page: FeedPage = {
users: {},
messages: [],
postStats: {},
mentions: {},
noteActions: {},
relayHints: {},
topZaps: {},
since: 0,
until: 0,
}
let primaryArticle: PrimalArticle | undefined;
let lastNote: PrimalArticle | undefined;
const unsub = subscribeTo(subId, (type, _, content) => {

View File

@ -1,6 +1,6 @@
import { useIntl } from "@cookbook/solid-intl";
import { useParams } from "@solidjs/router";
import { Component, createEffect, createSignal, For, Show } from "solid-js";
import { Component, createEffect, createSignal, For, onMount, Show } from "solid-js";
import { createStore } from "solid-js/store";
import { APP_ID } from "../App";
import { Kind } from "../constants";
@ -312,7 +312,7 @@ const Longform: Component< { naddr: string } > = (props) => {
getUserProfileInfo(pubkey(), account?.publicKey, subId);
});
createEffect(() => {
onMount(() => {
if (naddr() === 'naddr1_test') {
setNote(() => ({