a bunch of new stuff

This commit is contained in:
keyan 2021-04-22 17:14:32 -05:00
parent 8ecc81f3f7
commit ec3f6b922d
30 changed files with 530 additions and 303 deletions

View File

@ -54,46 +54,55 @@ function nestComments (flat, parentId) {
return [result, added]
}
// we have to do our own query because ltree is unsupported
const SELECT =
`SELECT id, created_at as "createdAt", updated_at as "updatedAt", title,
text, url, "userId", "parentId", ltree2text("path") AS "path"`
export default {
Query: {
items: async (parent, args, { models }) => {
return await models.$queryRaw(`
SELECT id, "created_at" as "createdAt", title, url, text,
"userId", nlevel(path)-1 AS depth, ltree2text("path") AS "path"
${SELECT}
FROM "Item"
WHERE "parentId" IS NULL`)
},
item: async (parent, { id }, { models }) => {
const res = await models.$queryRaw(`
SELECT id, "created_at" as "createdAt", title, url, text,
"parentId", "userId", nlevel(path)-1 AS depth, ltree2text("path") AS "path"
return (await models.$queryRaw(`
${SELECT}
FROM "Item"
WHERE id = ${id}`)
return res.length ? res[0] : null
WHERE id = ${id}`))[0]
},
flatcomments: async (parent, { parentId }, { models }) => {
userItems: async (parent, { userId }, { models }) => {
return await models.$queryRaw(`
SELECT id, "created_at" as "createdAt", text, "parentId",
"userId", nlevel(path)-1 AS depth, ltree2text("path") AS "path"
${SELECT}
FROM "Item"
WHERE path <@ (SELECT path FROM "Item" where id = ${parentId}) AND id != ${parentId}
ORDER BY "path"`)
WHERE "userId" = ${userId} AND "parentId" IS NULL
ORDER BY created_at DESC`)
},
comments: async (parent, { parentId }, { models }) => {
const flat = await models.$queryRaw(`
SELECT id, "created_at" as "createdAt", text, "parentId",
"userId", nlevel(path)-1 AS depth, ltree2text("path") AS "path"
${SELECT}
FROM "Item"
WHERE path <@ (SELECT path FROM "Item" where id = ${parentId}) AND id != ${parentId}
ORDER BY "path"`)
return nestComments(flat, parentId)[0]
},
root: async (parent, { id }, { models }) => {
const res = await models.$queryRaw(`
SELECT id, title
userComments: async (parent, { userId }, { models }) => {
return await models.$queryRaw(`
${SELECT}
FROM "Item"
WHERE id = (SELECT ltree2text(subltree(path, 0, 1))::integer FROM "Item" WHERE id = ${id})`)
return res.length ? res[0] : null
WHERE "userId" = ${userId} AND "parentId" IS NOT NULL
ORDER BY created_at DESC`)
},
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 = ${id})`))[0]
}
},

View File

@ -2,18 +2,22 @@ export default {
Query: {
me: async (parent, args, { models, me }) =>
me ? await models.user.findUnique({ where: { id: me.id } }) : null,
user: async (parent, { id }, { models }) =>
await models.user.findUnique({ where: { id } }),
user: async (parent, { name }, { models }) => {
console.log(name)
return await models.user.findUnique({ where: { name } })
},
users: async (parent, args, { models }) =>
await models.user.findMany()
},
User: {
messages: async (user, args, { models }) =>
await models.message.findMany({
where: {
userId: user.id
}
})
nitems: async (user, args, { models }) => {
return await models.item.count({ where: { userId: user.id, parentId: null } })
},
ncomments: async (user, args, { models }) => {
return await models.item.count({ where: { userId: user.id, parentId: { not: null } } })
},
stacked: () => 0,
sats: () => 0
}
}

View File

@ -4,8 +4,9 @@ export default gql`
extend type Query {
items: [Item!]!
item(id: ID!): Item
userItems(userId: ID!): [Item!]
comments(parentId: ID!): [Item!]!
flatcomments(parentId: ID!): [Item!]!
userComments(userId: ID!): [Item!]
root(id: ID!): Item
}
@ -13,6 +14,7 @@ export default gql`
createLink(title: String!, url: String): Item!
createDiscussion(title: String!, text: String): Item!
createComment(text: String!, parentId: ID!): Item!
vote(sats: Int): Int!
}
type Item {

View File

@ -3,13 +3,16 @@ import { gql } from 'apollo-server-micro'
export default gql`
extend type Query {
me: User
user(id: ID!): User
user(name: String): User
users: [User!]
}
type User {
id: ID!
name: String
messages: [Message!]
nitems: Int!
ncomments: Int!
stacked: Int!
sats: Int!
}
`

View File

@ -1,12 +1,12 @@
import itemStyles from './item.module.css'
import styles from './comment.module.css'
import UpVote from '../svgs/lightning-arrow.svg'
import Text from './text'
import Link from 'next/link'
import Reply from './reply'
import { useState } from 'react'
import { gql, useQuery } from '@apollo/client'
import { timeSince } from '../lib/time'
import UpVote from './upvote'
function Parent ({ item }) {
const { data } = useQuery(
@ -42,46 +42,79 @@ function Parent ({ item }) {
)
}
export default function Comment ({ item, children, replyOpen, includeParent, cacheId }) {
export default function Comment ({ item, children, replyOpen, includeParent, cacheId, noReply }) {
const [reply, setReply] = useState(replyOpen)
return (
<>
<div className={`${itemStyles.item} ${styles.item}`}>
<UpVote width={24} height={24} className={`${itemStyles.upvote} ${styles.upvote}`} />
<div className={itemStyles.hunk}>
<div className={itemStyles.other}>
<Link href={`/@${item.user.name}`} passHref>
<a>@{item.user.name}</a>
</Link>
<span> </span>
<span>{timeSince(new Date(item.createdAt))}</span>
<span> \ </span>
<span>{item.sats} sats</span>
<span> \ </span>
<Link href={`/items/${item.id}`} passHref>
<a className='text-reset'>{item.ncomments} replies</a>
</Link>
{includeParent && <Parent item={item} />}
<div />
<div>
<div className={`${itemStyles.item} ${styles.item}`}>
<UpVote className={styles.upvote} />
<div className={itemStyles.hunk}>
<div className={itemStyles.other}>
<Link href={`/${item.user.name}`} passHref>
<a>@{item.user.name}</a>
</Link>
<span> </span>
<span>{timeSince(new Date(item.createdAt))}</span>
<span> \ </span>
<span>{item.sats} sats</span>
<span> \ </span>
<Link href={`/items/${item.id}`} passHref>
<a className='text-reset'>{item.ncomments} replies</a>
</Link>
{includeParent && <Parent item={item} />}
</div>
<div className={styles.text}>
<Text>{item.text}</Text>
</div>
</div>
<div className={styles.text}>
<Text>{item.text}</Text>
</div>
<div className={`${itemStyles.children} ${styles.children}`}>
{!noReply &&
<div
className={`${itemStyles.other} ${styles.reply}`}
onClick={() => setReply(!reply)}
>
{reply ? 'cancel' : 'reply'}
</div>}
{reply && <Reply parentId={item.id} onSuccess={() => setReply(replyOpen || false)} cacheId={cacheId} />}
{children}
<div className={styles.comments}>
{item.comments
? item.comments.map((item) => (
<Comment key={item.id} item={item} />
))
: null}
</div>
</div>
</div>
<div className={`${itemStyles.children} ${styles.children}`}>
<div
className={`${itemStyles.other} ${styles.reply}`}
onClick={() => setReply(!reply)}
>
{reply ? 'cancel' : 'reply'}
</>
)
}
export function CommentSkeleton ({ skeletonChildren }) {
const comments = skeletonChildren ? new Array(2).fill(null) : []
return (
<>
<div className={`${itemStyles.item} ${itemStyles.skeleton} ${styles.item} ${styles.skeleton}`}>
<UpVote className={styles.upvote} />
<div className={`${itemStyles.hunk} ${styles.hunk}`}>
<div className={itemStyles.other}>
<span className={`${itemStyles.otherItem} ${itemStyles.otherItemLonger} clouds`} />
<span className={`${itemStyles.otherItem} clouds`} />
<span className={`${itemStyles.otherItem} ${itemStyles.otherItemLonger} clouds`} />
</div>
<div className={`${styles.text} clouds`} />
</div>
{reply && <Reply parentId={item.id} onSuccess={() => setReply(replyOpen || false)} cacheId={cacheId} />}
{children}
</div>
<div className={`${itemStyles.children} ${styles.children}`}>
<div className={styles.comments}>
{item.comments
? item.comments.map((item) => (
<Comment key={item.id} item={item} />
{comments
? comments.map((_, i) => (
<CommentSkeleton key={i} />
))
: null}
</div>

View File

@ -23,4 +23,14 @@
.comments {
margin-left: 16px;
margin-top: .5rem;
}
.skeleton .hunk {
width: 100%;
}
.skeleton .text {
height: 100px;
border-radius: .4rem;
}

View File

@ -1,28 +1,23 @@
import { useQuery, gql } from '@apollo/client'
import Comment from './comment'
import { COMMENTS } from '../fragments'
import { useQuery } from '@apollo/client'
import Comment, { CommentSkeleton } from './comment'
export default function Comments ({ parentId }) {
const { data } = useQuery(
gql`
${COMMENTS}
export default function Comments ({ query, ...props }) {
const { loading, error, data } = useQuery(query)
{
comments(parentId: ${parentId}) {
...CommentsRecursive
}
}`
)
if (error) return <div>Failed to load!</div>
if (loading) {
const comments = new Array(3).fill(null)
if (!data) return null
return comments.map((_, i) => (
<div key={i} className='mt-2'>
<CommentSkeleton skeletonChildren />
</div>
))
}
return (
<div className='mt-5'>
{data.comments.map(item => (
<div key={item.id} className='mt-2'>
<Comment item={item} />
</div>
))}
return data.comments.map(item => (
<div key={item.id} className='mt-2'>
<Comment item={item} {...props} />
</div>
)
))
}

View File

@ -18,7 +18,11 @@ export default function Header () {
if (session) {
return (
<>
<Nav.Item>{session.user.name}</Nav.Item>
<Nav.Item>
<Link href={'/' + session.user.name} passHref>
<Nav.Link className='text-reset'>@{session.user.name}</Nav.Link>
</Link>
</Nav.Item>
<Nav.Item>
<Nav.Link onClick={signOut}>logout</Nav.Link>
</Nav.Item>
@ -36,7 +40,7 @@ export default function Header () {
<Link href='/' passHref>
<Navbar.Brand className={styles.brand}>STACKER NEWS</Navbar.Brand>
</Link>
<Nav className='mr-auto align-items-center' activeKey={router.pathname}>
<Nav className='mr-auto align-items-center' activeKey={router.asPath}>
<Nav.Item>
<Link href='/recent' passHref>
<Nav.Link>recent</Nav.Link>
@ -48,7 +52,7 @@ export default function Header () {
</Link>
</Nav.Item>
</Nav>
<Nav className='ml-auto align-items-center'>
<Nav className='ml-auto align-items-center' activeKey={router.asPath}>
<Corner />
</Nav>
</Container>

View File

@ -8,4 +8,6 @@
.navbar {
padding: 0rem 1.75rem;
background-color: transparent !important;
background-image: linear-gradient(#FADA5E, #FADA5E, transparent)
}

View File

@ -1,13 +1,19 @@
import Link from 'next/link'
import UpVote from '../svgs/lightning-arrow.svg'
import styles from './item.module.css'
import { timeSince } from '../lib/time'
import UpVote from './upvote'
export default function Item ({ item, children }) {
export default function Item ({ item, rank, children }) {
return (
<>
{rank
? (
<div className={styles.rank}>
{rank}
</div>)
: <div />}
<div className={styles.item}>
<UpVote width={24} height={24} className={styles.upvote} />
<UpVote />
<div className={styles.hunk}>
<div className={`${styles.main} flex-wrap flex-md-nowrap`}>
<Link href={`/items/${item.id}`} passHref>
@ -22,7 +28,7 @@ export default function Item ({ item, children }) {
<a className='text-reset'>{item.ncomments} comments</a>
</Link>
<span> \ </span>
<Link href={`/@${item.user.name}`} passHref>
<Link href={`/${item.user.name}`} passHref>
<a>@{item.user.name}</a>
</Link>
<span> </span>
@ -38,3 +44,28 @@ export default function Item ({ item, children }) {
</>
)
}
export function ItemSkeleton ({ rank }) {
return (
<>
{rank &&
<div className={styles.rank}>
{rank}
</div>}
<div className={`${styles.item} ${styles.skeleton}`}>
<UpVote />
<div className={styles.hunk}>
<div className={`${styles.main} flex-wrap flex-md-nowrap`}>
<span className={`${styles.title} clouds text-reset flex-md-fill flex-md-shrink-0 mr-2`} />
<span className={`${styles.link} clouds`} />
</div>
<div className={styles.other}>
<span className={`${styles.otherItem} clouds`} />
<span className={`${styles.otherItem} ${styles.otherItemLonger} clouds`} />
<span className={`${styles.otherItem} ${styles.otherItemLonger} clouds`} />
</div>
</div>
</div>
</>
)
}

View File

@ -1,13 +1,3 @@
.upvote {
fill: grey;
min-width: fit-content;
}
.upvote:hover {
fill: darkgray;
cursor: pointer;
}
.title {
font-weight: 500;
white-space: normal;
@ -45,4 +35,47 @@
.children {
margin-top: 1rem;
margin-left: 24px;
}
.rank {
font-weight: 600;
align-items: center;
display: flex;
color: grey;
font-size: 90%;
}
.skeleton .other {
height: 17px;
align-items: center;
display: flex;
}
.skeleton .title {
background-color: grey;
width: 500px;
border-radius: .4rem;
height: 19px;
margin: 3px 0px;
}
.skeleton .link {
height: 14px;
background-color: grey;
width: 800px;
border-radius: .4rem;
margin: 3px 0px;
}
.skeleton .otherItem {
display: inline-flex;
width: 42px;
height: 70%;
border-radius: .4rem;
background-color: grey;
margin-right: .5rem;
}
.skeleton .otherItemLonger {
width: 60px;
}

View File

@ -1,35 +1,27 @@
import { gql, useQuery } from '@apollo/client'
import React from 'react'
import Item from './item'
import { useQuery } from '@apollo/client'
import Item, { ItemSkeleton } from './item'
import styles from './items.module.css'
export default function Items () {
const { loading, error, data } = useQuery(
gql`
{ items {
id
createdAt
title
url
user {
name
}
sats
ncomments
} }`
)
if (error) return <div>Failed to load</div>
if (loading) return <div>Loading...</div>
export default function Items ({ query, rank }) {
const { loading, error, data } = useQuery(query)
if (error) return <div>Failed to load!</div>
if (loading) {
const items = new Array(30).fill(null)
return (
<div className={styles.grid}>
{items.map((_, i) => (
<ItemSkeleton rank={i + 1} key={i} />
))}
</div>
)
}
const { items } = data
return (
<div className={styles.grid}>
{items.map((item, i) => (
<React.Fragment key={item.id}>
<div className={styles.rank} key={item.id}>
{i + 1}
</div>
<Item item={item} />
</React.Fragment>
<Item item={item} rank={rank && i + 1} key={item.id} />
))}
</div>
)

View File

@ -1,12 +1,4 @@
.grid {
display: grid;
grid-template-columns: auto 1fr;
}
.rank {
font-weight: 600;
align-items: center;
display: flex;
color: grey;
font-size: 90%;
}

View File

@ -1,19 +1,20 @@
import Header from './header'
import Container from 'react-bootstrap/Container'
import { Lightning } from './lightning'
import { LightningProvider } from './lightning'
export default function Layout ({ noContain, children }) {
return (
<>
<Header />
<Lightning />
{noContain
? children
: (
<Container className='my-2 py-2 px-sm-0'>
{children}
</Container>
)}
<LightningProvider>
<Header />
{noContain
? children
: (
<Container className='my-2 py-2 px-sm-0'>
{children}
</Container>
)}
</LightningProvider>
</>
)
}

View File

@ -1,5 +1,30 @@
import React, { useRef, useEffect } from 'react'
import { useRef, useEffect } from 'react'
export const LightningContext = React.createContext()
export class LightningProvider extends React.Component {
state = {
bolts: 0,
strike: () => this.setState(state => {
return {
...this.state,
bolts: this.state.bolts + 1
}
})
}
render () {
const { state, props: { children } } = this
return (
<LightningContext.Provider value={state}>
{new Array(this.state.bolts).fill(null).map((_, i) => <Lightning key={i} />)}
{children}
</LightningContext.Provider>
)
}
}
export const LightningConsumer = LightningContext.Consumer
export function Lightning () {
const canvasRef = useRef(null)
@ -14,21 +39,14 @@ export function Lightning () {
const bolt = new Bolt(context, {
startPoint: [Math.random() * (canvas.width * 0.5) + (canvas.width * 0.25), 0],
length: canvas.height,
speed: options.speed,
spread: options.spread,
branches: options.branching
speed: 100,
spread: 30,
branches: 20
})
bolt.draw()
}, [])
return <canvas className='position-absolute' ref={canvasRef} style={{ zIndex: -1 }} />
}
// Initialize options.
const options = {
speed: 80,
spread: 40,
branching: 5
return <canvas className='position-fixed' ref={canvasRef} style={{ zIndex: -1 }} />
}
function Bolt (ctx, options) {
@ -40,6 +58,7 @@ function Bolt (ctx, options) {
spread: 50,
branches: 10,
maxBranches: 10,
lineWidth: 3,
...options
}
this.point = [this.options.startPoint[0], this.options.startPoint[1]]
@ -59,7 +78,7 @@ function Bolt (ctx, options) {
ctx.shadowOffsetY = 0
ctx.fillStyle = 'rgba(250, 250, 250, 1)'
ctx.strokeStyle = 'rgba(250, 218, 94, 1)'
ctx.lineWidth = 2
ctx.lineWidth = this.options.lineWidth
this.draw = (isChild) => {
ctx.beginPath()
ctx.moveTo(this.point[0], this.point[1])
@ -78,6 +97,9 @@ function Bolt (ctx, options) {
Math.pow(this.point[1] - this.options.startPoint[1], 2)
)
// make skinnier?
// ctx.lineWidth = ctx.lineWidth * 0.98
if (rand(0, 99) < this.options.branches && this.children.length < this.options.maxBranches) {
this.children.push(new Bolt(ctx, {
startPoint: [this.point[0], this.point[1]],
@ -86,7 +108,8 @@ function Bolt (ctx, options) {
resistance: this.options.resistance,
speed: this.options.speed - 2,
spread: this.options.spread - 2,
branches: this.options.branches
branches: this.options.branches,
lineWidth: ctx.lineWidth
}))
}
@ -101,19 +124,14 @@ function Bolt (ctx, options) {
if (d < this.options.length) {
window.requestAnimationFrame(() => { this.draw() })
} else {
ctx.canvas.style.opacity = 1
this.fade()
}
}
this.fade = function () {
ctx.shadowColor = 'rgba(250, 250, 250, .5)'
ctx.fillStyle = 'rgba(250, 250, 250, .05)'
ctx.fillRect(0, 0, window.innerWidth, window.innerHeight)
const color = ctx.getImageData(0, 0, 1, 1)
console.log(color.data)
if (color.data[0] >= 250 && color.data[3] > 240) {
ctx.fillStyle = 'rgba(250, 250, 250, 1)'
ctx.canvas.style.opacity -= 0.04
if (ctx.canvas.style.opacity <= 0) {
ctx.clearRect(0, 0, window.innerWidth, window.innerHeight)
return
}

View File

@ -2,7 +2,7 @@ import { Form, Input, SubmitButton } from '../components/form'
import * as Yup from 'yup'
import { gql, useMutation } from '@apollo/client'
import styles from './reply.module.css'
import { COMMENTS } from '../fragments'
import { COMMENTS } from '../fragments/comments'
export const CommentSchema = Yup.object({
text: Yup.string().required('required').trim()

17
components/upvote.js Normal file
View File

@ -0,0 +1,17 @@
import { LightningConsumer } from './lightning'
import UpArrow from '../svgs/lightning-arrow.svg'
import styles from './upvote.module.css'
export default function UpVote ({ className }) {
return (
<LightningConsumer>
{({ strike }) =>
<UpArrow
width={24}
height={24}
className={`${styles.upvote} ${className || ''}`}
onClick={strike}
/>}
</LightningConsumer>
)
}

View File

@ -0,0 +1,9 @@
.upvote {
fill: grey;
min-width: fit-content;
}
.upvote:hover {
fill: darkgray;
cursor: pointer;
}

31
components/user-header.js Normal file
View File

@ -0,0 +1,31 @@
import Link from 'next/link'
import { useRouter } from 'next/router'
import Nav from 'react-bootstrap/Nav'
export default function UserHeader ({ user }) {
const router = useRouter()
return (
<>
<h1>@{user.name} <small className='text-success'>[{user.stacked} stacked, {user.sats} sats]</small></h1>
<Nav
activeKey={router.asPath}
>
<Nav.Item>
<Link href={'/' + user.name} passHref>
<Nav.Link>{user.nitems} posts</Nav.Link>
</Link>
</Nav.Item>
<Nav.Item>
<Link href={'/' + user.name + '/comments'} passHref>
<Nav.Link>{user.ncomments} comments</Nav.Link>
</Link>
</Nav.Item>
<Nav.Item>
<Link href={'/' + user.name + '/sativity'} passHref>
<Nav.Link>sativity</Nav.Link>
</Link>
</Nav.Item>
</Nav>
</>
)
}

24
fragments/items.js Normal file
View File

@ -0,0 +1,24 @@
import { gql } from '@apollo/client'
export const ITEM_FIELDS = gql`
fragment ItemFields on Item {
id
parentId
createdAt
title
url
user {
name
}
sats
ncomments
}`
export const ITEMS_FEED = gql`
${ITEM_FIELDS}
{
items {
...ItemFields
}
}`

View File

@ -43,4 +43,4 @@
"prisma": "2.19.0",
"standard": "^16.0.3"
}
}
}

View File

@ -1,3 +1,52 @@
export default function User () {
return <div>hi</div>
import Layout from '../components/layout'
import Items from '../components/items'
import { ITEM_FIELDS } from '../fragments/items'
import { gql } from '@apollo/client'
import ApolloClient from '../api/client'
import UserHeader from '../components/user-header'
export async function getServerSideProps ({ params }) {
const { error, data: { user } } = await ApolloClient.query({
query:
gql`{
user(name: "${params.username}") {
id
createdAt
name
nitems
ncomments
stacked
sats
}
}`
})
if (!user || error) {
return {
notFound: true
}
}
return {
props: {
user
}
}
}
export default function User ({ user }) {
const query = gql`
${ITEM_FIELDS}
{
items: userItems(userId: ${user.id}) {
...ItemFields
}
}
`
return (
<Layout>
<UserHeader user={user} />
<Items query={query} />
</Layout>
)
}

View File

@ -0,0 +1,52 @@
import Layout from '../../components/layout'
import Comments from '../../components/comments'
import { COMMENT_FIELDS } from '../../fragments/comments'
import { gql } from '@apollo/client'
import ApolloClient from '../../api/client'
import UserHeader from '../../components/user-header'
export async function getServerSideProps ({ params }) {
const { error, data: { user } } = await ApolloClient.query({
query:
gql`{
user(name: "${params.username}") {
id
createdAt
name
nitems
ncomments
stacked
sats
}
}`
})
if (!user || error) {
return {
notFound: true
}
}
return {
props: {
user
}
}
}
export default function User ({ user }) {
const query = gql`
${COMMENT_FIELDS}
{
comments: userComments(userId: ${user.id}) {
...CommentFields
}
}
`
return (
<Layout>
<UserHeader user={user} />
<Comments query={query} includeParent noReply />
</Layout>
)
}

View File

@ -1,95 +1,11 @@
import Layout from '../components/layout'
import React from 'react'
import Items from '../components/items'
// function Users () {
// const { loading, error, data } = useQuery(gql`{ users { id, name } }`)
// if (error) return <div>Failed to load</div>
// if (loading) return <div>Loading...</div>
// const { users } = data
// return (
// <div>
// {users.map(user => (
// <div key={user.id}>{user.name}</div>
// ))}
// </div>
// )
// }
// function NewItem ({ parentId }) {
// const [session] = useSession()
// const [createItem] = useMutation(
// gql`
// mutation CreateItem($text: String!, $parentId: ID) {
// createItem(text: $text, parentId: $parentId) {
// id
// }
// }`, {
// update (cache, { data: { createItem } }) {
// cache.modify({
// fields: {
// items (existingItems = [], { readField }) {
// const newItemRef = cache.writeFragment({
// data: createItem,
// fragment: gql`
// fragment NewItem on Item {
// id
// user {
// name
// }
// text
// depth
// }
// `
// })
// for (let i = 0; i < existingItems.length; i++) {
// if (readField('id', existingItems[i]) === parentId) {
// return [...existingItems.slice(0, i), newItemRef, ...existingItems.slice(i)]
// }
// }
// return [newItemRef, ...existingItems]
// }
// }
// })
// }
// })
// const [open, setOpen] = useState(false)
// if (!session) return null
// if (!open) {
// return (
// <div onClick={() => setOpen(true)}>
// {parentId ? 'reply' : 'submit'}
// </div>
// )
// }
// let text
// return (
// <form
// style={{ marginLeft: '5px' }}
// onSubmit={e => {
// e.preventDefault()
// createItem({ variables: { text: text.value, parentId } })
// setOpen(false)
// text.value = ''
// }}
// >
// <textarea
// ref={node => {
// text = node
// }}
// />
// <button type='submit'>Submit</button>
// </form>
// )
// }
import { ITEMS_FEED } from '../fragments/items'
export default function Index () {
return (
<Layout>
<Items />
<Items query={ITEMS_FEED} rank />
</Layout>
)
}

View File

@ -6,6 +6,7 @@ import Reply from '../../components/reply'
import Comment from '../../components/comment'
import Text from '../../components/text'
import Comments from '../../components/comments'
import { COMMENTS } from '../../fragments/comments'
export async function getServerSideProps ({ params }) {
const { error, data: { item } } = await ApolloClient.query({
@ -41,6 +42,14 @@ export async function getServerSideProps ({ params }) {
}
export default function FullItem ({ item }) {
const commentsQuery = gql`
${COMMENTS}
{
comments(parentId: ${item.id}) {
...CommentsRecursive
}
}`
return (
<Layout>
{item.parentId
@ -53,7 +62,9 @@ export default function FullItem ({ item }) {
</Item>
</>
)}
<Comments parentId={item.id} />
<div className='mt-5'>
<Comments query={commentsQuery} />
</div>
</Layout>
)
}

View File

@ -20,6 +20,7 @@ model User {
image String?
items Item[]
messages Message[]
votes Vote[]
@@map(name: "users")
}
@ -43,12 +44,27 @@ model Item {
parent Item? @relation("ParentChildren", fields: [parentId], references: [id])
parentId Int?
children Item[] @relation("ParentChildren")
votes Vote[]
path Unsupported("LTREE")?
@@index([userId])
@@index([parentId])
}
model Vote {
id Int @id @default(autoincrement())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
sats Int
item Item @relation(fields: [itemId], references: [id])
itemId Int
user User @relation(fields: [userId], references: [id])
userId Int
@@index([itemId])
@@index([userId])
}
model Account {
id Int @id @default(autoincrement())
createdAt DateTime @default(now()) @map(name: "created_at")

BIN
public/clouds.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -16,7 +16,7 @@ $form-feedback-icon-invalid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3
$line-height-base: 1.75;
$input-btn-padding-y: .42rem;
$input-btn-padding-x: .84rem;
$btn-padding-y: .5rem;
$btn-padding-y: .42rem;
$btn-padding-x: 1.1rem;
$btn-font-weight: bold;
$btn-focus-width: 0;
@ -59,53 +59,26 @@ $container-max-widths: (
color: #ffffff;
}
.flashit{
animation: flash ease-out 7s infinite;
animation-delay: -3s;
.nav-link.active {
font-weight: bold;
}
@keyframes flash {
from { opacity: 0; }
42% { opacity: 0; }
43% { opacity: 0.6; }
44% { opacity: 0.2; }
46% { opacity: 1; }
50% { opacity: 0; }
92% { opacity: 0; }
93% { opacity: 0.6; }
94% { opacity: 0.2; }
96% { opacity: 1; }
to { opacity: 0; }
from { filter: brightness(1); background-position: 0 0;}
2% { filter: brightness(2.3); }
4% { filter: brightness(1.4); }
8% { filter: brightness(3); }
16% { filter: brightness(1); }
to { filter: brightness(1); background-position: 250px 0;}
}
.morphit{
animation: flash ease-out 7s infinite;
animation-delay: -3s;
}
@keyframes flash {
from { opacity: 0; }
42% { opacity: 0; }
43% { opacity: 0.6; }
44% { opacity: 0.2; }
46% { opacity: 1; }
50% { opacity: 0; }
92% { opacity: 0; }
93% { opacity: 0.6; }
94% { opacity: 0.2; }
96% { opacity: 1; }
to { opacity: 0; }
}
.fadeOut {
animation: fadeOut ease 1s;
}
@keyframes fadeOut {
0% {
opacity:1;
}
100% {
opacity:0;
}
}
.clouds {
animation: flash ease-out 3.5s infinite;
background: url('/clouds.jpeg');
background-color: grey;
background-repeat: repeat;
background-origin: content-box;
background-size: cover;
background-attachment: fixed;
opacity: .2;
}

View File