working previews

This commit is contained in:
keyan 2021-07-07 19:15:27 -05:00
parent 38df1fcdb7
commit 68e80b615c
17 changed files with 7072 additions and 3022 deletions

View File

@ -7,7 +7,7 @@ import typeDefs from './typeDefs'
import models from './models'
export default async function serverSideClient (req) {
const session = await getSession({ req })
const session = req && await getSession({ req })
return new ApolloClient({
ssrMode: true,
// Instead of "createHttpLink" use SchemaLink here

View File

@ -131,15 +131,6 @@ export default {
FROM "Item"
WHERE "userId" = $1 AND "parentId" IS NOT NULL
ORDER BY created_at DESC`, Number(userId))
},
root: async (parent, { id }, { models }) => {
return (await models.$queryRaw(`
${SELECT}
FROM "Item"
WHERE id = (
SELECT ltree2text(subltree(path, 0, 1))::integer
FROM "Item"
WHERE id = $1)`, Number(id)))[0]
}
},
@ -238,6 +229,24 @@ export default {
})
return sats || 0
},
root: async (item, args, { models }) => {
if (!item.parentId) {
return null
}
return (await models.$queryRaw(`
${SELECT}
FROM "Item"
WHERE id = (
SELECT ltree2text(subltree(path, 0, 1))::integer
FROM "Item"
WHERE id = $1)`, Number(item.id)))[0]
},
parent: async (item, args, { models }) => {
if (!item.parentId) {
return null
}
return await models.item.findUnique({ where: { id: item.parentId } })
}
}
}

View File

@ -7,7 +7,6 @@ export default gql`
notifications: [Item!]!
item(id: ID!): Item
userComments(userId: ID!): [Item!]
root(id: ID!): Item
}
extend type Mutation {
@ -34,6 +33,8 @@ export default gql`
url: String
text: String
parentId: Int
parent: Item
root: Item
user: User!
depth: Int!
sats: Int!

View File

@ -4,7 +4,6 @@ import Text from './text'
import Link from 'next/link'
import Reply from './reply'
import { useEffect, useRef, useState } from 'react'
import { gql, useQuery } from '@apollo/client'
import { timeSince } from '../lib/time'
import UpVote from './upvote'
import Eye from '../svgs/eye-fill.svg'
@ -12,15 +11,6 @@ import EyeClose from '../svgs/eye-close-line.svg'
import { useRouter } from 'next/router'
function Parent ({ item }) {
const { data } = useQuery(
gql`{
root(id: ${item.id}) {
id
title
}
}`
)
const ParentFrag = () => (
<>
<span> \ </span>
@ -30,16 +20,16 @@ function Parent ({ item }) {
</>
)
if (!data) {
if (!item.root) {
return <ParentFrag />
}
return (
<>
{data.root.id !== item.parentId && <ParentFrag />}
{Number(item.root.id) !== Number(item.parentId) && <ParentFrag />}
<span> \ </span>
<Link href={`/items/${data.root.id}`} passHref>
<a onClick={e => e.stopPropagation()} className='text-reset'>root: {data.root.title}</a>
<Link href={`/items/${item.root.id}`} passHref>
<a onClick={e => e.stopPropagation()} className='text-reset'>root: {item.root.title}</a>
</Link>
</>
)

View File

@ -81,7 +81,7 @@ export default function Header () {
</div>
)
} else {
return path !== '/login' && <Button href='/login' onClick={signIn}>login</Button>
return path !== '/login' && <Button id='login' href='/login' onClick={signIn}>login</Button>
}
}
@ -133,6 +133,9 @@ export function HeaderPreview () {
<Link href='/' passHref>
<Navbar.Brand className={`${styles.brand} d-none d-sm-block`}>STACKER NEWS</Navbar.Brand>
</Link>
<Nav.Item className='text-monospace' style={{ opacity: '.5' }}>
<Price />
</Nav.Item>
</Nav>
</Navbar>
</Container>

View File

@ -2,35 +2,13 @@ import Header from './header'
import Head from 'next/head'
import Container from 'react-bootstrap/Container'
import { LightningProvider } from './lightning'
import { useRouter } from 'next/router'
import Footer from './footer'
import { NextSeo } from 'next-seo'
import Seo from './seo'
export default function Layout ({ noContain, noFooter, children }) {
const router = useRouter()
const defaultTitle = router.asPath.split('?')[0].slice(1)
const fullTitle = `${defaultTitle && `${defaultTitle} \\ `}stacker news`
const desc = 'Discuss Bitcoin. Stack sats. News for plebs.'
return (
<>
<NextSeo
title={fullTitle}
description={desc}
openGraph={{
title: fullTitle,
description: desc,
images: [
{
url: 'https://stacker.news/favicon.png'
}
],
site_name: 'Stacker News'
}}
twitter={{
site: '@stacker_news',
cardType: 'summary_large_image'
}}
/>
<Seo />
<LightningProvider>
<Head>
<meta name='viewport' content='initial-scale=1.0, width=device-width' />

51
components/seo.js Normal file
View File

@ -0,0 +1,51 @@
import { NextSeo } from 'next-seo'
import { useRouter } from 'next/router'
import RemoveMarkdown from 'remove-markdown'
export default function Seo ({ item, user }) {
const router = useRouter()
const pathNoQuery = router.asPath.split('?')[0]
const defaultTitle = pathNoQuery.slice(1)
let fullTitle = `${defaultTitle && `${defaultTitle} \\ `}stacker news`
let desc = 'Bitcoin news powered by the Lightning Network. Stack sats with real Bitcoiners.'
if (item) {
if (item.title) {
fullTitle = `${item.title} \\ stacker news`
} else if (item.root) {
fullTitle = `reply on: ${item.root.title} \\ stacker news`
}
if (item.text) {
desc = RemoveMarkdown(item.text)
if (desc) {
desc = desc.replace(/\s+/g, ' ')
}
} else {
desc = `@${item.user.name} stacked ${item.sats} sats ${item.url ? `posting ${item.url}` : ''}`
}
desc += ` [${item.ncomments} comments, ${item.boost} boost]`
}
if (user) {
desc = `@${user.name} has [${user.stacked} stacked, ${user.sats} sats, ${user.nitems} posts, ${user.ncomments} comments]`
}
return (
<NextSeo
title={fullTitle}
description={desc}
openGraph={{
title: fullTitle,
description: desc,
images: [
{
url: 'https://stacker.news/api/capture' + pathNoQuery
}
],
site_name: 'Stacker News'
}}
twitter={{
site: '@stacker_news',
cardType: 'summary_large_image'
}}
/>
)
}

View File

@ -13,6 +13,10 @@ export const COMMENT_FIELDS = gql`
boost
meSats
ncomments
root {
id
title
}
}
`

View File

@ -14,6 +14,10 @@ export const ITEM_FIELDS = gql`
boost
meSats
ncomments
root {
id
title
}
}`
export const MORE_ITEMS = gql`

9848
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -23,6 +23,8 @@
"next": "^10.2.3",
"next-auth": "^3.13.3",
"next-seo": "^4.24.0",
"node-webshot": "^1.0.4",
"pageres": "^6.2.3",
"prisma": "^2.25.0",
"qrcode.react": "^1.0.1",
"react": "17.0.1",
@ -31,6 +33,7 @@
"react-markdown": "^6.0.2",
"react-syntax-highlighter": "^15.4.3",
"remark-gfm": "^1.0.0",
"remove-markdown": "^0.3.0",
"sass": "^1.32.8",
"secp256k1": "^4.0.2",
"swr": "^0.5.4",

View File

@ -3,6 +3,7 @@ import Items from '../components/items'
import { gql } from '@apollo/client'
import ApolloClient from '../api/client'
import UserHeader from '../components/user-header'
import Seo from '../components/seo'
export async function getServerSideProps ({ req, params }) {
const { error, data: { user } } = await (await ApolloClient(req)).query({
@ -36,6 +37,7 @@ export async function getServerSideProps ({ req, params }) {
export default function User ({ user }) {
return (
<Layout>
<Seo user={user} />
<UserHeader user={user} />
<Items variables={{ sort: 'user', userId: user.id }} />
</Layout>

View File

@ -3,6 +3,7 @@ import { gql } from '@apollo/client'
import ApolloClient from '../../api/client'
import UserHeader from '../../components/user-header'
import CommentsFlat from '../../components/comments-flat'
import Seo from '../../components/seo'
export async function getServerSideProps ({ req, params }) {
const { error, data: { user } } = await (await ApolloClient(req)).query({
@ -36,6 +37,7 @@ export async function getServerSideProps ({ req, params }) {
export default function UserComments ({ user }) {
return (
<Layout>
<Seo user={user} />
<UserHeader user={user} />
<CommentsFlat variables={{ userId: user.id }} includeParent noReply clickToContext />
</Layout>

View File

@ -0,0 +1,11 @@
import Pageres from 'pageres'
import path from 'path'
export default async function handler (req, res) {
const url = 'http://' + path.join('localhost:3000', ...(req.query.path || []))
res.setHeader('Content-Type', 'image/png')
const streams = await new Pageres({ crop: true, delay: 1 })
.src(url, ['600x300'])
.run()
res.status(200).end(streams[0])
}

View File

@ -8,7 +8,7 @@ import { COMMENTS } from '../../fragments/comments'
import { ITEM_FIELDS } from '../../fragments/items'
import { gql, useQuery } from '@apollo/client'
import styles from '../../styles/item.module.css'
import Head from 'next/head'
import Seo from '../../components/seo'
export async function getServerSideProps ({ params: { id } }) {
return {
@ -60,13 +60,11 @@ function LoadItem ({ query }) {
return (
<>
<Seo item={item} />
{item.parentId
? <Comment item={item} replyOpen includeParent noComments />
: (
<>
<Head>
<title>{item.title} \ stacker news</title>
</Head>
<Item item={item}>
{item.text && <div className='mb-3'><Text>{item.text}</Text></div>}
<Reply parentId={item.id} />

View File

@ -0,0 +1,44 @@
import ApolloClient from '../../api/client'
import { MORE_ITEMS } from '../../fragments/items'
import Item from '../../components/item'
import styles from '../../components/items.module.css'
import LayoutPreview from '../../components/layout-preview'
import { LightningProvider } from '../../components/lightning'
// we can't SSR on the normal page because we'd have to hyrdate the cache
// on the client which is a lot of work, i.e. a bit fat todo
export async function getServerSideProps ({ params }) {
// grab the item on the server side
const { error, data: { moreItems: { items } } } = await (await ApolloClient()).query({
query: MORE_ITEMS,
variables: { sort: 'hot' }
})
if (!items || error) {
return {
notFound: true
}
}
return {
props: {
items
}
}
}
export default function IndexPreview ({ items }) {
return (
<>
<LayoutPreview>
<LightningProvider>
<div className={styles.grid}>
{items.map((item, i) => (
<Item item={item} rank={i + 1} key={item.id} />
))}
</div>
</LightningProvider>
</LayoutPreview>
</>
)
}

View File

@ -9,9 +9,9 @@ import Comment from '../../../components/comment'
// we can't SSR on the normal page because we'd have to hyrdate the cache
// on the client which is a lot of work, i.e. a bit fat todo
export async function getServerSideProps ({ req, params }) {
export async function getServerSideProps ({ params }) {
// grab the item on the server side
const { error, data: { item } } = await (await ApolloClient(req)).query({
const { error, data: { item } } = await (await ApolloClient()).query({
query:
gql`
${ITEM_FIELDS}
@ -36,18 +36,30 @@ export async function getServerSideProps ({ req, params }) {
}
}
// export async function getStaticPaths () {
// return {
// paths: [],
// // Enable statically generating additional pages
// // For example: `/posts/3`
// fallback: 'blocking'
// }
// }
export default function ItemPreview ({ item }) {
return (
<LayoutPreview>
<LightningProvider>
{item.parentId
? <Comment item={item} includeParent noComments />
: (
<Item item={item}>
{item.text && <div className='mb-3'><Text>{item.text}</Text></div>}
</Item>
)}
</LightningProvider>
</LayoutPreview>
<>
<LayoutPreview>
<LightningProvider>
{item.parentId
? <Comment item={item} includeParent noReply noComments />
: (
<Item item={item}>
{item.text && <div className='mb-3'><Text>{item.text}</Text></div>}
</Item>
)}
</LightningProvider>
</LayoutPreview>
</>
)
}