mirror of
https://github.com/smolgrrr/TAO.git
synced 2024-09-20 09:21:25 +00:00
emojis
This commit is contained in:
parent
088fe7243a
commit
f48d5ff024
@ -3,12 +3,16 @@
|
||||
"version": "0.1.0",
|
||||
"private": false,
|
||||
"dependencies": {
|
||||
"@emoji-mart/data": "^1.1.2",
|
||||
"@emoji-mart/react": "^1.1.1",
|
||||
"@heroicons/react": "^2.0.18",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^17.0.45",
|
||||
"@types/react": "^18.2.21",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
"@types/react-swipeable-views": "^0.13.2",
|
||||
"emoji-mart": "^5.5.2",
|
||||
"emoji-picker-react": "^4.5.15",
|
||||
"link-preview-js": "^3.0.5",
|
||||
"nostr-tools": "latest",
|
||||
"react": "^18.2.0",
|
||||
|
71
client/src/components/Forms/Emojis/emoji-picker.tsx
Normal file
71
client/src/components/Forms/Emojis/emoji-picker.tsx
Normal file
@ -0,0 +1,71 @@
|
||||
import data, { Emoji } from "@emoji-mart/data";
|
||||
import Picker from "@emoji-mart/react";
|
||||
import { RefObject } from "react";
|
||||
import customEmojis from '../custom_emojis.json';
|
||||
|
||||
interface EmojiPickerProps {
|
||||
topOffset: number;
|
||||
leftOffset: number;
|
||||
onEmojiSelect: (e: Emoji) => void;
|
||||
onClickOutside: () => void;
|
||||
height?: number;
|
||||
ref: RefObject<HTMLDivElement>;
|
||||
}
|
||||
|
||||
const customCategoryIcons = {
|
||||
categoryIcons: {
|
||||
"poast": {
|
||||
src: 'https://poa.st/emoji/custom/poast_hat.png',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export function EmojiPicker({
|
||||
topOffset,
|
||||
leftOffset,
|
||||
onEmojiSelect,
|
||||
onClickOutside,
|
||||
height = 300,
|
||||
ref,
|
||||
}: EmojiPickerProps) {
|
||||
const customEmojiList = customEmojis.map(pack => {
|
||||
return {
|
||||
id: pack.id,
|
||||
name: pack.name,
|
||||
emojis: pack.emojis.map(e => {
|
||||
return {
|
||||
id: e.shortcode,
|
||||
name: e.shortcode,
|
||||
skins: [{ src: e.static_url }],
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="z-25"
|
||||
ref={ref}>
|
||||
<style>
|
||||
{`
|
||||
em-emoji-picker { max-height: ${height}px; }
|
||||
`}
|
||||
</style>
|
||||
<Picker
|
||||
autoFocus
|
||||
data={data}
|
||||
custom={customEmojiList}
|
||||
perLine={7}
|
||||
previewPosition="none"
|
||||
skinTonePosition="search"
|
||||
theme="dark"
|
||||
onEmojiSelect={onEmojiSelect}
|
||||
onClickOutside={onClickOutside}
|
||||
maxFrequentRows={3}
|
||||
categoryIcons={customCategoryIcons}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
@ -2,12 +2,15 @@ import {
|
||||
ArrowUpTrayIcon,
|
||||
CpuChipIcon,
|
||||
ArrowPathIcon,
|
||||
Square2StackIcon
|
||||
} from "@heroicons/react/24/outline";
|
||||
import { XCircleIcon } from "@heroicons/react/24/solid";
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { generatePrivateKey, getPublicKey, finishEvent, UnsignedEvent, Event as NostrEvent, nip19 } from "nostr-tools";
|
||||
import { publish } from "../../utils/relays";
|
||||
import { renderMedia, attachFile } from "../../utils/FileUpload";
|
||||
import { EmojiPicker } from "./Emojis/emoji-picker";
|
||||
import customEmojis from './custom_emojis.json';
|
||||
|
||||
const useWorkers = (numCores: number, unsigned: UnsignedEvent, difficulty: string, deps: any[]) => {
|
||||
const [messageFromWorker, setMessageFromWorker] = useState(null);
|
||||
@ -54,6 +57,7 @@ const NewNoteCard = ({
|
||||
refEvent,
|
||||
tagType
|
||||
}: FormProps) => {
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
const [comment, setComment] = useState("");
|
||||
const [file, setFile] = useState("");
|
||||
const [sk, setSk] = useState(generatePrivateKey());
|
||||
@ -77,7 +81,6 @@ const NewNoteCard = ({
|
||||
|
||||
const { startWork, messageFromWorker, doingWorkProgress } = useWorkers(numCores, unsigned, difficulty, [unsigned]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (refEvent && tagType && unsigned.tags.length === 0) {
|
||||
const tags = tagMapping[tagType];
|
||||
@ -134,6 +137,43 @@ const NewNoteCard = ({
|
||||
}
|
||||
}, [messageFromWorker]);
|
||||
|
||||
//Emoji stuff
|
||||
const emojiRef = useRef(null);
|
||||
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
|
||||
|
||||
interface Emoji {
|
||||
native?: string;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
const emojiNames = customEmojis.map(p => p.emojis).flat();
|
||||
function getEmojiById(id: string) {
|
||||
return emojiNames.find(e => e.shortcode === id);
|
||||
}
|
||||
|
||||
async function onEmojiSelect(emoji: Emoji) {
|
||||
setShowEmojiPicker(false);
|
||||
try {
|
||||
if (emoji.id) {
|
||||
const e = getEmojiById(emoji.id);
|
||||
if (e) {
|
||||
setComment(comment + " :" + e.shortcode + ":");
|
||||
unsigned.tags.push(['emoji', e.shortcode, e.url]);
|
||||
};
|
||||
}
|
||||
} catch {
|
||||
//ignore
|
||||
}
|
||||
}
|
||||
|
||||
const topOffset = ref.current?.getBoundingClientRect().top;
|
||||
const leftOffset = ref.current?.getBoundingClientRect().left;
|
||||
|
||||
function pickEmoji(e: React.MouseEvent) {
|
||||
e.stopPropagation();
|
||||
setShowEmojiPicker(!showEmojiPicker);
|
||||
}
|
||||
|
||||
return (
|
||||
<form
|
||||
name="post"
|
||||
@ -151,7 +191,7 @@ const NewNoteCard = ({
|
||||
id="togglePostFormLink"
|
||||
className="text-lg text-neutral-500 text-center mb-2 font-semibold"
|
||||
>
|
||||
{tagType === 'Reply' ? 'Reply to thread' : 'Start Thread'}
|
||||
{tagType === 'Reply' ? 'Reply to thread' : 'Start New Thread'}
|
||||
</div>
|
||||
<div className="px-4 pt-4 flex flex-col bg-neutral-900 rounded-lg">
|
||||
<textarea
|
||||
@ -181,6 +221,18 @@ const NewNoteCard = ({
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="items-center">
|
||||
{showEmojiPicker && (
|
||||
<EmojiPicker
|
||||
topOffset={topOffset || 0}
|
||||
leftOffset={leftOffset || 0}
|
||||
onEmojiSelect={onEmojiSelect}
|
||||
onClickOutside={() => setShowEmojiPicker(false)}
|
||||
ref={emojiRef}
|
||||
/>
|
||||
)}
|
||||
<Square2StackIcon className="h-4 w-4 text-neutral-400 cursor-pointer" onClick={pickEmoji}/>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<ArrowUpTrayIcon
|
||||
className="h-4 w-4 text-neutral-400 cursor-pointer"
|
||||
|
133788
client/src/components/Forms/custom_emojis.json
Normal file
133788
client/src/components/Forms/custom_emojis.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -55,7 +55,7 @@ const Home = () => {
|
||||
<div className="w-full px-4 sm:px-0 sm:max-w-xl mx-auto my-2">
|
||||
<NewNoteCard />
|
||||
</div>
|
||||
<div className="flex items-center justify-center w-full py-4">
|
||||
<div className="flex items-center justify-center w-full py-4 z-2">
|
||||
<label htmlFor="toggleB" className="flex items-center cursor-pointer">
|
||||
<div className="relative">
|
||||
<input
|
||||
|
@ -62,7 +62,7 @@ const PostCard = ({
|
||||
<CardContainer>
|
||||
<div className={`flex flex-col gap-2`}>
|
||||
<div className={`flex flex-col break-words ${type !== "OP" ? 'hover:cursor-pointer' : ''}`} onClick={handleClick}>
|
||||
<ContentPreview key={event.id} comment={comment} />
|
||||
<ContentPreview key={event.id} eventdata={event} />
|
||||
</div>
|
||||
{renderMedia(file)}
|
||||
{repliedTo && <div className="flex items-center mt-1" >
|
||||
|
@ -71,7 +71,7 @@ const QuoteEmbed = ({
|
||||
<div className="p-3 rounded-lg border border-neutral-700 bg-neutral-800">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-col break-words">
|
||||
<ContentPreview key={event.id} comment={comment} />
|
||||
<ContentPreview key={event.id} eventdata={event} />
|
||||
</div>
|
||||
{renderMedia(file)}
|
||||
<div className="flex justify-between items-center">
|
||||
|
@ -5,12 +5,33 @@ import { useEffect, useState } from "react";
|
||||
import { subNoteOnce } from "../../../utils/subscriptions";
|
||||
import { nip19 } from "nostr-tools";
|
||||
import LinkModal from "./LinkPreview";
|
||||
import { parseContent } from "../../../utils/content";
|
||||
|
||||
const ContentPreview = ({ key, comment }: { key: string; comment: string }) => {
|
||||
const RichText = ({ text, isExpanded, emojiMap }: { text: string; isExpanded: boolean; emojiMap: Record<string, any> }) => {
|
||||
const content = isExpanded ? text.split('\n') : text.slice(0, 350).split('\n');
|
||||
|
||||
return (
|
||||
<>
|
||||
{content.map((line, i) => (
|
||||
<div key={i}>
|
||||
{line.split(' ').map((word, j) =>
|
||||
emojiMap[word]
|
||||
? <img className="w-8 h-8 mx-0.5 inline" src={emojiMap[word]} alt={word} key={j} />
|
||||
: `${word} `
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const ContentPreview = ({ key, eventdata }: { key: string; eventdata: Event }) => {
|
||||
const { comment, file } = parseContent(eventdata);
|
||||
const [finalComment, setFinalComment] = useState(comment);
|
||||
const [quoteEvents, setQuoteEvents] = useState<Event[]>([]); // Initialize state
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
const [url, setUrl] = useState("");
|
||||
const [emojiMap, setEmojiMap] = useState<Record<string, any>>({});
|
||||
|
||||
// Define your callback function for subGlobalFeed
|
||||
const onEvent = (event: Event, relay: string) => {
|
||||
@ -31,6 +52,16 @@ const ContentPreview = ({ key, comment }: { key: string; comment: string }) => {
|
||||
subNoteOnce(id_to_hex, onEvent);
|
||||
setFinalComment(finalComment.replace("nostr:" + nostrQuoteID, "").trim());
|
||||
}
|
||||
|
||||
let newEmojiMap: Record<string, any> = {};
|
||||
eventdata.tags.forEach(tag => {
|
||||
if (tag[0] === "emoji") {
|
||||
newEmojiMap[`:${tag[1]}:`] = tag[2];
|
||||
}
|
||||
});
|
||||
// Update the state variable
|
||||
setEmojiMap(newEmojiMap);
|
||||
|
||||
}, [comment, finalComment]);
|
||||
|
||||
const getMetadataEvent = (event: Event) => {
|
||||
@ -44,11 +75,8 @@ const ContentPreview = ({ key, comment }: { key: string; comment: string }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="gap-2 flex flex-col break-words text-sm">
|
||||
{isExpanded
|
||||
? finalComment.split('\n').map((line, i) => <React.Fragment key={i}>{line}<br/></React.Fragment>)
|
||||
: finalComment.slice(0, 350).split('\n').map((line, i) => <React.Fragment key={i}>{line}<br/></React.Fragment>)
|
||||
}
|
||||
<div className="gap-2 flex flex-col break-words font-normal">
|
||||
<RichText text={finalComment} isExpanded={isExpanded} emojiMap={emojiMap} />
|
||||
{finalComment.length > 350 && (
|
||||
<button
|
||||
className="text-sm text-neutral-500"
|
||||
|
Loading…
Reference in New Issue
Block a user