{error}
diff --git a/src/app/feed/components/FeedView.ts b/src/app/feed/components/FeedView.ts
deleted file mode 100644
index b79acb9..0000000
--- a/src/app/feed/components/FeedView.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { useCallback, useRef, useState } from 'react'
-import { Post } from '../posts/posts.ts'
-
-const PageSize = 20
-
-export function useFeedViewModel(
- loadMore: (cursor: string | null, amount: number) => Promise
,
-) {
- const [pages, setPages] = useState([])
- const [hasMore, setHasMore] = useState(true)
- const [error, setError] = useState(null)
-
- const cursor = useRef(null)
- const loading = useRef(false)
-
- const loadNextPage = useCallback(async () => {
- if (loading.current || !hasMore || error) return
- loading.current = true
-
- try {
- const delay = new Promise((resolve) => setTimeout(resolve, 500))
- const pagePromise = loadMore(cursor.current, PageSize)
- const [page] = await Promise.all([pagePromise, delay])
- setHasMore(page.length >= PageSize)
- cursor.current = page.at(-1)?.postId ?? null
- setPages((prev) => [...prev, page])
- } catch (e: unknown) {
- const err = e as Error
- setError(err.message)
- } finally {
- loading.current = false
- }
- }, [loadMore, hasMore, error])
-
- return { pages, setPages, loadNextPage, error } as const
-}
diff --git a/src/app/feed/components/FeedView.tsx b/src/app/feed/components/FeedView.tsx
deleted file mode 100644
index d35256f..0000000
--- a/src/app/feed/components/FeedView.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import { useRef } from 'react'
-import { useIntersectionLoad } from '../../../hooks/useIntersectionLoad.ts'
-import { Post } from '../posts/posts.ts'
-import PostItem from './PostItem.tsx'
-
-interface FeedViewProps {
- pages: Post[][]
- onLoadMore: () => Promise
-}
-
-export default function FeedView({ pages, onLoadMore }: FeedViewProps) {
- const sentinelRef = useRef(null)
- const posts = pages.flat()
-
- useIntersectionLoad(onLoadMore, sentinelRef)
-
- return (
-
-
-
- {posts.map((post) => (
-
- ))}
-
-
-
-
- )
-}
diff --git a/src/app/feed/components/NewCommentWidget.tsx b/src/app/feed/components/NewCommentWidget.tsx
new file mode 100644
index 0000000..3d2e4ea
--- /dev/null
+++ b/src/app/feed/components/NewCommentWidget.tsx
@@ -0,0 +1,58 @@
+import { useState } from 'react'
+import FancyTextEditor, {
+ TextInputKeyDownEvent,
+} from '../../../components/inputs/FancyTextEditor.tsx'
+import Button from '../../../components/buttons/Button.tsx'
+import { useTranslations } from '../../i18n/translations.ts'
+
+interface NewCommentWidgetProps {
+ onSubmit: (content: string) => void
+ isSubmitting?: boolean
+}
+
+export default function NewCommentWidget({
+ onSubmit,
+ isSubmitting = false,
+}: NewCommentWidgetProps) {
+ const { t } = useTranslations()
+ const [content, setContent] = useState('')
+
+ const onContentInput = (value: string) => {
+ setContent(value)
+ }
+
+ const handleSubmit = () => {
+ if (!content.trim()) {
+ return
+ }
+
+ onSubmit(content)
+
+ setContent('')
+ }
+
+ const onInputKeyDown = (e: TextInputKeyDownEvent) => {
+ if (e.key === 'Enter' && e.ctrlKey) {
+ e.preventDefault()
+ handleSubmit()
+ }
+ }
+
+ return (
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/components/NewPostWidget.tsx b/src/app/feed/components/NewPostWidget.tsx
similarity index 87%
rename from src/components/NewPostWidget.tsx
rename to src/app/feed/components/NewPostWidget.tsx
index 7146bc7..e0bd87a 100644
--- a/src/components/NewPostWidget.tsx
+++ b/src/app/feed/components/NewPostWidget.tsx
@@ -1,8 +1,9 @@
import { useState } from 'react'
-import FancyTextEditor, { TextInputKeyDownEvent } from './inputs/FancyTextEditor.tsx'
-import Button from './buttons/Button.tsx'
-import { openFileDialog } from '../utils/openFileDialog.ts'
+import FancyTextEditor, { TextInputKeyDownEvent } from '../../../components/inputs/FancyTextEditor.tsx'
+import Button from '../../../components/buttons/Button.tsx'
+import { openFileDialog } from '../../../utils/openFileDialog.ts'
import makePica from 'pica'
+import { useTranslations } from '../../i18n/translations.ts'
interface NewPostWidgetProps {
onSubmit: (
@@ -22,6 +23,7 @@ interface Attachment {
}
export default function NewPostWidget({ onSubmit, isSubmitting = false }: NewPostWidgetProps) {
+ const { t } = useTranslations()
const [content, setContent] = useState('')
const [attachments, setAttachments] = useState([])
const [isPublic, setIsPublic] = useState(false)
@@ -72,7 +74,7 @@ export default function NewPostWidget({ onSubmit, isSubmitting = false }: NewPos
onInput={onContentInput}
onKeyDown={onInputKeyDown}
className="mb-3"
- placeholder="write something..."
+ placeholder={t('post.editor.placeholder')}
/>
{attachments.length > 0 && (
@@ -93,7 +95,7 @@ export default function NewPostWidget({ onSubmit, isSubmitting = false }: NewPos
@@ -196,7 +198,15 @@ async function optimizeImageSize(
dstCanvas.width = width
dstCanvas.height = height
- await pica.resize(srcCanvas, dstCanvas)
+ try {
+ // TODO resistFingerprinting in FF and other causes this to break.
+ // knowing this, i would still rather be able to post from other browsers for now
+ // and will hopefully find a better solution
+ await pica.resize(srcCanvas, dstCanvas)
+ } catch (e) {
+ console.error('cant resize image', e)
+ return file
+ }
let blob = await pica.toBlob(dstCanvas, outputType, quality)
diff --git a/src/app/feed/components/PostItem.tsx b/src/app/feed/components/PostItem.tsx
index 582e05b..db766f6 100644
--- a/src/app/feed/components/PostItem.tsx
+++ b/src/app/feed/components/PostItem.tsx
@@ -1,12 +1,24 @@
-import { Post, PostMedia } from '../posts/posts.ts'
-import { Link } from 'react-router-dom'
+import { PostMedia, PostReaction } from '../posts/posts.ts'
import { useEffect, useState } from 'react'
+import { Link } from 'react-router-dom'
+import { PostInfo } from '../posts/usePostViewModel.ts'
+import { useUserStore } from '../../user/user.ts'
interface PostItemProps {
- post: Post
+ post: PostInfo
+ reactions: PostReaction[]
+ addReaction: (emoji: string) => void
+ clearReaction: (emoji: string) => void
+ hideViewButton?: boolean
}
-export default function PostItem({ post }: PostItemProps) {
+export default function PostItem({
+ post,
+ reactions,
+ addReaction,
+ clearReaction,
+ hideViewButton = false,
+}: PostItemProps) {
const formattedDate = post.createdAt.toLocaleString('en-US', {
year: 'numeric',
month: 'short',
@@ -29,10 +41,15 @@ export default function PostItem({ post }: PostItemProps) {
return (
+ {post.possibleReactions.map((emoji) => {
+ const count = reactions.filter((r) => r.emoji === emoji).length
+ const didReact = reactions.some((r) => r.emoji == emoji && r.authorName == username)
+ const onClick = () => {
+ if (didReact) {
+ clearReaction(emoji)
+ } else {
+ addReaction(emoji)
+ }
+ }
+
+ return (
+
+ )
+ })}
+
+ )
+}
+
+interface PostReactionButtonProps {
+ emoji: string
+ didReact: boolean
+ count: number
+ onClick: () => void
+}
+
+function PostReactionButton({ emoji, didReact, onClick, count }: PostReactionButtonProps) {
+ const formattedCount = count < 100 ? count.toString() : `99+`
+
+ return (
+