Skip to content

Commit

Permalink
timeline
Browse files Browse the repository at this point in the history
  • Loading branch information
yamader committed Jul 8, 2023
1 parent 54b8e93 commit 5be67ee
Show file tree
Hide file tree
Showing 12 changed files with 167 additions and 66 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"@radix-ui/react-tooltip": "1.0.6",
"ahooks": "3.7.8",
"jotai": "2.2.2",
"lucide-react": "0.259.0",
"mfm-js": "0.23.3",
"misskey-js": "0.0.16",
"react": "18.2.0",
Expand Down
11 changes: 11 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions src/app/(main)/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,11 @@ function HeaderLink({ href, children }: { href: string; children: string }) {
}

function UserMenu() {
const { logout } = useAuth()
const { account, logout } = useAuth()
const profile = useProfile()

const host = profile?.host ?? account?.host

return (
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
Expand All @@ -66,10 +68,10 @@ function UserMenu() {
{profile ? (
<Link
className="flex flex-col rounded-lg px-3 py-2 outline-none hover:bg-neutral-100 active:bg-neutral-200"
href={`/profile?user=@${profile.username}@${profile.host}`}>
href={`/profile?user=@${profile.username}@${host}`}>
<span className="overflow-hidden text-ellipsis text-lg font-bold">{profile.name}</span>
<span className="overflow-hidden text-ellipsis font-inter text-sm font-bold text-neutral-500">
@{profile.username}@{profile.host}
@{profile.username}@{host}
</span>
</Link>
) : (
Expand Down
34 changes: 21 additions & 13 deletions src/app/(main)/home/page.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,35 @@
"use client"

import { useState } from "react"
import { Loader2 } from "lucide-react"

import NotePreview from "~/components/NotePreview"
import { useTL } from "~/features/timeline/libs"
import NotePreview from "~/features/timeline/NotePreview"
import { useBottom } from "~/libs/utils"

// todo: TLの切り替え
export default function HomePage() {
const { notes, more } = useTL("homeTimeline")
const [loadingMore, setLoadingMore] = useState(false)

useBottom(() => {
setLoadingMore(true)
more().then(() => setLoadingMore(false))
})
useBottom(more)

return (
<div>
{notes.map((note, i) => (
<NotePreview note={note} key={i} />
))}
{loadingMore && <div>loading...</div>}
</div>
<>
<div className="rounded-xl border border-neutral-100 bg-white shadow">
<div className="flex justify-center">
<p className="border-x px-4 py-2">※これはHTLです</p>
</div>
<div className="flex flex-col">
{notes.map((note, i) => (
<div className="border-t" key={i}>
<NotePreview note={note} />
</div>
))}
</div>
</div>
<div className="mx-auto mb-8 mt-6 flex items-center gap-1 font-bold">
<Loader2 className="animate-spin" size={24} />
<p className="text-center">Loading...</p>
</div>
</>
)
}
8 changes: 5 additions & 3 deletions src/app/(main)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import Header from "./Header"

export default function MainLayout({ children }: { children: React.ReactNode }) {
return (
<div className="mx-auto flex h-full w-full max-w-4xl flex-col px-4">
<Header />
<div className="flex grow flex-col">{children}</div>
<div className="flex min-h-full flex-col bg-neutral-100">
<main className="mx-auto flex w-full max-w-4xl grow flex-col px-4">
<Header />
{children}
</main>
</div>
)
}
14 changes: 14 additions & 0 deletions src/components/FilePreview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { entities } from "misskey-js"
import Image from "next/image"

export default function FilePreview({ file }: { file: entities.DriveFile }) {
if (file.type.startsWith("image/")) {
return <Image src={file.url} width={125} height={125} alt="File" />
} else {
return (
<>
{file.url}({file.type})
</>
)
}
}
40 changes: 0 additions & 40 deletions src/components/NotePreview.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion src/features/auth/libs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function useLogin(login?: boolean) {
const client = useClient()

useEffect(() => {
if (login && client && !account) router.push("/login")
if (login && client && !account) router.push("/")
}, [login, client, account, router])

return account
Expand Down
5 changes: 5 additions & 0 deletions src/features/profile/libs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { atom, useAtomValue } from "jotai"
import { entities } from "misskey-js"

import { apiAtom } from "~/features/api/libs"
import { useLogin } from "~/features/auth/libs"

// utils

Expand All @@ -18,6 +19,10 @@ export function statusEmoji(status: entities.UserLite["onlineStatus"] = "unknown
}
}

export function profileLink(user: entities.UserLite) {
return `/profile?user=@${user.username}@${user.host}`
}

// atoms

export const profileAtom = atom(async get => {
Expand Down
85 changes: 85 additions & 0 deletions src/features/timeline/NotePreview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { MoreHorizontal, Plus, Repeat2, Reply } from "lucide-react"
import { entities } from "misskey-js"
import Image from "next/image"
import Link from "next/link"
import { memo } from "react"

import FilePreview from "~/components/FilePreview"
import { profileLink } from "~/features/profile/libs"
import { reltime } from "~/libs/utils"

type NotePreviewProps = {
note: entities.Note
renote?: entities.Note
}

const NotePreviewMemo = memo<NotePreviewProps>(function NP({ note }) {
return <NotePreview note={note} />
})

export default NotePreviewMemo

// Todo: まともなTLのデザイン
function NotePreview({ note, renote }: NotePreviewProps) {
if (note.renote && !note.text) {
return <NotePreview note={note.renote} renote={note} />
}

return (
<div className="p-3">
{renote && <RenoteHeader rn={renote} />}
<div className="flex gap-1.5">
<Link
className="m-1 h-fit w-fit overflow-hidden rounded-[48px] shadow transition-all hover:rounded-md"
href={profileLink(note.user)}>
<Image src={note.user.avatarUrl} width={48} height={48} alt="Icon" />
</Link>
<div className="flex w-full flex-col gap-0.5">
<div className="flex justify-between">
<div className="flex gap-1 font-bold">
<Link className="hover:underline" href={profileLink(note.user)}>
{note.user.name}
</Link>
<p>
<span>@{note.user.username}</span>
<span className="text-neutral-400">@{note.user.host}</span>
</p>
</div>
<p>{reltime(note.createdAt)}</p>
</div>
<p>{note.text}</p>
{!!note.files.length && (
// todo: grid layout
<div className="">
{note.files.map((file, i) => (
<div key={i}>
<FilePreview file={file} />
</div>
))}
</div>
)}
<div className="mt-1 flex gap-8">
<Reply size={20} />
<Repeat2 size={20} />
<Plus size={20} />
<MoreHorizontal size={20} />
</div>
</div>
</div>
</div>
)
}

function RenoteHeader({ rn }: { rn: entities.Note }) {
return (
<div className="mb-1 flex justify-between text-neutral-600">
<p className="ml-1 flex items-center gap-1 text-sm">
<Repeat2 size={16} />
<Link className="font-bold hover:underline" href={profileLink(rn.user)}>
{rn.user.name}
</Link>
</p>
<p>{reltime(rn.createdAt)}</p>
</div>
)
}
14 changes: 9 additions & 5 deletions src/features/timeline/libs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ export function useTL(channel: TLChanNames) {
const api = useAPI()
const [notes, setNotes] = useState<entities.Note[]>([])

useLogin(true)
const account = useLogin(true)
const host = account?.host ?? null

// first time
useEffect(() => {
Expand All @@ -19,20 +20,22 @@ export function useTL(channel: TLChanNames) {
const res = await api.request("notes/timeline", {
limit: 10,
})
res.forEach(note => (note.user.host ??= host))
setNotes(res)
})()
}, [api])
}, [api, host])

// streaming
useEffect(() => {
if (!stream) return
const conn = stream?.on("note", note => {
setNotes(notes => notes.concat(note))
note.user.host ??= host
setNotes(notes => [note, ...notes])
})
return () => {
conn?.off("note")
}
}, [stream])
}, [stream, host])

// scroll
const more = useCallback(async () => {
Expand All @@ -41,8 +44,9 @@ export function useTL(channel: TLChanNames) {
limit: 30,
untilId: notes[notes.length - 1].id,
})
res.forEach(note => (note.user.host ??= host))
setNotes(notes => notes.concat(res))
}, [api, notes])
}, [api, notes, host])

return { notes, more }
}
11 changes: 10 additions & 1 deletion src/libs/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { useScroll, useSize } from "ahooks"
import { useEffect, useState } from "react"

// utils

export function reltime(date: string) {
// todo: relative
return new Date(date).toISOString().slice(0, -1).split("T").join(" ")
}

// hooks

export function useClient() {
const [x, setX] = useState(false)
useEffect(() => setX(true), [])
Expand All @@ -15,6 +24,6 @@ export function useBottom(f: () => void) {
const pos = useScroll(document)

useEffect(() => {
if ((size?.height ?? 0) + (pos?.top ?? 0) === document.body.scrollHeight) f()
if ((size?.height ?? 0) + (pos?.top ?? 0) >= document.body.scrollHeight) f()
}, [size?.height, pos?.top, f])
}

0 comments on commit 5be67ee

Please sign in to comment.