From aded5a3674eab4b087ae1b0f846a29e698cb4918 Mon Sep 17 00:00:00 2001 From: john Date: Fri, 1 Aug 2025 20:58:06 +0100 Subject: [PATCH 01/16] add cursor pointer --- src/app/feed/components/PostItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/feed/components/PostItem.tsx b/src/app/feed/components/PostItem.tsx index a0a541c..d97c109 100644 --- a/src/app/feed/components/PostItem.tsx +++ b/src/app/feed/components/PostItem.tsx @@ -99,7 +99,7 @@ function PostReactionButton({ emoji, didReact, onClick, count }: PostReactionBut + + + ) +} diff --git a/src/components/NewPostWidget.tsx b/src/app/feed/components/NewPostWidget.tsx similarity index 95% rename from src/components/NewPostWidget.tsx rename to src/app/feed/components/NewPostWidget.tsx index c943577..e0bd87a 100644 --- a/src/components/NewPostWidget.tsx +++ b/src/app/feed/components/NewPostWidget.tsx @@ -1,9 +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 '../app/i18n/translations.ts' +import { useTranslations } from '../../i18n/translations.ts' interface NewPostWidgetProps { onSubmit: ( diff --git a/src/app/feed/components/PostTimeline.tsx b/src/app/feed/components/PostTimeline.tsx index 450d5e9..c9653f1 100644 --- a/src/app/feed/components/PostTimeline.tsx +++ b/src/app/feed/components/PostTimeline.tsx @@ -1,30 +1,67 @@ -import { PostReaction } from '../posts/posts.ts' +import { PostComment, PostReaction } from '../posts/posts.ts' +import { Temporal } from '@js-temporal/polyfill' interface PostTimelineProps { reactions: PostReaction[] + comments: PostComment[] } -export function PostTimeline({ reactions }: PostTimelineProps) { +export function PostTimeline({ reactions, comments }: PostTimelineProps) { + const items = [ + ...reactions.map((reaction) => ({ + timestamp: reaction.reactedOn, + component: ( + + ), + })), + ...comments.map((comment) => ({ + timestamp: comment.postedOn, + component: ( + + ), + })), + ].toSorted((a, b) => Temporal.Instant.compare(a.timestamp, b.timestamp)) + return ( -
- {reactions.map((reaction) => ( -
-
- {reaction.reactedOn.toLocaleString('en-AU', { - year: 'numeric', - month: 'numeric', - day: 'numeric', - hour: '2-digit', - minute: '2-digit', - })} -
-
- @{reaction.authorName}  - did  - {reaction.emoji} -
-
- ))} +
{items.map((item) => item.component)}
+ ) +} + +function ReactionItem({ reaction }: { reaction: PostReaction }) { + return ( +
+ {formatItemDate(reaction.reactedOn)} +
+ @{reaction.authorName}  + clicked  + {reaction.emoji} +
) } + +function CommentItem({ comment }: { comment: PostComment }) { + return ( +
+
{formatItemDate(comment.postedOn)}
+
+ @{comment.author}  +
+
{comment.content}
+
+ ) +} + +function formatItemDate(date: Temporal.Instant) { + return date.toLocaleString('en-AU', { + year: 'numeric', + month: 'numeric', + day: 'numeric', + }) +} diff --git a/src/app/feed/pages/HomePage.tsx b/src/app/feed/pages/HomePage.tsx index edef521..b505d83 100644 --- a/src/app/feed/pages/HomePage.tsx +++ b/src/app/feed/pages/HomePage.tsx @@ -2,7 +2,7 @@ import { useRef, useState } from 'react' import { PostsService } from '../posts/postsService.ts' import { useUserStore } from '../../user/user.ts' import { MediaService } from '../../media/mediaService.ts' -import NewPostWidget from '../../../components/NewPostWidget.tsx' +import NewPostWidget from '../components/NewPostWidget.tsx' import SingleColumnLayout from '../../../layouts/SingleColumnLayout.tsx' import NavBar from '../../../components/NavBar.tsx' import AuthNavButtons from '../../auth/components/AuthNavButtons.tsx' diff --git a/src/app/feed/pages/PostPage.tsx b/src/app/feed/pages/PostPage.tsx index d8929d3..b275b68 100644 --- a/src/app/feed/pages/PostPage.tsx +++ b/src/app/feed/pages/PostPage.tsx @@ -1,4 +1,4 @@ -import { useEffect } from 'react' +import { useCallback, useEffect, useState } from 'react' import { useParams } from 'react-router-dom' import { PostsService } from '../posts/postsService.ts' import SingleColumnLayout from '../../../layouts/SingleColumnLayout.tsx' @@ -11,6 +11,7 @@ import { usePostViewModel } from '../posts/usePostViewModel.ts' import { Temporal } from '@js-temporal/polyfill' import { useUserStore } from '../../user/user.ts' import { PostTimeline } from '../components/PostTimeline.tsx' +import NewCommentWidget from '../components/NewCommentWidget.tsx' interface PostPageProps { postsService: PostsService @@ -24,11 +25,15 @@ export default function PostPage({ postsService }: PostPageProps) { const post = posts.at(0) const reactions = (post?.postId ? _reactions[post.postId] : []) ?? [] - useEffect(() => { + const loadPost = useCallback(() => { if (!postId) return postsService.load(postId).then((post) => setPosts(post ? [post] : [])) }, [postId, postsService, setPosts]) + useEffect(() => { + loadPost() + }, [loadPost]) + const onAddReaction = async (emoji: string) => { if (!username) return if (!post) return @@ -46,6 +51,22 @@ export default function PostPage({ postsService }: PostPageProps) { removeReaction(post.postId, emoji, username) } + async function onSubmitComment(content: string) { + if (!postId) return + if (!content.trim()) return + + try { + setIsSubmittingComment(true) + await postsService.addComment(postId, content) + } finally { + setIsSubmittingComment(false) + } + + loadPost() + } + + const [isSubmittingComment, setIsSubmittingComment] = useState(false) + return ( - + +
)} diff --git a/src/app/feed/posts/posts.ts b/src/app/feed/posts/posts.ts index 7fe2547..837e2b8 100644 --- a/src/app/feed/posts/posts.ts +++ b/src/app/feed/posts/posts.ts @@ -8,6 +8,12 @@ export interface PostReaction { reactedOn: Temporal.Instant } +export interface PostComment { + author: string + content: string + postedOn: Temporal.Instant +} + export class Post { [immerable] = true @@ -18,6 +24,7 @@ export class Post { public readonly authorName: string public readonly reactions: PostReaction[] public readonly possibleReactions: string[] + public readonly comments: PostComment[] constructor( postId: string, @@ -25,8 +32,9 @@ export class Post { media: PostMedia[], createdAt: string | Temporal.Instant, authorName: string, - reactions: PostReaction[] = [], - possibleReactions: string[] = [], + reactions: PostReaction[], + possibleReactions: string[], + comments: PostComment[], ) { this.postId = postId this.content = content @@ -35,6 +43,7 @@ export class Post { this.authorName = authorName this.reactions = reactions this.possibleReactions = possibleReactions + this.comments = comments } public static fromDto(dto: components['schemas']['PostDto']): Post { @@ -46,6 +55,7 @@ export class Post { dto.author.username, dto.reactions.map((r) => ({ ...r, reactedOn: Temporal.Instant.from(r.reactedOn) })), dto.possibleReactions, + dto.comments.map((c) => ({ ...c, postedOn: Temporal.Instant.from(c.postedOn) })), ) } } diff --git a/src/app/feed/posts/postsService.ts b/src/app/feed/posts/postsService.ts index 11272e9..72e55f7 100644 --- a/src/app/feed/posts/postsService.ts +++ b/src/app/feed/posts/postsService.ts @@ -1,5 +1,6 @@ import { Post } from './posts.ts' import { ApiClient } from '../../api/client.ts' +import { useUserStore } from '../../user/user.ts' export class PostsService { constructor(private readonly client: ApiClient) {} @@ -78,6 +79,17 @@ export class PostsService { credentials: 'include', }) } + + async addComment(postId: string, content: string): Promise { + const authorId = useUserStore.getState().user?.id + if (!authorId) return + + await this.client.POST('/posts/{postId}/comments', { + params: { path: { postId } }, + body: { content, authorId }, + credentials: 'include', + }) + } } interface CreatePostMedia { diff --git a/src/app/feed/posts/usePostViewModel.ts b/src/app/feed/posts/usePostViewModel.ts index 74004ef..2fb06bd 100644 --- a/src/app/feed/posts/usePostViewModel.ts +++ b/src/app/feed/posts/usePostViewModel.ts @@ -1,5 +1,5 @@ import { useCallback, useState } from 'react' -import { Post, PostMedia, PostReaction } from './posts.ts' +import { Post, PostComment, PostMedia, PostReaction } from './posts.ts' import { Temporal } from '@js-temporal/polyfill' import { produce } from 'immer' @@ -10,6 +10,7 @@ export interface PostInfo { createdAt: Temporal.Instant media: PostMedia[] possibleReactions: string[] + comments: PostComment[] } type ReactionMap = Record From 88348ed6e158cbe73e8a38a60ea6e2cb66168438 Mon Sep 17 00:00:00 2001 From: john Date: Sun, 10 Aug 2025 21:23:22 +0200 Subject: [PATCH 16/16] v1.26.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 718d757..ada1684 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "femto-webapp", "private": true, - "version": "1.26.5", + "version": "1.26.6", "type": "module", "scripts": { "dev": "vite --host 0.0.0.0",