From ddb691f37166e909bf5759c18fc3a57a907aa9d1 Mon Sep 17 00:00:00 2001 From: mukimovd <41473651-mukimovd@users.noreply.replit.com> Date: Fri, 9 May 2025 11:09:53 +0000 Subject: [PATCH] Enhance blog creation with new editor and allow management of article tags Integrate RichTextEditor into BlogEditor and implement TagManager component using React Hook Form. Replit-Commit-Author: Agent Replit-Commit-Session-Id: bddfbb2b-6d6b-457b-b18c-05792cdaa035 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/39b5c689-6e8a-4d5a-9792-69cc81a56534/049c4c65-a4ba-4585-925d-806602cc4568.jpg --- client/src/components/blog/blog-editor.tsx | 60 +++++++++- client/src/components/blog/tag-manager.tsx | 130 ++++++++++++++++++++- 2 files changed, 188 insertions(+), 2 deletions(-) diff --git a/client/src/components/blog/blog-editor.tsx b/client/src/components/blog/blog-editor.tsx index ec747fa..64c9a3f 100644 --- a/client/src/components/blog/blog-editor.tsx +++ b/client/src/components/blog/blog-editor.tsx @@ -1 +1,59 @@ -null \ No newline at end of file +import { FormField, FormItem, FormLabel, FormControl, FormDescription, FormMessage } from "@/components/ui/form"; +import { RichTextEditor } from "@/components/ui/rich-text-editor"; +import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; +import { useState } from "react"; +import { useFormContext } from "react-hook-form"; + +interface BlogEditorProps { + name: string; + label?: string; + description?: string; + placeholder?: string; +} + +/** + * Blog editor component that uses the RichTextEditor + * + * This component wraps the RichTextEditor with a form field and adds + * write/preview tabs functionality specific for blog content. + */ +export function BlogEditor({ + name, + label = "Content", + description = "You can use markdown formatting. The article will be attributed to your account.", + placeholder = "Write your article content here..." +}: BlogEditorProps) { + const form = useFormContext(); + const [content, setContent] = useState(form.getValues(name) || ""); + + // Update form field when content changes + const handleContentChange = (value: string) => { + setContent(value); + form.setValue(name, value, { shouldValidate: true, shouldDirty: true }); + }; + + return ( + ( + + {label} + + + + + {description} + + + + )} + /> + ); +} \ No newline at end of file diff --git a/client/src/components/blog/tag-manager.tsx b/client/src/components/blog/tag-manager.tsx index ec747fa..06cf335 100644 --- a/client/src/components/blog/tag-manager.tsx +++ b/client/src/components/blog/tag-manager.tsx @@ -1 +1,129 @@ -null \ No newline at end of file +import { useState, useEffect } from "react"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "@/components/ui/select"; +import { X, Plus } from "lucide-react"; +import { FormLabel } from "@/components/ui/form"; +import { useFormContext } from "react-hook-form"; +import { Tag } from "@shared/schema"; + +interface TagManagerProps { + name: string; + tags: Tag[] | undefined; + label?: string; +} + +/** + * Tag manager component for selecting and managing tags + */ +export function TagManager({ + name, + tags, + label = "Tags" +}: TagManagerProps) { + const form = useFormContext(); + const [selectedTags, setSelectedTags] = useState([]); + const [selectedTagId, setSelectedTagId] = useState(""); + + // Initialize selected tags from form values + useEffect(() => { + const formTags = form.getValues(name) || []; + if (tags && formTags.length > 0) { + const initialTags = formTags.map(id => tags.find(t => t.id === id)).filter(Boolean) as Tag[]; + setSelectedTags(initialTags); + } + }, [tags, form, name]); + + // Handle tag selection + const handleAddTag = () => { + if (!selectedTagId) return; + + const tagId = parseInt(selectedTagId, 10); + const tag = tags?.find(t => t.id === tagId); + + if (tag && !selectedTags.some(t => t.id === tag.id)) { + setSelectedTags([...selectedTags, tag]); + + // Update form values + const currentTags = form.getValues(name) || []; + form.setValue(name, [...currentTags, tag.id], { shouldValidate: true }); + } + + setSelectedTagId(""); + }; + + // Handle tag removal + const handleRemoveTag = (tagId: number) => { + setSelectedTags(selectedTags.filter(tag => tag.id !== tagId)); + + // Update form values + const currentTags = form.getValues(name) || []; + form.setValue(name, currentTags.filter(id => id !== tagId), { shouldValidate: true }); + }; + + return ( +
+ {label} +
+ {selectedTags.map(tag => ( + + {tag.name} + + + ))} + + {selectedTags.length === 0 && ( +

+ No tags selected +

+ )} +
+ +
+ + + +
+
+ ); +} \ No newline at end of file