percobaan kirim stiker di forum

This commit is contained in:
2025-04-23 15:21:12 +08:00
parent cf84daedba
commit 14a080992e
9 changed files with 535 additions and 8 deletions

View File

@@ -3,10 +3,10 @@ import Forum_MainDetail from "@/app_modules/forum/detail/main_detail";
export default async function Page() {
const userLoginId = await funGetUserIdByToken();
return (
<>
<Forum_MainDetail userLoginId={userLoginId as string} />
{/* <Forum_V3_MainDetail userLoginId={userLoginId as string} /> */}
</>
);
}

View File

@@ -0,0 +1,140 @@
"use client";
import { useEditor, EditorContent } from "@tiptap/react";
import StarterKit from "@tiptap/starter-kit";
import Image from "@tiptap/extension-image";
import { useState } from "react";
import {
Box,
Button,
Group,
Image as MantineImage,
Stack,
Text,
} from "@mantine/core";
import Underline from "@tiptap/extension-underline";
import { MainColor } from "@/app_modules/_global/color";
const listStiker = [
{
id: 2,
name: "stiker2",
url: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQN9AKmsBY4yqdn3GueJJEVPJbfmf853gDL4cN8uc9eqsCTiJ1fzhcpywzVP68NCJEA5NQ&usqp=CAU",
},
{
id: 3,
name: "stiker3",
url: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS2lkV3ZiQ8m-OELSui2JGVy80vnh1cyRUV7NrgFNluPVVs2HUAyCHwCMAKGe2s5jk2sn8&usqp=CAU",
},
{
id: 4,
name: "stiker4",
url: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQHy9ZdsPc6dHgVTl5yIGpRJ-KtpTIsXA2_kbfO1Oc-pv_f7CNKGxhO56RjKujE3xCyb9k&usqp=CAU",
},
];
export default function RichTextWithStickers() {
const [chat, setChat] = useState<string[]>([]);
const editor = useEditor({
extensions: [
StarterKit, // Sudah include Bold, Italic, dll
Underline, // Tambahan untuk underline
Image,
],
content: "",
});
const insertSticker = (url: string) => {
editor?.chain().focus().setImage({ src: url }).run();
};
return (
<Stack p="md">
<Text fw={700}>Tiptap Editor dengan Stiker Inline</Text>
<Box
style={{
border: "1px solid #ccc",
borderRadius: 4,
padding: 8,
minHeight: 150,
backgroundColor: MainColor.white,
}}
>
<Group spacing="xs" mb="sm">
<Button
variant="default"
onClick={() => editor?.chain().focus().toggleBold().run()}
>
B
</Button>
<Button
variant="default"
onClick={() => editor?.chain().focus().toggleItalic().run()}
>
I
</Button>
<Button
variant="default"
onClick={() => editor?.chain().focus().toggleUnderline().run()}
>
U
</Button>
</Group>
<EditorContent
editor={editor}
style={{
backgroundColor: "white",
}}
/>
</Box>
<Button
mt="sm"
onClick={() => {
if (editor) {
setChat((prev) => [...prev, editor.getHTML()]);
editor.commands.clearContent();
}
}}
>
Kirim
</Button>
<Group>
{listStiker.map((item) => (
<Box
key={item.id}
component="button"
onClick={() => insertSticker(item.url)}
style={{
border: "none",
background: "transparent",
cursor: "pointer",
}}
>
<MantineImage
w={30}
h={30}
src={item.url}
alt={item.name}
styles={{
image: {
width: 30,
height: 30,
},
}}
/>
</Box>
))}
</Group>
{/* <Stack mt="lg" p="md" bg="gray.1">
{chat.map((item, index) => (
<Box key={index} dangerouslySetInnerHTML={{ __html: item }} />
))}
</Stack> */}
</Stack>
);
}

View File

@@ -0,0 +1,226 @@
"use client";
import React, { useState, useEffect } from "react";
import {
Box,
Button,
Group,
Image,
Paper,
ScrollArea,
SimpleGrid,
Stack,
Text,
Tooltip,
Modal,
} from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
import dynamic from "next/dynamic";
import { MainColor } from "@/app_modules/_global/color";
// Dynamic import ReactQuill dengan SSR disabled
const ReactQuill = dynamic(
async () => {
const { default: RQ } = await import("react-quill");
// Tidak perlu import CSS dengan import statement
return function comp({ forwardedRef, ...props }: any) {
return <RQ ref={forwardedRef} {...props} />;
};
},
{ ssr: false, loading: () => <p>Loading Editor...</p> }
);
const listStiker = [
{
id: 1,
name: "stiker2",
url: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQN9AKmsBY4yqdn3GueJJEVPJbfmf853gDL4cN8uc9eqsCTiJ1fzhcpywzVP68NCJEA5NQ&usqp=CAU",
},
{
id: 2,
name: "stiker3",
url: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS2lkV3ZiQ8m-OELSui2JGVy80vnh1cyRUV7NrgFNluPVVs2HUAyCHwCMAKGe2s5jk2sn8&usqp=CAU",
},
{
id: 3,
name: "stiker4",
url: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQHy9ZdsPc6dHgVTl5yIGpRJ-KtpTIsXA2_kbfO1Oc-pv_f7CNKGxhO56RjKujE3xCyb9k&usqp=CAU",
},
];
type ChatItem = {
content: string; // HTML content including text and stickers
};
export default function Page() {
const [editorContent, setEditorContent] = useState("");
const [chat, setChat] = useState<ChatItem[]>([]);
const [opened, { open, close }] = useDisclosure(false);
const quillRef = React.useRef<any>(null);
const [quillLoaded, setQuillLoaded] = useState(false);
// Load CSS on client-side only
useEffect(() => {
// Add Quill CSS via <link> tag
const link = document.createElement("link");
link.href = "https://cdn.quilljs.com/1.3.6/quill.snow.css";
link.rel = "stylesheet";
document.head.appendChild(link);
// Add custom style for stickers inside Quill editor
const style = document.createElement("style");
style.textContent = `
.ql-editor img {
max-width: 40px !important;
max-height: 40px !important;
}
.chat-content img {
max-width: 40px !important;
max-height: 40px !important;
}
`;
document.head.appendChild(style);
setQuillLoaded(true);
return () => {
// Clean up when component unmounts
document.head.removeChild(link);
document.head.removeChild(style);
};
}, []);
// Custom toolbar options for ReactQuill
const modules = {
toolbar: [
[{ header: [1, 2, false] }],
["bold", "italic", "underline", "strike", "blockquote"],
[{ list: "ordered" }, { list: "bullet" }],
["link", "image"],
["clean"],
],
};
const formats = [
"header",
"bold",
"italic",
"underline",
"strike",
"blockquote",
"list",
"bullet",
"link",
"image",
];
const insertSticker = (stickerUrl: string) => {
if (!quillRef.current) return;
const quill = quillRef.current.getEditor();
const range = quill.getSelection(true);
// Custom image insertion with size
// Use custom blot or HTML string with size attributes
const stickerHtml = `<img src="${stickerUrl}" alt="sticker" style="width: 40px; height: 40px;">`;
// Insert HTML at cursor position
quill.clipboard.dangerouslyPasteHTML(range.index, stickerHtml);
// Move cursor after inserted sticker
quill.setSelection(range.index + 1, 0);
// Focus back on editor
quill.focus();
// Close sticker modal
close();
};
// Function to send message
const sendMessage = () => {
if (editorContent.trim() !== "") {
setChat((prev) => [...prev, { content: editorContent }]);
setEditorContent(""); // Clear after sending
}
};
return (
<Stack p={"md"} spacing="md">
<SimpleGrid cols={2}>
<Stack bg={"gray.1"} h={560} p="md">
<Stack>
<ScrollArea>
{chat.map((item, index) => (
<Box key={index} mb="md">
<div
className="chat-content"
dangerouslySetInnerHTML={{ __html: item.content }}
/>
</Box>
))}
</ScrollArea>
</Stack>
</Stack>
<Paper withBorder p="md">
<Text size="sm" weight={500} mb="xs">
Chat Preview Data:
</Text>
<ScrollArea h={520}>
<pre style={{ whiteSpace: "pre-wrap" }}>
{JSON.stringify(chat, null, 2)}
</pre>
</ScrollArea>
</Paper>
</SimpleGrid>
<Box w="100%" maw={800}>
<Box mb="xs" bg={MainColor.white}>
{quillLoaded && (
<ReactQuill
forwardedRef={quillRef}
theme="snow"
value={editorContent}
onChange={setEditorContent}
modules={modules}
formats={formats}
placeholder="Ketik pesan di sini atau tambahkan stiker..."
style={{
height: 120,
marginBottom: 40,
backgroundColor: MainColor.white,
}}
/>
)}
</Box>
<Group position="apart">
<Button variant="outline" onClick={open} color="blue">
Tambah Stiker
</Button>
<Button onClick={sendMessage}>Kirim Pesan</Button>
</Group>
</Box>
{/* Sticker Modal */}
<Modal opened={opened} onClose={close} title="Pilih Stiker" size="md">
<SimpleGrid cols={3} spacing="md">
{listStiker.map((item) => (
<Box key={item.id}>
<Tooltip label={item.name}>
<Image
src={item.url}
height={100}
width={100}
alt={item.name}
style={{ cursor: "pointer" }}
onClick={() => insertSticker(item.url)}
/>
</Tooltip>
</Box>
))}
</SimpleGrid>
</Modal>
</Stack>
);
}