import { Show, createSignal, createEffect, onCleanup } from "solid-js"; import { useParams, useNavigate, query } from "@solidjs/router"; import { Title } from "@solidjs/meta"; import { createAsync } from "@solidjs/router"; import { getRequestEvent } from "solid-js/web"; import { getPrivilegeLevel, getUserID } from "~/server/utils"; import { api } from "~/lib/api"; import { ConnectionFactory } from "~/server/utils"; import Dropzone from "~/components/blog/Dropzone"; import TextEditor from "~/components/blog/TextEditor"; import TagMaker from "~/components/blog/TagMaker"; import AddAttachmentSection from "~/components/blog/AddAttachmentSection"; import XCircle from "~/components/icons/XCircle"; import AddImageToS3 from "~/lib/s3upload"; const getPostForEdit = query(async (id: string) => { "use server"; const event = getRequestEvent()!; const privilegeLevel = await getPrivilegeLevel(event.nativeEvent); const userID = await getUserID(event.nativeEvent); const conn = ConnectionFactory(); const query = `SELECT * FROM Post WHERE id = ?`; const results = await conn.execute({ sql: query, args: [id] }); const tagQuery = `SELECT * FROM Tag WHERE post_id = ?`; const tagRes = await conn.execute({ sql: tagQuery, args: [id] }); const post = results.rows[0]; const tags = tagRes.rows; return { post, tags, privilegeLevel, userID }; }, "post-for-edit"); export default function EditPost() { const params = useParams(); const navigate = useNavigate(); const data = createAsync(() => getPostForEdit(params.id)); const [title, setTitle] = createSignal(""); const [subtitle, setSubtitle] = createSignal(""); const [body, setBody] = createSignal(""); const [bannerPhoto, setBannerPhoto] = createSignal(""); const [bannerImageFile, setBannerImageFile] = createSignal(); const [bannerImageHolder, setBannerImageHolder] = createSignal< string | ArrayBuffer | null >(null); const [requestedDeleteImage, setRequestedDeleteImage] = createSignal(false); const [published, setPublished] = createSignal(false); const [tags, setTags] = createSignal([]); const [tagInputValue, setTagInputValue] = createSignal(""); const [loading, setLoading] = createSignal(false); const [error, setError] = createSignal(""); const [showAutoSaveMessage, setShowAutoSaveMessage] = createSignal(false); let autosaveInterval: number | undefined; // Populate form when data loads createEffect(() => { const postData = data(); if (postData?.post) { const p = postData.post as any; setTitle(p.title?.replaceAll("_", " ") || ""); setSubtitle(p.subtitle || ""); setBody(p.body || ""); setBannerPhoto(p.banner_photo || ""); setPublished(p.published || false); if (postData.tags) { const tagValues = (postData.tags as any[]).map((t) => t.value); setTags(tagValues); } } }); const autoSave = async () => { const titleVal = title(); const postData = data(); if (titleVal && postData?.post) { try { let bannerImageKey = ""; const bannerFile = bannerImageFile(); if (bannerFile) { bannerImageKey = (await AddImageToS3( bannerFile, titleVal, "blog" )) as string; } await api.database.updatePost.mutate({ id: parseInt(params.id), title: titleVal.replaceAll(" ", "_"), subtitle: subtitle() || null, body: body() || null, banner_photo: bannerImageKey !== "" ? bannerImageKey : requestedDeleteImage() ? "_DELETE_IMAGE_" : null, published: published(), tags: tags().length > 0 ? tags() : null, author_id: data()!.userID }); showAutoSaveTrigger(); } catch (err) { console.error("Autosave failed:", err); } } }; const showAutoSaveTrigger = () => { setShowAutoSaveMessage(true); setTimeout(() => { setShowAutoSaveMessage(false); }, 5000); }; // Set up autosave interval (2 minutes) autosaveInterval = setInterval( () => { autoSave(); }, 2 * 60 * 1000 ) as unknown as number; onCleanup(() => { if (autosaveInterval) { clearInterval(autosaveInterval); } }); const handleBannerImageDrop = (acceptedFiles: File[]) => { if (acceptedFiles.length > 0) { const file = acceptedFiles[0]; setRequestedDeleteImage(false); setBannerImageFile(file); const reader = new FileReader(); reader.onload = () => { setBannerImageHolder(reader.result); }; reader.readAsDataURL(file); } }; const removeBannerImage = () => { setBannerImageFile(undefined); setBannerImageHolder(null); setRequestedDeleteImage(true); }; const tagHandler = (input: string) => { const split = input.split(" "); if (split.length > 1) { const newSplit: string[] = []; split.forEach((word) => { if (word[0] === "#" && word.length > 1) { setTags((prevTags) => [...prevTags, word]); } else { newSplit.push(word); } }); setTagInputValue(newSplit.join(" ")); } else { setTagInputValue(input); } }; const deleteTag = (idx: number) => { setTags((prevTags) => prevTags.filter((_, index) => index !== idx)); }; const handleSubmit = async (e: Event) => { e.preventDefault(); if (!data()?.userID) { setError("You must be logged in to edit posts"); return; } setLoading(true); setError(""); try { let bannerImageKey = ""; const bannerFile = bannerImageFile(); if (bannerFile) { bannerImageKey = (await AddImageToS3( bannerFile, title(), "blog" )) as string; } await api.database.updatePost.mutate({ id: parseInt(params.id), title: title().replaceAll(" ", "_"), subtitle: subtitle() || null, body: body() || null, banner_photo: bannerImageKey !== "" ? bannerImageKey : requestedDeleteImage() ? "_DELETE_IMAGE_" : null, published: published(), tags: tags().length > 0 ? tags() : null, author_id: data()!.userID }); navigate(`/blog/${encodeURIComponent(title().replaceAll(" ", "_"))}`); } catch (err) { console.error("Error updating post:", err); setError("Failed to update post. Please try again."); } finally { setLoading(false); } }; return ( <> Edit Post | Michael Freno
Unauthorized
You must be an admin to edit posts.
} >
Loading post...
} >
Edit a Blog
{/* Title */}
setTitle(e.currentTarget.value)} required name="title" placeholder=" " class="underlinedInput w-full bg-transparent" />
{/* Subtitle */}
setSubtitle(e.currentTarget.value)} name="subtitle" placeholder=" " class="underlinedInput w-full bg-transparent" />
{/* Banner */}
Banner
{/* Attachments */} {/* Text Editor */}
{/* Tags */} {/* Auto-save message */}
{showAutoSaveMessage() ? "Auto save success!" : ""}
{/* Publish checkbox */}
setPublished(e.currentTarget.checked)} />
Published
{/* Error message */}
{error()}
{/* Submit button */}
); }