mirror of
https://github.com/smolgrrr/TAO.git
synced 2024-09-20 01:11:25 +00:00
commit
33da9990f8
@ -1,13 +1,12 @@
|
||||
import {
|
||||
CpuChipIcon,
|
||||
PlusCircleIcon
|
||||
CpuChipIcon
|
||||
} from "@heroicons/react/24/outline";
|
||||
import { XCircleIcon } from "@heroicons/react/24/solid";
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { UnsignedEvent, Event as NostrEvent, nip19 } from "nostr-tools";
|
||||
import { renderMedia } from "../../utils/FileUpload";
|
||||
import { useSubmitForm } from "./handleSubmit";
|
||||
import "../../styles/Form.css";
|
||||
import EmotePicker from "../modals/EmotePicker/EmotePicker";
|
||||
import emotes from "../modals/EmotePicker/custom_emojis.json"
|
||||
|
||||
interface FormProps {
|
||||
refEvent?: NostrEvent;
|
||||
@ -20,9 +19,7 @@ const NewNoteCard = ({
|
||||
tagType,
|
||||
hashtag,
|
||||
}: FormProps) => {
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
const [comment, setComment] = useState("");
|
||||
const [file, setFile] = useState("");
|
||||
const [unsigned, setUnsigned] = useState<UnsignedEvent>({
|
||||
kind: 1,
|
||||
tags: [
|
||||
@ -38,8 +35,6 @@ const NewNoteCard = ({
|
||||
const [difficulty, setDifficulty] = useState(
|
||||
localStorage.getItem("difficulty") || "21"
|
||||
);
|
||||
const [fileSizeError, setFileSizeError] = useState(false);
|
||||
const [uploadingFile, setUploadingFile] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (hashtag) {
|
||||
@ -78,17 +73,16 @@ const NewNoteCard = ({
|
||||
useEffect(() => {
|
||||
setUnsigned(prevUnsigned => ({
|
||||
...prevUnsigned,
|
||||
content: `${comment} ${file}`,
|
||||
content: `${comment}`,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
}));
|
||||
}, [comment, file]);
|
||||
}, [comment]);
|
||||
|
||||
const { handleSubmit: originalHandleSubmit, doingWorkProp, doingWorkProgress } = useSubmitForm(unsigned, difficulty);
|
||||
|
||||
const handleSubmit = async (event: React.FormEvent) => {
|
||||
await originalHandleSubmit(event);
|
||||
setComment("");
|
||||
setFile("");
|
||||
setUnsigned(prevUnsigned => ({
|
||||
...prevUnsigned,
|
||||
content: '',
|
||||
@ -96,6 +90,24 @@ const NewNoteCard = ({
|
||||
}));
|
||||
};
|
||||
|
||||
//Emoji stuff
|
||||
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
|
||||
|
||||
interface Emoji {
|
||||
category: string;
|
||||
shortcode: string;
|
||||
static_url: string;
|
||||
tags: string[];
|
||||
url: string;
|
||||
visible_in_picker: boolean;
|
||||
}
|
||||
|
||||
async function onEmojiSelect(emoji: Emoji) {
|
||||
setShowEmojiPicker(false);
|
||||
setComment(comment + " :" + emoji.shortcode + ":");
|
||||
unsigned.tags.push(['emoji', emoji.shortcode, emoji.url]);
|
||||
}
|
||||
|
||||
return (
|
||||
<form
|
||||
name="post"
|
||||
@ -114,14 +126,6 @@ const NewNoteCard = ({
|
||||
onChange={(e) => setComment(e.target.value)}
|
||||
rows={comment.split('\n').length || 1}
|
||||
/>
|
||||
<div className="relative">
|
||||
{file !== "" && (
|
||||
<button onClick={() => setFile("")}>
|
||||
<XCircleIcon className="h-10 w-10 absolute shadow z-100 text-blue-500" />
|
||||
</button>
|
||||
)}
|
||||
{renderMedia([file])}
|
||||
</div>
|
||||
<div className="h-14 flex items-center justify-between">
|
||||
<div className="inline-flex items-center gap-2 bg-neutral-800 px-1.5 py-1 rounded-lg">
|
||||
<div className="inline-flex items-center gap-1.5 text-neutral-300">
|
||||
@ -133,10 +137,11 @@ const NewNoteCard = ({
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-4">
|
||||
<EmotePicker onEmojiSelect={(emoji: Emoji) => onEmojiSelect(emoji)} />
|
||||
<button
|
||||
type="submit"
|
||||
className={`bg-black border h-9 inline-flex items-center justify-center px-4 rounded-lg text-white font-medium text-sm ${doingWorkProp || uploadingFile ? 'cursor-not-allowed' : ''}`}
|
||||
disabled={doingWorkProp || uploadingFile}
|
||||
className={`bg-black border h-9 inline-flex items-center justify-center px-4 rounded-lg text-white font-medium text-sm ${doingWorkProp ? 'cursor-not-allowed' : ''}`}
|
||||
disabled={doingWorkProp}
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
@ -144,9 +149,6 @@ const NewNoteCard = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{fileSizeError ? (
|
||||
<span className="text-red-500">File size should not exceed 2.5MB</span>
|
||||
) : null}
|
||||
{doingWorkProp ? (
|
||||
<div className="flex animate-pulse text-sm text-gray-300">
|
||||
<CpuChipIcon className="h-4 w-4 ml-auto" />
|
||||
|
116
client/src/components/modals/EmotePicker/EmotePicker.tsx
Normal file
116
client/src/components/modals/EmotePicker/EmotePicker.tsx
Normal file
@ -0,0 +1,116 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import emotes from './custom_emojis.json';
|
||||
import { PlusCircleIcon } from '@heroicons/react/24/outline';
|
||||
|
||||
interface Emoji {
|
||||
category: string;
|
||||
shortcode: string;
|
||||
static_url: string;
|
||||
tags: string[];
|
||||
url: string;
|
||||
visible_in_picker: boolean;
|
||||
}
|
||||
|
||||
interface EmotePickerProps {
|
||||
onEmojiSelect?: (emoji: Emoji) => void;
|
||||
}
|
||||
|
||||
const EmotePicker: React.FC<EmotePickerProps> = ({ onEmojiSelect }) => {
|
||||
const [showEmotes, setShowEmotes] = useState(false);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [hoveredEmote, setHoveredEmote] = useState<string | null>(null);
|
||||
const [activeTab, setActiveTab] = useState(0);
|
||||
const modalRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const toggleEmotes = () => {
|
||||
setShowEmotes(!showEmotes);
|
||||
};
|
||||
|
||||
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSearchQuery(e.target.value);
|
||||
};
|
||||
|
||||
const categories = [
|
||||
{ label: '0-4', range: /^[0-4]/ },
|
||||
{ label: '5-9', range: /^[5-9]/ },
|
||||
{ label: 'A-E', range: /^[A-E]/i },
|
||||
{ label: 'F-J', range: /^[F-J]/i },
|
||||
{ label: 'K-N', range: /^[K-N]/i },
|
||||
{ label: 'O-R', range: /^[O-R]/i },
|
||||
{ label: 'S-V', range: /^[S-V]/i },
|
||||
{ label: 'W-Z', range: /^[W-Z]/i },
|
||||
];
|
||||
|
||||
const filteredEmotes = emotes.filter((emote) => {
|
||||
const matchesSearch = emote.shortcode.toLowerCase().includes(searchQuery.toLowerCase());
|
||||
if (searchQuery) {
|
||||
return matchesSearch;
|
||||
} else {
|
||||
const category = categories[activeTab];
|
||||
return matchesSearch && category.range.test(emote.shortcode);
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
// Explicitly type the event parameter
|
||||
function handleClickOutside(event: MouseEvent) {
|
||||
// Use a type assertion to tell TypeScript the target is an HTMLElement
|
||||
const target = event.target as HTMLElement;
|
||||
if (modalRef.current && !modalRef.current.contains(target)) {
|
||||
setShowEmotes(false);
|
||||
}
|
||||
}
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, []); // Removed modalRef from dependency array as it doesn't change
|
||||
|
||||
return (
|
||||
<div className="static m-auto">
|
||||
<PlusCircleIcon className="h-4 w-4 text-neutral-400 cursor-pointer" onClick={toggleEmotes} />
|
||||
{showEmotes && (
|
||||
<div ref={modalRef} className="absolute flex flex-wrap m-4 p-2 rounded-lg sm:w-full md:w-full lg:w-1/4 bg-[#151617] max-h-64 overflow-y-auto max-w-full right-0 left-0 mx-auto">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search emotes..."
|
||||
value={searchQuery}
|
||||
onChange={handleSearch}
|
||||
className="w-full mb-2 h-10 px-4 py-2 bg-gray-900 rounded-md shadow-sm focus:outline-none focus:border-blue-500"
|
||||
/>
|
||||
<div className="flex w-full mb-2">
|
||||
{categories.map((category, index) => (
|
||||
<button
|
||||
type="button"
|
||||
key={index}
|
||||
className={`text-xs px-2 py-1 rounded-md ${activeTab === index ? 'bg-gray-700' : 'bg-gray-600'
|
||||
}`}
|
||||
onClick={() => setActiveTab(index)}
|
||||
>
|
||||
{category.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{filteredEmotes.map((emote: Emoji, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="text-center relative"
|
||||
onMouseEnter={() => setHoveredEmote(emote.shortcode)}
|
||||
onMouseLeave={() => setHoveredEmote(null)}
|
||||
onClick={() => onEmojiSelect && onEmojiSelect(emote)} // Add this line
|
||||
>
|
||||
<img src={emote.static_url} className="w-7 m-1" />
|
||||
{hoveredEmote === emote.shortcode && (
|
||||
<div className="absolute bottom-full left-1/2 -translate-x-1/2 bg-gray-800 text-white px-2 py-1 rounded-md">
|
||||
{emote.shortcode}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmotePicker;
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user