a bunch of new stuff
This commit is contained in:
parent
8ecc81f3f7
commit
ec3f6b922d
|
@ -54,46 +54,55 @@ function nestComments (flat, parentId) {
|
||||||
return [result, added]
|
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 {
|
export default {
|
||||||
Query: {
|
Query: {
|
||||||
items: async (parent, args, { models }) => {
|
items: async (parent, args, { models }) => {
|
||||||
return await models.$queryRaw(`
|
return await models.$queryRaw(`
|
||||||
SELECT id, "created_at" as "createdAt", title, url, text,
|
${SELECT}
|
||||||
"userId", nlevel(path)-1 AS depth, ltree2text("path") AS "path"
|
|
||||||
FROM "Item"
|
FROM "Item"
|
||||||
WHERE "parentId" IS NULL`)
|
WHERE "parentId" IS NULL`)
|
||||||
},
|
},
|
||||||
item: async (parent, { id }, { models }) => {
|
item: async (parent, { id }, { models }) => {
|
||||||
const res = await models.$queryRaw(`
|
return (await models.$queryRaw(`
|
||||||
SELECT id, "created_at" as "createdAt", title, url, text,
|
${SELECT}
|
||||||
"parentId", "userId", nlevel(path)-1 AS depth, ltree2text("path") AS "path"
|
|
||||||
FROM "Item"
|
FROM "Item"
|
||||||
WHERE id = ${id}`)
|
WHERE id = ${id}`))[0]
|
||||||
return res.length ? res[0] : null
|
|
||||||
},
|
},
|
||||||
flatcomments: async (parent, { parentId }, { models }) => {
|
userItems: async (parent, { userId }, { models }) => {
|
||||||
return await models.$queryRaw(`
|
return await models.$queryRaw(`
|
||||||
SELECT id, "created_at" as "createdAt", text, "parentId",
|
${SELECT}
|
||||||
"userId", nlevel(path)-1 AS depth, ltree2text("path") AS "path"
|
|
||||||
FROM "Item"
|
FROM "Item"
|
||||||
WHERE path <@ (SELECT path FROM "Item" where id = ${parentId}) AND id != ${parentId}
|
WHERE "userId" = ${userId} AND "parentId" IS NULL
|
||||||
ORDER BY "path"`)
|
ORDER BY created_at DESC`)
|
||||||
},
|
},
|
||||||
comments: async (parent, { parentId }, { models }) => {
|
comments: async (parent, { parentId }, { models }) => {
|
||||||
const flat = await models.$queryRaw(`
|
const flat = await models.$queryRaw(`
|
||||||
SELECT id, "created_at" as "createdAt", text, "parentId",
|
${SELECT}
|
||||||
"userId", nlevel(path)-1 AS depth, ltree2text("path") AS "path"
|
|
||||||
FROM "Item"
|
FROM "Item"
|
||||||
WHERE path <@ (SELECT path FROM "Item" where id = ${parentId}) AND id != ${parentId}
|
WHERE path <@ (SELECT path FROM "Item" where id = ${parentId}) AND id != ${parentId}
|
||||||
ORDER BY "path"`)
|
ORDER BY "path"`)
|
||||||
return nestComments(flat, parentId)[0]
|
return nestComments(flat, parentId)[0]
|
||||||
},
|
},
|
||||||
root: async (parent, { id }, { models }) => {
|
userComments: async (parent, { userId }, { models }) => {
|
||||||
const res = await models.$queryRaw(`
|
return await models.$queryRaw(`
|
||||||
SELECT id, title
|
${SELECT}
|
||||||
FROM "Item"
|
FROM "Item"
|
||||||
WHERE id = (SELECT ltree2text(subltree(path, 0, 1))::integer FROM "Item" WHERE id = ${id})`)
|
WHERE "userId" = ${userId} AND "parentId" IS NOT NULL
|
||||||
return res.length ? res[0] : 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]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -2,18 +2,22 @@ export default {
|
||||||
Query: {
|
Query: {
|
||||||
me: async (parent, args, { models, me }) =>
|
me: async (parent, args, { models, me }) =>
|
||||||
me ? await models.user.findUnique({ where: { id: me.id } }) : null,
|
me ? await models.user.findUnique({ where: { id: me.id } }) : null,
|
||||||
user: async (parent, { id }, { models }) =>
|
user: async (parent, { name }, { models }) => {
|
||||||
await models.user.findUnique({ where: { id } }),
|
console.log(name)
|
||||||
|
return await models.user.findUnique({ where: { name } })
|
||||||
|
},
|
||||||
users: async (parent, args, { models }) =>
|
users: async (parent, args, { models }) =>
|
||||||
await models.user.findMany()
|
await models.user.findMany()
|
||||||
},
|
},
|
||||||
|
|
||||||
User: {
|
User: {
|
||||||
messages: async (user, args, { models }) =>
|
nitems: async (user, args, { models }) => {
|
||||||
await models.message.findMany({
|
return await models.item.count({ where: { userId: user.id, parentId: null } })
|
||||||
where: {
|
},
|
||||||
userId: user.id
|
ncomments: async (user, args, { models }) => {
|
||||||
}
|
return await models.item.count({ where: { userId: user.id, parentId: { not: null } } })
|
||||||
})
|
},
|
||||||
|
stacked: () => 0,
|
||||||
|
sats: () => 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,9 @@ export default gql`
|
||||||
extend type Query {
|
extend type Query {
|
||||||
items: [Item!]!
|
items: [Item!]!
|
||||||
item(id: ID!): Item
|
item(id: ID!): Item
|
||||||
|
userItems(userId: ID!): [Item!]
|
||||||
comments(parentId: ID!): [Item!]!
|
comments(parentId: ID!): [Item!]!
|
||||||
flatcomments(parentId: ID!): [Item!]!
|
userComments(userId: ID!): [Item!]
|
||||||
root(id: ID!): Item
|
root(id: ID!): Item
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +14,7 @@ export default gql`
|
||||||
createLink(title: String!, url: String): Item!
|
createLink(title: String!, url: String): Item!
|
||||||
createDiscussion(title: String!, text: String): Item!
|
createDiscussion(title: String!, text: String): Item!
|
||||||
createComment(text: String!, parentId: ID!): Item!
|
createComment(text: String!, parentId: ID!): Item!
|
||||||
|
vote(sats: Int): Int!
|
||||||
}
|
}
|
||||||
|
|
||||||
type Item {
|
type Item {
|
||||||
|
|
|
@ -3,13 +3,16 @@ import { gql } from 'apollo-server-micro'
|
||||||
export default gql`
|
export default gql`
|
||||||
extend type Query {
|
extend type Query {
|
||||||
me: User
|
me: User
|
||||||
user(id: ID!): User
|
user(name: String): User
|
||||||
users: [User!]
|
users: [User!]
|
||||||
}
|
}
|
||||||
|
|
||||||
type User {
|
type User {
|
||||||
id: ID!
|
id: ID!
|
||||||
name: String
|
name: String
|
||||||
messages: [Message!]
|
nitems: Int!
|
||||||
|
ncomments: Int!
|
||||||
|
stacked: Int!
|
||||||
|
sats: Int!
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import itemStyles from './item.module.css'
|
import itemStyles from './item.module.css'
|
||||||
import styles from './comment.module.css'
|
import styles from './comment.module.css'
|
||||||
import UpVote from '../svgs/lightning-arrow.svg'
|
|
||||||
import Text from './text'
|
import Text from './text'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import Reply from './reply'
|
import Reply from './reply'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { gql, useQuery } from '@apollo/client'
|
import { gql, useQuery } from '@apollo/client'
|
||||||
import { timeSince } from '../lib/time'
|
import { timeSince } from '../lib/time'
|
||||||
|
import UpVote from './upvote'
|
||||||
|
|
||||||
function Parent ({ item }) {
|
function Parent ({ item }) {
|
||||||
const { data } = useQuery(
|
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)
|
const [reply, setReply] = useState(replyOpen)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={`${itemStyles.item} ${styles.item}`}>
|
<div />
|
||||||
<UpVote width={24} height={24} className={`${itemStyles.upvote} ${styles.upvote}`} />
|
<div>
|
||||||
<div className={itemStyles.hunk}>
|
<div className={`${itemStyles.item} ${styles.item}`}>
|
||||||
<div className={itemStyles.other}>
|
<UpVote className={styles.upvote} />
|
||||||
<Link href={`/@${item.user.name}`} passHref>
|
<div className={itemStyles.hunk}>
|
||||||
<a>@{item.user.name}</a>
|
<div className={itemStyles.other}>
|
||||||
</Link>
|
<Link href={`/${item.user.name}`} passHref>
|
||||||
<span> </span>
|
<a>@{item.user.name}</a>
|
||||||
<span>{timeSince(new Date(item.createdAt))}</span>
|
</Link>
|
||||||
<span> \ </span>
|
<span> </span>
|
||||||
<span>{item.sats} sats</span>
|
<span>{timeSince(new Date(item.createdAt))}</span>
|
||||||
<span> \ </span>
|
<span> \ </span>
|
||||||
<Link href={`/items/${item.id}`} passHref>
|
<span>{item.sats} sats</span>
|
||||||
<a className='text-reset'>{item.ncomments} replies</a>
|
<span> \ </span>
|
||||||
</Link>
|
<Link href={`/items/${item.id}`} passHref>
|
||||||
{includeParent && <Parent item={item} />}
|
<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>
|
||||||
<div className={styles.text}>
|
</div>
|
||||||
<Text>{item.text}</Text>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
<div className={`${itemStyles.children} ${styles.children}`}>
|
</>
|
||||||
<div
|
)
|
||||||
className={`${itemStyles.other} ${styles.reply}`}
|
}
|
||||||
onClick={() => setReply(!reply)}
|
|
||||||
>
|
export function CommentSkeleton ({ skeletonChildren }) {
|
||||||
{reply ? 'cancel' : 'reply'}
|
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>
|
</div>
|
||||||
{reply && <Reply parentId={item.id} onSuccess={() => setReply(replyOpen || false)} cacheId={cacheId} />}
|
</div>
|
||||||
{children}
|
<div className={`${itemStyles.children} ${styles.children}`}>
|
||||||
<div className={styles.comments}>
|
<div className={styles.comments}>
|
||||||
{item.comments
|
{comments
|
||||||
? item.comments.map((item) => (
|
? comments.map((_, i) => (
|
||||||
<Comment key={item.id} item={item} />
|
<CommentSkeleton key={i} />
|
||||||
))
|
))
|
||||||
: null}
|
: null}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -23,4 +23,14 @@
|
||||||
.comments {
|
.comments {
|
||||||
margin-left: 16px;
|
margin-left: 16px;
|
||||||
margin-top: .5rem;
|
margin-top: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.skeleton .hunk {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton .text {
|
||||||
|
height: 100px;
|
||||||
|
border-radius: .4rem;
|
||||||
}
|
}
|
|
@ -1,28 +1,23 @@
|
||||||
import { useQuery, gql } from '@apollo/client'
|
import { useQuery } from '@apollo/client'
|
||||||
import Comment from './comment'
|
import Comment, { CommentSkeleton } from './comment'
|
||||||
import { COMMENTS } from '../fragments'
|
|
||||||
|
|
||||||
export default function Comments ({ parentId }) {
|
export default function Comments ({ query, ...props }) {
|
||||||
const { data } = useQuery(
|
const { loading, error, data } = useQuery(query)
|
||||||
gql`
|
|
||||||
${COMMENTS}
|
|
||||||
|
|
||||||
{
|
if (error) return <div>Failed to load!</div>
|
||||||
comments(parentId: ${parentId}) {
|
if (loading) {
|
||||||
...CommentsRecursive
|
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 (
|
return data.comments.map(item => (
|
||||||
<div className='mt-5'>
|
<div key={item.id} className='mt-2'>
|
||||||
{data.comments.map(item => (
|
<Comment item={item} {...props} />
|
||||||
<div key={item.id} className='mt-2'>
|
|
||||||
<Comment item={item} />
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,11 @@ export default function Header () {
|
||||||
if (session) {
|
if (session) {
|
||||||
return (
|
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.Item>
|
||||||
<Nav.Link onClick={signOut}>logout</Nav.Link>
|
<Nav.Link onClick={signOut}>logout</Nav.Link>
|
||||||
</Nav.Item>
|
</Nav.Item>
|
||||||
|
@ -36,7 +40,7 @@ export default function Header () {
|
||||||
<Link href='/' passHref>
|
<Link href='/' passHref>
|
||||||
<Navbar.Brand className={styles.brand}>STACKER NEWS</Navbar.Brand>
|
<Navbar.Brand className={styles.brand}>STACKER NEWS</Navbar.Brand>
|
||||||
</Link>
|
</Link>
|
||||||
<Nav className='mr-auto align-items-center' activeKey={router.pathname}>
|
<Nav className='mr-auto align-items-center' activeKey={router.asPath}>
|
||||||
<Nav.Item>
|
<Nav.Item>
|
||||||
<Link href='/recent' passHref>
|
<Link href='/recent' passHref>
|
||||||
<Nav.Link>recent</Nav.Link>
|
<Nav.Link>recent</Nav.Link>
|
||||||
|
@ -48,7 +52,7 @@ export default function Header () {
|
||||||
</Link>
|
</Link>
|
||||||
</Nav.Item>
|
</Nav.Item>
|
||||||
</Nav>
|
</Nav>
|
||||||
<Nav className='ml-auto align-items-center'>
|
<Nav className='ml-auto align-items-center' activeKey={router.asPath}>
|
||||||
<Corner />
|
<Corner />
|
||||||
</Nav>
|
</Nav>
|
||||||
</Container>
|
</Container>
|
||||||
|
|
|
@ -8,4 +8,6 @@
|
||||||
|
|
||||||
.navbar {
|
.navbar {
|
||||||
padding: 0rem 1.75rem;
|
padding: 0rem 1.75rem;
|
||||||
|
background-color: transparent !important;
|
||||||
|
background-image: linear-gradient(#FADA5E, #FADA5E, transparent)
|
||||||
}
|
}
|
|
@ -1,13 +1,19 @@
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import UpVote from '../svgs/lightning-arrow.svg'
|
|
||||||
import styles from './item.module.css'
|
import styles from './item.module.css'
|
||||||
import { timeSince } from '../lib/time'
|
import { timeSince } from '../lib/time'
|
||||||
|
import UpVote from './upvote'
|
||||||
|
|
||||||
export default function Item ({ item, children }) {
|
export default function Item ({ item, rank, children }) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{rank
|
||||||
|
? (
|
||||||
|
<div className={styles.rank}>
|
||||||
|
{rank}
|
||||||
|
</div>)
|
||||||
|
: <div />}
|
||||||
<div className={styles.item}>
|
<div className={styles.item}>
|
||||||
<UpVote width={24} height={24} className={styles.upvote} />
|
<UpVote />
|
||||||
<div className={styles.hunk}>
|
<div className={styles.hunk}>
|
||||||
<div className={`${styles.main} flex-wrap flex-md-nowrap`}>
|
<div className={`${styles.main} flex-wrap flex-md-nowrap`}>
|
||||||
<Link href={`/items/${item.id}`} passHref>
|
<Link href={`/items/${item.id}`} passHref>
|
||||||
|
@ -22,7 +28,7 @@ export default function Item ({ item, children }) {
|
||||||
<a className='text-reset'>{item.ncomments} comments</a>
|
<a className='text-reset'>{item.ncomments} comments</a>
|
||||||
</Link>
|
</Link>
|
||||||
<span> \ </span>
|
<span> \ </span>
|
||||||
<Link href={`/@${item.user.name}`} passHref>
|
<Link href={`/${item.user.name}`} passHref>
|
||||||
<a>@{item.user.name}</a>
|
<a>@{item.user.name}</a>
|
||||||
</Link>
|
</Link>
|
||||||
<span> </span>
|
<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>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -1,13 +1,3 @@
|
||||||
.upvote {
|
|
||||||
fill: grey;
|
|
||||||
min-width: fit-content;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upvote:hover {
|
|
||||||
fill: darkgray;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
|
@ -45,4 +35,47 @@
|
||||||
.children {
|
.children {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
margin-left: 24px;
|
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;
|
||||||
}
|
}
|
|
@ -1,35 +1,27 @@
|
||||||
import { gql, useQuery } from '@apollo/client'
|
import { useQuery } from '@apollo/client'
|
||||||
import React from 'react'
|
import Item, { ItemSkeleton } from './item'
|
||||||
import Item from './item'
|
|
||||||
import styles from './items.module.css'
|
import styles from './items.module.css'
|
||||||
|
|
||||||
export default function Items () {
|
export default function Items ({ query, rank }) {
|
||||||
const { loading, error, data } = useQuery(
|
const { loading, error, data } = useQuery(query)
|
||||||
gql`
|
if (error) return <div>Failed to load!</div>
|
||||||
{ items {
|
if (loading) {
|
||||||
id
|
const items = new Array(30).fill(null)
|
||||||
createdAt
|
|
||||||
title
|
return (
|
||||||
url
|
<div className={styles.grid}>
|
||||||
user {
|
{items.map((_, i) => (
|
||||||
name
|
<ItemSkeleton rank={i + 1} key={i} />
|
||||||
}
|
))}
|
||||||
sats
|
</div>
|
||||||
ncomments
|
)
|
||||||
} }`
|
}
|
||||||
)
|
|
||||||
if (error) return <div>Failed to load</div>
|
|
||||||
if (loading) return <div>Loading...</div>
|
|
||||||
const { items } = data
|
const { items } = data
|
||||||
return (
|
return (
|
||||||
<div className={styles.grid}>
|
<div className={styles.grid}>
|
||||||
{items.map((item, i) => (
|
{items.map((item, i) => (
|
||||||
<React.Fragment key={item.id}>
|
<Item item={item} rank={rank && i + 1} key={item.id} />
|
||||||
<div className={styles.rank} key={item.id}>
|
|
||||||
{i + 1}
|
|
||||||
</div>
|
|
||||||
<Item item={item} />
|
|
||||||
</React.Fragment>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,12 +1,4 @@
|
||||||
.grid {
|
.grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto 1fr;
|
grid-template-columns: auto 1fr;
|
||||||
}
|
|
||||||
|
|
||||||
.rank {
|
|
||||||
font-weight: 600;
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
color: grey;
|
|
||||||
font-size: 90%;
|
|
||||||
}
|
}
|
|
@ -1,19 +1,20 @@
|
||||||
import Header from './header'
|
import Header from './header'
|
||||||
import Container from 'react-bootstrap/Container'
|
import Container from 'react-bootstrap/Container'
|
||||||
import { Lightning } from './lightning'
|
import { LightningProvider } from './lightning'
|
||||||
|
|
||||||
export default function Layout ({ noContain, children }) {
|
export default function Layout ({ noContain, children }) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Header />
|
<LightningProvider>
|
||||||
<Lightning />
|
<Header />
|
||||||
{noContain
|
{noContain
|
||||||
? children
|
? children
|
||||||
: (
|
: (
|
||||||
<Container className='my-2 py-2 px-sm-0'>
|
<Container className='my-2 py-2 px-sm-0'>
|
||||||
{children}
|
{children}
|
||||||
</Container>
|
</Container>
|
||||||
)}
|
)}
|
||||||
|
</LightningProvider>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 () {
|
export function Lightning () {
|
||||||
const canvasRef = useRef(null)
|
const canvasRef = useRef(null)
|
||||||
|
@ -14,21 +39,14 @@ export function Lightning () {
|
||||||
const bolt = new Bolt(context, {
|
const bolt = new Bolt(context, {
|
||||||
startPoint: [Math.random() * (canvas.width * 0.5) + (canvas.width * 0.25), 0],
|
startPoint: [Math.random() * (canvas.width * 0.5) + (canvas.width * 0.25), 0],
|
||||||
length: canvas.height,
|
length: canvas.height,
|
||||||
speed: options.speed,
|
speed: 100,
|
||||||
spread: options.spread,
|
spread: 30,
|
||||||
branches: options.branching
|
branches: 20
|
||||||
})
|
})
|
||||||
bolt.draw()
|
bolt.draw()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return <canvas className='position-absolute' ref={canvasRef} style={{ zIndex: -1 }} />
|
return <canvas className='position-fixed' ref={canvasRef} style={{ zIndex: -1 }} />
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize options.
|
|
||||||
const options = {
|
|
||||||
speed: 80,
|
|
||||||
spread: 40,
|
|
||||||
branching: 5
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function Bolt (ctx, options) {
|
function Bolt (ctx, options) {
|
||||||
|
@ -40,6 +58,7 @@ function Bolt (ctx, options) {
|
||||||
spread: 50,
|
spread: 50,
|
||||||
branches: 10,
|
branches: 10,
|
||||||
maxBranches: 10,
|
maxBranches: 10,
|
||||||
|
lineWidth: 3,
|
||||||
...options
|
...options
|
||||||
}
|
}
|
||||||
this.point = [this.options.startPoint[0], this.options.startPoint[1]]
|
this.point = [this.options.startPoint[0], this.options.startPoint[1]]
|
||||||
|
@ -59,7 +78,7 @@ function Bolt (ctx, options) {
|
||||||
ctx.shadowOffsetY = 0
|
ctx.shadowOffsetY = 0
|
||||||
ctx.fillStyle = 'rgba(250, 250, 250, 1)'
|
ctx.fillStyle = 'rgba(250, 250, 250, 1)'
|
||||||
ctx.strokeStyle = 'rgba(250, 218, 94, 1)'
|
ctx.strokeStyle = 'rgba(250, 218, 94, 1)'
|
||||||
ctx.lineWidth = 2
|
ctx.lineWidth = this.options.lineWidth
|
||||||
this.draw = (isChild) => {
|
this.draw = (isChild) => {
|
||||||
ctx.beginPath()
|
ctx.beginPath()
|
||||||
ctx.moveTo(this.point[0], this.point[1])
|
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)
|
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) {
|
if (rand(0, 99) < this.options.branches && this.children.length < this.options.maxBranches) {
|
||||||
this.children.push(new Bolt(ctx, {
|
this.children.push(new Bolt(ctx, {
|
||||||
startPoint: [this.point[0], this.point[1]],
|
startPoint: [this.point[0], this.point[1]],
|
||||||
|
@ -86,7 +108,8 @@ function Bolt (ctx, options) {
|
||||||
resistance: this.options.resistance,
|
resistance: this.options.resistance,
|
||||||
speed: this.options.speed - 2,
|
speed: this.options.speed - 2,
|
||||||
spread: this.options.spread - 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) {
|
if (d < this.options.length) {
|
||||||
window.requestAnimationFrame(() => { this.draw() })
|
window.requestAnimationFrame(() => { this.draw() })
|
||||||
} else {
|
} else {
|
||||||
|
ctx.canvas.style.opacity = 1
|
||||||
this.fade()
|
this.fade()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fade = function () {
|
this.fade = function () {
|
||||||
ctx.shadowColor = 'rgba(250, 250, 250, .5)'
|
ctx.canvas.style.opacity -= 0.04
|
||||||
ctx.fillStyle = 'rgba(250, 250, 250, .05)'
|
if (ctx.canvas.style.opacity <= 0) {
|
||||||
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.clearRect(0, 0, window.innerWidth, window.innerHeight)
|
ctx.clearRect(0, 0, window.innerWidth, window.innerHeight)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Form, Input, SubmitButton } from '../components/form'
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
import { gql, useMutation } from '@apollo/client'
|
import { gql, useMutation } from '@apollo/client'
|
||||||
import styles from './reply.module.css'
|
import styles from './reply.module.css'
|
||||||
import { COMMENTS } from '../fragments'
|
import { COMMENTS } from '../fragments/comments'
|
||||||
|
|
||||||
export const CommentSchema = Yup.object({
|
export const CommentSchema = Yup.object({
|
||||||
text: Yup.string().required('required').trim()
|
text: Yup.string().required('required').trim()
|
||||||
|
|
|
@ -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>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
.upvote {
|
||||||
|
fill: grey;
|
||||||
|
min-width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upvote:hover {
|
||||||
|
fill: darkgray;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
|
@ -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>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}`
|
|
@ -43,4 +43,4 @@
|
||||||
"prisma": "2.19.0",
|
"prisma": "2.19.0",
|
||||||
"standard": "^16.0.3"
|
"standard": "^16.0.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,52 @@
|
||||||
export default function User () {
|
import Layout from '../components/layout'
|
||||||
return <div>hi</div>
|
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>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,95 +1,11 @@
|
||||||
import Layout from '../components/layout'
|
import Layout from '../components/layout'
|
||||||
import React from 'react'
|
|
||||||
import Items from '../components/items'
|
import Items from '../components/items'
|
||||||
|
import { ITEMS_FEED } from '../fragments/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>
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
|
|
||||||
export default function Index () {
|
export default function Index () {
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
<Items />
|
<Items query={ITEMS_FEED} rank />
|
||||||
</Layout>
|
</Layout>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import Reply from '../../components/reply'
|
||||||
import Comment from '../../components/comment'
|
import Comment from '../../components/comment'
|
||||||
import Text from '../../components/text'
|
import Text from '../../components/text'
|
||||||
import Comments from '../../components/comments'
|
import Comments from '../../components/comments'
|
||||||
|
import { COMMENTS } from '../../fragments/comments'
|
||||||
|
|
||||||
export async function getServerSideProps ({ params }) {
|
export async function getServerSideProps ({ params }) {
|
||||||
const { error, data: { item } } = await ApolloClient.query({
|
const { error, data: { item } } = await ApolloClient.query({
|
||||||
|
@ -41,6 +42,14 @@ export async function getServerSideProps ({ params }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function FullItem ({ item }) {
|
export default function FullItem ({ item }) {
|
||||||
|
const commentsQuery = gql`
|
||||||
|
${COMMENTS}
|
||||||
|
{
|
||||||
|
comments(parentId: ${item.id}) {
|
||||||
|
...CommentsRecursive
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
{item.parentId
|
{item.parentId
|
||||||
|
@ -53,7 +62,9 @@ export default function FullItem ({ item }) {
|
||||||
</Item>
|
</Item>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Comments parentId={item.id} />
|
<div className='mt-5'>
|
||||||
|
<Comments query={commentsQuery} />
|
||||||
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ model User {
|
||||||
image String?
|
image String?
|
||||||
items Item[]
|
items Item[]
|
||||||
messages Message[]
|
messages Message[]
|
||||||
|
votes Vote[]
|
||||||
|
|
||||||
@@map(name: "users")
|
@@map(name: "users")
|
||||||
}
|
}
|
||||||
|
@ -43,12 +44,27 @@ model Item {
|
||||||
parent Item? @relation("ParentChildren", fields: [parentId], references: [id])
|
parent Item? @relation("ParentChildren", fields: [parentId], references: [id])
|
||||||
parentId Int?
|
parentId Int?
|
||||||
children Item[] @relation("ParentChildren")
|
children Item[] @relation("ParentChildren")
|
||||||
|
votes Vote[]
|
||||||
path Unsupported("LTREE")?
|
path Unsupported("LTREE")?
|
||||||
|
|
||||||
@@index([userId])
|
@@index([userId])
|
||||||
@@index([parentId])
|
@@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 {
|
model Account {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
createdAt DateTime @default(now()) @map(name: "created_at")
|
createdAt DateTime @default(now()) @map(name: "created_at")
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
|
@ -16,7 +16,7 @@ $form-feedback-icon-invalid: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3
|
||||||
$line-height-base: 1.75;
|
$line-height-base: 1.75;
|
||||||
$input-btn-padding-y: .42rem;
|
$input-btn-padding-y: .42rem;
|
||||||
$input-btn-padding-x: .84rem;
|
$input-btn-padding-x: .84rem;
|
||||||
$btn-padding-y: .5rem;
|
$btn-padding-y: .42rem;
|
||||||
$btn-padding-x: 1.1rem;
|
$btn-padding-x: 1.1rem;
|
||||||
$btn-font-weight: bold;
|
$btn-font-weight: bold;
|
||||||
$btn-focus-width: 0;
|
$btn-focus-width: 0;
|
||||||
|
@ -59,53 +59,26 @@ $container-max-widths: (
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flashit{
|
.nav-link.active {
|
||||||
animation: flash ease-out 7s infinite;
|
font-weight: bold;
|
||||||
animation-delay: -3s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes flash {
|
@keyframes flash {
|
||||||
from { opacity: 0; }
|
from { filter: brightness(1); background-position: 0 0;}
|
||||||
42% { opacity: 0; }
|
2% { filter: brightness(2.3); }
|
||||||
43% { opacity: 0.6; }
|
4% { filter: brightness(1.4); }
|
||||||
44% { opacity: 0.2; }
|
8% { filter: brightness(3); }
|
||||||
46% { opacity: 1; }
|
16% { filter: brightness(1); }
|
||||||
50% { opacity: 0; }
|
to { filter: brightness(1); background-position: 250px 0;}
|
||||||
92% { opacity: 0; }
|
|
||||||
93% { opacity: 0.6; }
|
|
||||||
94% { opacity: 0.2; }
|
|
||||||
96% { opacity: 1; }
|
|
||||||
to { opacity: 0; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.morphit{
|
.clouds {
|
||||||
animation: flash ease-out 7s infinite;
|
animation: flash ease-out 3.5s infinite;
|
||||||
animation-delay: -3s;
|
background: url('/clouds.jpeg');
|
||||||
}
|
background-color: grey;
|
||||||
|
background-repeat: repeat;
|
||||||
@keyframes flash {
|
background-origin: content-box;
|
||||||
from { opacity: 0; }
|
background-size: cover;
|
||||||
42% { opacity: 0; }
|
background-attachment: fixed;
|
||||||
43% { opacity: 0.6; }
|
opacity: .2;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue