mirror of
https://github.com/smolgrrr/TAO.git
synced 2024-09-20 01:11:25 +00:00
commit
33da9990f8
@ -1,13 +1,12 @@
|
|||||||
import {
|
import {
|
||||||
CpuChipIcon,
|
CpuChipIcon
|
||||||
PlusCircleIcon
|
|
||||||
} from "@heroicons/react/24/outline";
|
} from "@heroicons/react/24/outline";
|
||||||
import { XCircleIcon } from "@heroicons/react/24/solid";
|
import { useState, useEffect } from "react";
|
||||||
import { useState, useEffect, useRef } from "react";
|
|
||||||
import { UnsignedEvent, Event as NostrEvent, nip19 } from "nostr-tools";
|
import { UnsignedEvent, Event as NostrEvent, nip19 } from "nostr-tools";
|
||||||
import { renderMedia } from "../../utils/FileUpload";
|
|
||||||
import { useSubmitForm } from "./handleSubmit";
|
import { useSubmitForm } from "./handleSubmit";
|
||||||
import "../../styles/Form.css";
|
import "../../styles/Form.css";
|
||||||
|
import EmotePicker from "../modals/EmotePicker/EmotePicker";
|
||||||
|
import emotes from "../modals/EmotePicker/custom_emojis.json"
|
||||||
|
|
||||||
interface FormProps {
|
interface FormProps {
|
||||||
refEvent?: NostrEvent;
|
refEvent?: NostrEvent;
|
||||||
@ -20,9 +19,7 @@ const NewNoteCard = ({
|
|||||||
tagType,
|
tagType,
|
||||||
hashtag,
|
hashtag,
|
||||||
}: FormProps) => {
|
}: FormProps) => {
|
||||||
const ref = useRef<HTMLDivElement | null>(null);
|
|
||||||
const [comment, setComment] = useState("");
|
const [comment, setComment] = useState("");
|
||||||
const [file, setFile] = useState("");
|
|
||||||
const [unsigned, setUnsigned] = useState<UnsignedEvent>({
|
const [unsigned, setUnsigned] = useState<UnsignedEvent>({
|
||||||
kind: 1,
|
kind: 1,
|
||||||
tags: [
|
tags: [
|
||||||
@ -38,8 +35,6 @@ const NewNoteCard = ({
|
|||||||
const [difficulty, setDifficulty] = useState(
|
const [difficulty, setDifficulty] = useState(
|
||||||
localStorage.getItem("difficulty") || "21"
|
localStorage.getItem("difficulty") || "21"
|
||||||
);
|
);
|
||||||
const [fileSizeError, setFileSizeError] = useState(false);
|
|
||||||
const [uploadingFile, setUploadingFile] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (hashtag) {
|
if (hashtag) {
|
||||||
@ -78,17 +73,16 @@ const NewNoteCard = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setUnsigned(prevUnsigned => ({
|
setUnsigned(prevUnsigned => ({
|
||||||
...prevUnsigned,
|
...prevUnsigned,
|
||||||
content: `${comment} ${file}`,
|
content: `${comment}`,
|
||||||
created_at: Math.floor(Date.now() / 1000),
|
created_at: Math.floor(Date.now() / 1000),
|
||||||
}));
|
}));
|
||||||
}, [comment, file]);
|
}, [comment]);
|
||||||
|
|
||||||
const { handleSubmit: originalHandleSubmit, doingWorkProp, doingWorkProgress } = useSubmitForm(unsigned, difficulty);
|
const { handleSubmit: originalHandleSubmit, doingWorkProp, doingWorkProgress } = useSubmitForm(unsigned, difficulty);
|
||||||
|
|
||||||
const handleSubmit = async (event: React.FormEvent) => {
|
const handleSubmit = async (event: React.FormEvent) => {
|
||||||
await originalHandleSubmit(event);
|
await originalHandleSubmit(event);
|
||||||
setComment("");
|
setComment("");
|
||||||
setFile("");
|
|
||||||
setUnsigned(prevUnsigned => ({
|
setUnsigned(prevUnsigned => ({
|
||||||
...prevUnsigned,
|
...prevUnsigned,
|
||||||
content: '',
|
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 (
|
return (
|
||||||
<form
|
<form
|
||||||
name="post"
|
name="post"
|
||||||
@ -114,14 +126,6 @@ const NewNoteCard = ({
|
|||||||
onChange={(e) => setComment(e.target.value)}
|
onChange={(e) => setComment(e.target.value)}
|
||||||
rows={comment.split('\n').length || 1}
|
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="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-2 bg-neutral-800 px-1.5 py-1 rounded-lg">
|
||||||
<div className="inline-flex items-center gap-1.5 text-neutral-300">
|
<div className="inline-flex items-center gap-1.5 text-neutral-300">
|
||||||
@ -133,10 +137,11 @@ const NewNoteCard = ({
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
|
<EmotePicker onEmojiSelect={(emoji: Emoji) => onEmojiSelect(emoji)} />
|
||||||
<button
|
<button
|
||||||
type="submit"
|
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' : ''}`}
|
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 || uploadingFile}
|
disabled={doingWorkProp}
|
||||||
>
|
>
|
||||||
Submit
|
Submit
|
||||||
</button>
|
</button>
|
||||||
@ -144,9 +149,6 @@ const NewNoteCard = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{fileSizeError ? (
|
|
||||||
<span className="text-red-500">File size should not exceed 2.5MB</span>
|
|
||||||
) : null}
|
|
||||||
{doingWorkProp ? (
|
{doingWorkProp ? (
|
||||||
<div className="flex animate-pulse text-sm text-gray-300">
|
<div className="flex animate-pulse text-sm text-gray-300">
|
||||||
<CpuChipIcon className="h-4 w-4 ml-auto" />
|
<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