+ {comment.replies && comment.replies.map(reply => (
+
+ ))}
+
))}
}
@@ -66,8 +111,8 @@ type CommentsStatusProps = {
}
Comments.Status = function Status({children}: CommentsStatusProps) {
- const conditions = ['error', 'problem']
- const isError = conditions.some(condition => children?.toString().toLowerCase().includes(condition))
+ const errorConditions = ['error', 'problem']
+ const isError = errorConditions.some(condition => children?.toString().toLowerCase().includes(condition))
return (
void } | null>(null)
+
+Comments.Form = function Form({slug, commentFormRef}: CommentsProps) {
+ const [parentId, setParentId] = useState('')
const [state, formAction, pending] = useActionState(addComment, initialState)
const {data: session} = useSession()
+ const replyContext = useContext(ReplyContext)
+
+ useEffect(() => {
+ if (replyContext?.replyTo?.parent_id !== null) {
+ setParentId(replyContext?.replyTo?.parent_id ?? '')
+ } else {
+ setParentId(replyContext?.replyTo?.id)
+ }
+ }, [replyContext?.replyTo])
const handleKeyDown = (e: React.KeyboardEvent) => {
if ((e.ctrlKey || e.metaKey) && (e.key === 'Enter' || e.key === 'NumpadEnter')) {
e.preventDefault()
e.currentTarget.form?.requestSubmit()
}
+
+ if (e.key === 'Escape' && replyContext?.replyTo !== null) {
+ replyContext?.setReplyTo(null)
+ commentFormRef?.current?.blur()
+ }
}
return (
@@ -108,7 +170,7 @@ Comments.Form = function Form({slug}: CommentsProps) {
}
@@ -138,18 +208,38 @@ Comments.Form = function Form({slug}: CommentsProps) {
)
}
+
+type ReplyContextType = {
+ replyTo: Comment | null
+ setReplyTo: (replyTo: Comment | null) => void
+}
+
+const ReplyContext = createContext(null)
+
type CommentsProps = {
slug: string
comments?: any
+ commentFormRef?: RefObject
}
export default function Comments({slug, comments}: CommentsProps) {
+ const [replyTo, setReplyTo] = useState(null)
+ const commentFormRef = useRef(null)
+
+ const focusCommentForm = () => {
+ commentFormRef.current?.focus()
+ }
+
return (
-
- {comments &&
-
- }
-
-
+
+
+
+ {comments &&
+
+ }
+
+
+
+
)
}
\ No newline at end of file
diff --git a/lib/getComments.ts b/lib/getComments.ts
index e889823..c7d7e9e 100644
--- a/lib/getComments.ts
+++ b/lib/getComments.ts
@@ -1,15 +1,17 @@
import {createClient} from '@/lib/supabase/client'
+import {QueryData} from '@supabase/supabase-js'
export async function getComments(slug: string) {
try {
const supabase = await createClient()
- const {data: comments, error} = await supabase
+ const commentsQuery = supabase
.from('comments')
.select(`
id,
content,
published,
created_at,
+ parent_id,
user:users!inner(id, name, image),
article:articles!inner(id, title, slug)
`)
@@ -17,7 +19,32 @@ export async function getComments(slug: string) {
.eq('published', true)
.order('created_at', {ascending: false})
- return comments
+ type Comments = QueryData
+
+ const {data: comments, error} = await commentsQuery
+
+ const commentMap = comments?.reduce<{ [key: number]: Comment }>((acc, comment) => {
+ // @ts-ignore
+ acc[comment.id] = {...comment, replies: []}
+ return acc
+ }, {});
+
+ return comments?.reduce((nested, comment) => {
+ if (typeof commentMap !== 'undefined') {
+ if (comment.parent_id !== null) {
+ const parent = commentMap[comment.parent_id];
+ if (parent) {
+ // @ts-ignore
+ parent.replies?.push(commentMap[comment.id])
+ // @ts-ignore
+ parent.replies?.sort((a, b) => a.id - b.id)
+ }
+ } else {
+ nested.push(commentMap[comment.id]);
+ }
+ }
+ return nested;
+ }, [])
} catch (error) {
console.error(error)
}
diff --git a/package.json b/package.json
index be1b4da..832d565 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
"version": "0.1.0",
"private": true,
"scripts": {
- "dev": "next dev",
+ "dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
"lint": "next lint"
diff --git a/types/database.types.ts b/types/database.types.ts
index fd4b9b9..6cd92cc 100644
--- a/types/database.types.ts
+++ b/types/database.types.ts
@@ -1,215 +1,249 @@
export type Json =
- | string
- | number
- | boolean
- | null
- | { [key: string]: Json }
- | Json[]
+ | string
+ | number
+ | boolean
+ | null
+ | { [key: string]: Json | undefined }
+ | Json[]
-export interface Database {
- graphql_public: {
- Tables: {
- [_ in never]: never
+export type Database = {
+ public: {
+ Tables: {
+ analytics: {
+ Row: {
+ article_id: number
+ created_at: string | null
+ id: number
+ views: number
}
- Views: {
- [_ in never]: never
+ Insert: {
+ article_id: number
+ created_at?: string | null
+ id?: number
+ views?: number
}
- Functions: {
- graphql: {
- Args: {
- operationName?: string
- query?: string
- variables?: Json
- extensions?: Json
- }
- Returns: Json
- }
+ Update: {
+ article_id?: number
+ created_at?: string | null
+ id?: number
+ views?: number
}
- Enums: {
- [_ in never]: never
+ Relationships: [
+ {
+ foreignKeyName: "analytics_article_id_fkey"
+ columns: ["article_id"]
+ isOneToOne: false
+ referencedRelation: "articles"
+ referencedColumns: ["id"]
+ },
+ ]
+ }
+ articles: {
+ Row: {
+ created_at: string | null
+ id: number
+ slug: string
+ title: string | null
}
- CompositeTypes: {
- [_ in never]: never
+ Insert: {
+ created_at?: string | null
+ id?: number
+ slug: string
+ title?: string | null
}
+ Update: {
+ created_at?: string | null
+ id?: number
+ slug?: string
+ title?: string | null
+ }
+ Relationships: []
+ }
+ comments: {
+ Row: {
+ article_id: number
+ content: string
+ created_at: string | null
+ id: number
+ published: boolean | null
+ user_id: number
+ }
+ Insert: {
+ article_id: number
+ content: string
+ created_at?: string | null
+ id?: number
+ published?: boolean | null
+ user_id: number
+ }
+ Update: {
+ article_id?: number
+ content?: string
+ created_at?: string | null
+ id?: number
+ published?: boolean | null
+ user_id?: number
+ }
+ Relationships: [
+ {
+ foreignKeyName: "comments_article_id_fkey"
+ columns: ["article_id"]
+ isOneToOne: false
+ referencedRelation: "articles"
+ referencedColumns: ["id"]
+ },
+ {
+ foreignKeyName: "comments_user_id_fkey"
+ columns: ["user_id"]
+ isOneToOne: false
+ referencedRelation: "users"
+ referencedColumns: ["id"]
+ },
+ ]
+ }
+ users: {
+ Row: {
+ created_at: string | null
+ email: string
+ id: number
+ image: string | null
+ name: string
+ }
+ Insert: {
+ created_at?: string | null
+ email: string
+ id?: number
+ image?: string | null
+ name: string
+ }
+ Update: {
+ created_at?: string | null
+ email?: string
+ id?: number
+ image?: string | null
+ name?: string
+ }
+ Relationships: []
+ }
}
- public: {
- Tables: {
- [_ in never]: never
- }
- Views: {
- [_ in never]: never
- }
- Functions: {
- [_ in never]: never
- }
- Enums: {
- [_ in never]: never
- }
- CompositeTypes: {
- [_ in never]: never
- }
+ Views: {
+ [_ in never]: never
}
- storage: {
- Tables: {
- buckets: {
- Row: {
- allowed_mime_types: string[] | null
- avif_autodetection: boolean | null
- created_at: string | null
- file_size_limit: number | null
- id: string
- name: string
- owner: string | null
- public: boolean | null
- updated_at: string | null
- }
- Insert: {
- allowed_mime_types?: string[] | null
- avif_autodetection?: boolean | null
- created_at?: string | null
- file_size_limit?: number | null
- id: string
- name: string
- owner?: string | null
- public?: boolean | null
- updated_at?: string | null
- }
- Update: {
- allowed_mime_types?: string[] | null
- avif_autodetection?: boolean | null
- created_at?: string | null
- file_size_limit?: number | null
- id?: string
- name?: string
- owner?: string | null
- public?: boolean | null
- updated_at?: string | null
- }
- }
- migrations: {
- Row: {
- executed_at: string | null
- hash: string
- id: number
- name: string
- }
- Insert: {
- executed_at?: string | null
- hash: string
- id: number
- name: string
- }
- Update: {
- executed_at?: string | null
- hash?: string
- id?: number
- name?: string
- }
- }
- objects: {
- Row: {
- bucket_id: string | null
- created_at: string | null
- id: string
- last_accessed_at: string | null
- metadata: Json | null
- name: string | null
- owner: string | null
- path_tokens: string[] | null
- updated_at: string | null
- version: string | null
- }
- Insert: {
- bucket_id?: string | null
- created_at?: string | null
- id?: string
- last_accessed_at?: string | null
- metadata?: Json | null
- name?: string | null
- owner?: string | null
- path_tokens?: string[] | null
- updated_at?: string | null
- version?: string | null
- }
- Update: {
- bucket_id?: string | null
- created_at?: string | null
- id?: string
- last_accessed_at?: string | null
- metadata?: Json | null
- name?: string | null
- owner?: string | null
- path_tokens?: string[] | null
- updated_at?: string | null
- version?: string | null
- }
- }
- }
- Views: {
- [_ in never]: never
- }
- Functions: {
- can_insert_object: {
- Args: {
- bucketid: string
- name: string
- owner: string
- metadata: Json
- }
- Returns: undefined
- }
- extension: {
- Args: {
- name: string
- }
- Returns: string
- }
- filename: {
- Args: {
- name: string
- }
- Returns: string
- }
- foldername: {
- Args: {
- name: string
- }
- Returns: string[]
- }
- get_size_by_bucket: {
- Args: Record
- Returns: {
- size: number
- bucket_id: string
- }[]
- }
- search: {
- Args: {
- prefix: string
- bucketname: string
- limits?: number
- levels?: number
- offsets?: number
- search?: string
- sortcolumn?: string
- sortorder?: string
- }
- Returns: {
- name: string
- id: string
- updated_at: string
- created_at: string
- last_accessed_at: string
- metadata: Json
- }[]
- }
- }
- Enums: {
- [_ in never]: never
- }
- CompositeTypes: {
- [_ in never]: never
+ Functions: {
+ increment_views: {
+ Args: {
+ param_slug: string
+ param_title: string
}
+ Returns: undefined
+ }
+ total_views: {
+ Args: Record
+ Returns: number
+ }
}
+ Enums: {
+ [_ in never]: never
+ }
+ CompositeTypes: {
+ [_ in never]: never
+ }
+ }
}
+type PublicSchema = Database[Extract]
+
+export type Tables<
+ PublicTableNameOrOptions extends
+ | keyof (PublicSchema["Tables"] & PublicSchema["Views"])
+ | { schema: keyof Database },
+ TableName extends PublicTableNameOrOptions extends { schema: keyof Database }
+ ? keyof (Database[PublicTableNameOrOptions["schema"]]["Tables"] &
+ Database[PublicTableNameOrOptions["schema"]]["Views"])
+ : never = never,
+> = PublicTableNameOrOptions extends { schema: keyof Database }
+ ? (Database[PublicTableNameOrOptions["schema"]]["Tables"] &
+ Database[PublicTableNameOrOptions["schema"]]["Views"])[TableName] extends {
+ Row: infer R
+ }
+ ? R
+ : never
+ : PublicTableNameOrOptions extends keyof (PublicSchema["Tables"] &
+ PublicSchema["Views"])
+ ? (PublicSchema["Tables"] &
+ PublicSchema["Views"])[PublicTableNameOrOptions] extends {
+ Row: infer R
+ }
+ ? R
+ : never
+ : never
+
+export type TablesInsert<
+ PublicTableNameOrOptions extends
+ | keyof PublicSchema["Tables"]
+ | { schema: keyof Database },
+ TableName extends PublicTableNameOrOptions extends { schema: keyof Database }
+ ? keyof Database[PublicTableNameOrOptions["schema"]]["Tables"]
+ : never = never,
+> = PublicTableNameOrOptions extends { schema: keyof Database }
+ ? Database[PublicTableNameOrOptions["schema"]]["Tables"][TableName] extends {
+ Insert: infer I
+ }
+ ? I
+ : never
+ : PublicTableNameOrOptions extends keyof PublicSchema["Tables"]
+ ? PublicSchema["Tables"][PublicTableNameOrOptions] extends {
+ Insert: infer I
+ }
+ ? I
+ : never
+ : never
+
+export type TablesUpdate<
+ PublicTableNameOrOptions extends
+ | keyof PublicSchema["Tables"]
+ | { schema: keyof Database },
+ TableName extends PublicTableNameOrOptions extends { schema: keyof Database }
+ ? keyof Database[PublicTableNameOrOptions["schema"]]["Tables"]
+ : never = never,
+> = PublicTableNameOrOptions extends { schema: keyof Database }
+ ? Database[PublicTableNameOrOptions["schema"]]["Tables"][TableName] extends {
+ Update: infer U
+ }
+ ? U
+ : never
+ : PublicTableNameOrOptions extends keyof PublicSchema["Tables"]
+ ? PublicSchema["Tables"][PublicTableNameOrOptions] extends {
+ Update: infer U
+ }
+ ? U
+ : never
+ : never
+
+export type Enums<
+ PublicEnumNameOrOptions extends
+ | keyof PublicSchema["Enums"]
+ | { schema: keyof Database },
+ EnumName extends PublicEnumNameOrOptions extends { schema: keyof Database }
+ ? keyof Database[PublicEnumNameOrOptions["schema"]]["Enums"]
+ : never = never,
+> = PublicEnumNameOrOptions extends { schema: keyof Database }
+ ? Database[PublicEnumNameOrOptions["schema"]]["Enums"][EnumName]
+ : PublicEnumNameOrOptions extends keyof PublicSchema["Enums"]
+ ? PublicSchema["Enums"][PublicEnumNameOrOptions]
+ : never
+
+export type CompositeTypes<
+ PublicCompositeTypeNameOrOptions extends
+ | keyof PublicSchema["CompositeTypes"]
+ | { schema: keyof Database },
+ CompositeTypeName extends PublicCompositeTypeNameOrOptions extends {
+ schema: keyof Database
+ }
+ ? keyof Database[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"]
+ : never = never,
+> = PublicCompositeTypeNameOrOptions extends { schema: keyof Database }
+ ? Database[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"][CompositeTypeName]
+ : PublicCompositeTypeNameOrOptions extends keyof PublicSchema["CompositeTypes"]
+ ? PublicSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions]
+ : never