mirror of
https://github.com/PrimalHQ/primal-web-app.git
synced 2024-09-30 00:41:09 +00:00
Basic longform render
This commit is contained in:
parent
4c7de1e307
commit
6a2ce12501
1196
package-lock.json
generated
1196
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -38,6 +38,7 @@
|
||||
"qr-code-styling": "^1.6.0-rc.1",
|
||||
"sass": "1.67.0",
|
||||
"solid-js": "1.7.11",
|
||||
"solid-markdown": "^2.0.1",
|
||||
"solid-transition-group": "0.2.3"
|
||||
}
|
||||
}
|
||||
|
@ -43,6 +43,7 @@ const Moderation = lazy(() => import('./pages/Settings/Moderation'));
|
||||
const Menu = lazy(() => import('./pages/Settings/Menu'));
|
||||
const Landing = lazy(() => import('./pages/Landing'));
|
||||
const AppDownloadQr = lazy(() => import('./pages/appDownloadQr'));
|
||||
const Longform = lazy(() => import('./pages/Longform'));
|
||||
|
||||
const Terms = lazy(() => import('./pages/Terms'));
|
||||
const Privacy = lazy(() => import('./pages/Privacy'));
|
||||
@ -111,6 +112,7 @@ const Router: Component = () => {
|
||||
<Route path="/home" component={Home} />
|
||||
<Route path="/thread/:postId" component={Thread} />
|
||||
<Route path="/e/:postId" component={Thread} />
|
||||
<Route path="/l/:naddr" component={Longform} />
|
||||
<Route path="/explore/:scope?/:timeframe?" component={Explore} />
|
||||
<Route path="/messages/:sender?" component={Messages} />
|
||||
<Route path="/notifications" component={Notifications} />
|
||||
|
84
src/pages/Longform.module.scss
Normal file
84
src/pages/Longform.module.scss
Normal file
@ -0,0 +1,84 @@
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid var(--devider);
|
||||
|
||||
.author {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
|
||||
.userName {
|
||||
color: var(--text-primary);
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
.time {
|
||||
color: var(--text-tertiary);
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
.longform {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
position: relative;
|
||||
margin-block: 48px;
|
||||
margin-inline: 20px;
|
||||
|
||||
.title {
|
||||
color: var(--text-primary);
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.summary {
|
||||
color: var(--text-secondary);
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.image {
|
||||
width: 100%;
|
||||
object-fit: contain;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.content {
|
||||
* {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
p, li {
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--accent-links);
|
||||
}
|
||||
}
|
||||
|
||||
.tags {
|
||||
width: 100%;
|
||||
.tag {
|
||||
display: inline-block;
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
font-weight: 300;
|
||||
background-color: var(--background-input);
|
||||
padding: 2px 8px;
|
||||
border-radius: 8px;
|
||||
width: fit-content;
|
||||
margin: 4px;
|
||||
}
|
||||
}
|
||||
}
|
207
src/pages/Longform.tsx
Normal file
207
src/pages/Longform.tsx
Normal file
@ -0,0 +1,207 @@
|
||||
import { useIntl } from "@cookbook/solid-intl";
|
||||
import { useParams } from "@solidjs/router";
|
||||
import { Component, createEffect, createSignal, For, Show } from "solid-js";
|
||||
import { createStore } from "solid-js/store";
|
||||
import { APP_ID } from "../App";
|
||||
import { Kind } from "../constants";
|
||||
import { useAccountContext } from "../contexts/AccountContext";
|
||||
import { decodeIdentifier, hexToNpub } from "../lib/keys";
|
||||
import { getParametrizedEvent } from "../lib/notes";
|
||||
import { subscribeTo } from "../sockets";
|
||||
import { SolidMarkdown } from "solid-markdown";
|
||||
|
||||
import styles from './Longform.module.scss';
|
||||
import Loader from "../components/Loader/Loader";
|
||||
import { NostrUserContent, PrimalUser } from "../types/primal";
|
||||
import { getUserProfileInfo } from "../lib/profile";
|
||||
import { convertToUser, userName } from "../stores/profile";
|
||||
import Avatar from "../components/Avatar/Avatar";
|
||||
import { date, longDate, shortDate, veryLongDate } from "../lib/dates";
|
||||
|
||||
export type LongFormData = {
|
||||
title: string,
|
||||
summary: string,
|
||||
image: string,
|
||||
tags: string[],
|
||||
published: number,
|
||||
content: string,
|
||||
author: string,
|
||||
};
|
||||
|
||||
const emptyLongNote = {
|
||||
title: '',
|
||||
summary: '',
|
||||
image: '',
|
||||
tags: [],
|
||||
published: 0,
|
||||
content: '',
|
||||
author: '',
|
||||
}
|
||||
|
||||
const Longform: Component = () => {
|
||||
const account = useAccountContext();
|
||||
const params = useParams();
|
||||
const intl = useIntl();
|
||||
|
||||
const [note, setNote] = createStore<LongFormData>({...emptyLongNote});
|
||||
|
||||
const [pubkey, setPubkey] = createSignal<string>('');
|
||||
|
||||
// @ts-ignore
|
||||
const [author, setAuthor] = createStore<PrimalUser>()
|
||||
|
||||
createEffect(() => {
|
||||
if (!pubkey()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const naddr = params.naddr;
|
||||
const subId = `author_${naddr}_${APP_ID}`;
|
||||
|
||||
const unsub = subscribeTo(subId, (type, subId, content) =>{
|
||||
if (type === 'EOSE') {
|
||||
unsub();
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === 'EVENT') {
|
||||
if (!content) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(content.kind === Kind.Metadata) {
|
||||
const userContent = content as NostrUserContent;
|
||||
|
||||
const user = convertToUser(userContent);
|
||||
|
||||
setAuthor(() => ({ ...user }));
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
getUserProfileInfo(pubkey(), account?.publicKey, subId);
|
||||
});
|
||||
|
||||
createEffect(() => {
|
||||
const naddr = params.naddr;
|
||||
|
||||
if (typeof naddr === 'string' && naddr.startsWith('naddr')) {
|
||||
const decoded = decodeIdentifier(naddr);
|
||||
|
||||
const { pubkey, identifier, kind } = decoded.data;
|
||||
|
||||
const subId = `naddr_${naddr}_${APP_ID}`;
|
||||
|
||||
const unsub = subscribeTo(subId, (type, subId, content) =>{
|
||||
if (type === 'EOSE') {
|
||||
unsub();
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === 'EVENT') {
|
||||
if (!content) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(content.kind === Kind.LongForm) {
|
||||
|
||||
setPubkey(() => content.pubkey);
|
||||
|
||||
let n: LongFormData = {
|
||||
title: '',
|
||||
summary: '',
|
||||
image: '',
|
||||
tags: [],
|
||||
published: content.created_at || 0,
|
||||
content: content.content,
|
||||
author: content.pubkey,
|
||||
}
|
||||
|
||||
content.tags.forEach(tag => {
|
||||
switch (tag[0]) {
|
||||
case 't':
|
||||
n.tags.push(tag[1]);
|
||||
break;
|
||||
case 'title':
|
||||
n.title = tag[1];
|
||||
break;
|
||||
case 'summary':
|
||||
n.summary = tag[1];
|
||||
break;
|
||||
case 'image':
|
||||
n.image = tag[1];
|
||||
break;
|
||||
case 'published':
|
||||
n.published = parseInt(tag[1]);
|
||||
break;
|
||||
case 'content':
|
||||
n.content = tag[1];
|
||||
break;
|
||||
case 'author':
|
||||
n.author = tag[1];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
setNote(() => ({...n}));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
getParametrizedEvent(pubkey, identifier, kind, subId);
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<div class={styles.header}>
|
||||
<div class={styles.author}>
|
||||
<Show when={author}>
|
||||
<Avatar user={author} size="xs" />
|
||||
<div class={styles.userName}>
|
||||
{userName(author)}
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
<div class={styles.time}>
|
||||
{shortDate(note.published)}
|
||||
</div>
|
||||
</div>
|
||||
<div class={styles.longform}>
|
||||
<Show
|
||||
when={note.content.length > 0}
|
||||
fallback={<Loader />}
|
||||
>
|
||||
<div class={styles.title}>
|
||||
{note.title}
|
||||
</div>
|
||||
|
||||
<div class={styles.summary}>
|
||||
{note.summary}
|
||||
</div>
|
||||
|
||||
<div class={styles.tags}>
|
||||
<For each={note.tags}>
|
||||
{tag => (
|
||||
<div class={styles.tag}>
|
||||
{tag}
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
|
||||
<img class={styles.image} src={note.image} />
|
||||
|
||||
<div class={styles.content}>
|
||||
<SolidMarkdown
|
||||
children={note.content || ''}
|
||||
/>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</>);
|
||||
}
|
||||
|
||||
export default Longform;
|
@ -68,10 +68,13 @@ export const userName = (user: PrimalUser | undefined) => {
|
||||
if (!user) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const npub = user.npub || hexToNpub(user.pubkey) || '';
|
||||
|
||||
const name = user.display_name ||
|
||||
user.displayName ||
|
||||
user.name ||
|
||||
truncateNpub(user.npub);
|
||||
truncateNpub(npub);
|
||||
|
||||
return name ?
|
||||
name :
|
||||
@ -82,10 +85,12 @@ export const authorName = (user: PrimalUser | undefined) => {
|
||||
if (!user) {
|
||||
return '';
|
||||
}
|
||||
const npub = user.npub || hexToNpub(user.pubkey) || '';
|
||||
|
||||
const name = user.display_name ||
|
||||
user.displayName ||
|
||||
user.name ||
|
||||
truncateNpub(user.npub);
|
||||
truncateNpub(npub);
|
||||
|
||||
return name ?
|
||||
name :
|
||||
|
Loading…
Reference in New Issue
Block a user