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",
|
"version": "0.1.0",
|
||||||
"private": false,
|
"private": false,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@emoji-mart/data": "^1.1.2",
|
||||||
|
"@emoji-mart/react": "^1.1.1",
|
||||||
"@heroicons/react": "^2.0.18",
|
"@heroicons/react": "^2.0.18",
|
||||||
"@types/jest": "^27.5.2",
|
"@types/jest": "^27.5.2",
|
||||||
"@types/node": "^17.0.45",
|
"@types/node": "^17.0.45",
|
||||||
"@types/react": "^18.2.21",
|
"@types/react": "^18.2.21",
|
||||||
"@types/react-dom": "^18.2.7",
|
"@types/react-dom": "^18.2.7",
|
||||||
"@types/react-swipeable-views": "^0.13.2",
|
"@types/react-swipeable-views": "^0.13.2",
|
||||||
|
"emoji-mart": "^5.5.2",
|
||||||
|
"emoji-picker-react": "^4.5.15",
|
||||||
"link-preview-js": "^3.0.5",
|
"link-preview-js": "^3.0.5",
|
||||||
"nostr-tools": "latest",
|
"nostr-tools": "latest",
|
||||||
"react": "^18.2.0",
|
"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,
|
ArrowUpTrayIcon,
|
||||||
CpuChipIcon,
|
CpuChipIcon,
|
||||||
ArrowPathIcon,
|
ArrowPathIcon,
|
||||||
|
Square2StackIcon
|
||||||
} from "@heroicons/react/24/outline";
|
} from "@heroicons/react/24/outline";
|
||||||
import { XCircleIcon } from "@heroicons/react/24/solid";
|
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 { generatePrivateKey, getPublicKey, finishEvent, UnsignedEvent, Event as NostrEvent, nip19 } from "nostr-tools";
|
||||||
import { publish } from "../../utils/relays";
|
import { publish } from "../../utils/relays";
|
||||||
import { renderMedia, attachFile } from "../../utils/FileUpload";
|
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 useWorkers = (numCores: number, unsigned: UnsignedEvent, difficulty: string, deps: any[]) => {
|
||||||
const [messageFromWorker, setMessageFromWorker] = useState(null);
|
const [messageFromWorker, setMessageFromWorker] = useState(null);
|
||||||
@ -54,6 +57,7 @@ const NewNoteCard = ({
|
|||||||
refEvent,
|
refEvent,
|
||||||
tagType
|
tagType
|
||||||
}: FormProps) => {
|
}: FormProps) => {
|
||||||
|
const ref = useRef<HTMLDivElement | null>(null);
|
||||||
const [comment, setComment] = useState("");
|
const [comment, setComment] = useState("");
|
||||||
const [file, setFile] = useState("");
|
const [file, setFile] = useState("");
|
||||||
const [sk, setSk] = useState(generatePrivateKey());
|
const [sk, setSk] = useState(generatePrivateKey());
|
||||||
@ -77,7 +81,6 @@ const NewNoteCard = ({
|
|||||||
|
|
||||||
const { startWork, messageFromWorker, doingWorkProgress } = useWorkers(numCores, unsigned, difficulty, [unsigned]);
|
const { startWork, messageFromWorker, doingWorkProgress } = useWorkers(numCores, unsigned, difficulty, [unsigned]);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (refEvent && tagType && unsigned.tags.length === 0) {
|
if (refEvent && tagType && unsigned.tags.length === 0) {
|
||||||
const tags = tagMapping[tagType];
|
const tags = tagMapping[tagType];
|
||||||
@ -134,6 +137,43 @@ const NewNoteCard = ({
|
|||||||
}
|
}
|
||||||
}, [messageFromWorker]);
|
}, [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 (
|
return (
|
||||||
<form
|
<form
|
||||||
name="post"
|
name="post"
|
||||||
@ -151,7 +191,7 @@ const NewNoteCard = ({
|
|||||||
id="togglePostFormLink"
|
id="togglePostFormLink"
|
||||||
className="text-lg text-neutral-500 text-center mb-2 font-semibold"
|
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>
|
||||||
<div className="px-4 pt-4 flex flex-col bg-neutral-900 rounded-lg">
|
<div className="px-4 pt-4 flex flex-col bg-neutral-900 rounded-lg">
|
||||||
<textarea
|
<textarea
|
||||||
@ -181,6 +221,18 @@ const NewNoteCard = ({
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center gap-4">
|
<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">
|
<div className="flex items-center">
|
||||||
<ArrowUpTrayIcon
|
<ArrowUpTrayIcon
|
||||||
className="h-4 w-4 text-neutral-400 cursor-pointer"
|
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">
|
<div className="w-full px-4 sm:px-0 sm:max-w-xl mx-auto my-2">
|
||||||
<NewNoteCard />
|
<NewNoteCard />
|
||||||
</div>
|
</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">
|
<label htmlFor="toggleB" className="flex items-center cursor-pointer">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<input
|
<input
|
||||||
|
@ -62,7 +62,7 @@ const PostCard = ({
|
|||||||
<CardContainer>
|
<CardContainer>
|
||||||
<div className={`flex flex-col gap-2`}>
|
<div className={`flex flex-col gap-2`}>
|
||||||
<div className={`flex flex-col break-words ${type !== "OP" ? 'hover:cursor-pointer' : ''}`} onClick={handleClick}>
|
<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>
|
</div>
|
||||||
{renderMedia(file)}
|
{renderMedia(file)}
|
||||||
{repliedTo && <div className="flex items-center mt-1" >
|
{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="p-3 rounded-lg border border-neutral-700 bg-neutral-800">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className="flex flex-col break-words">
|
<div className="flex flex-col break-words">
|
||||||
<ContentPreview key={event.id} comment={comment} />
|
<ContentPreview key={event.id} eventdata={event} />
|
||||||
</div>
|
</div>
|
||||||
{renderMedia(file)}
|
{renderMedia(file)}
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
|
@ -5,12 +5,33 @@ import { useEffect, useState } from "react";
|
|||||||
import { subNoteOnce } from "../../../utils/subscriptions";
|
import { subNoteOnce } from "../../../utils/subscriptions";
|
||||||
import { nip19 } from "nostr-tools";
|
import { nip19 } from "nostr-tools";
|
||||||
import LinkModal from "./LinkPreview";
|
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 [finalComment, setFinalComment] = useState(comment);
|
||||||
const [quoteEvents, setQuoteEvents] = useState<Event[]>([]); // Initialize state
|
const [quoteEvents, setQuoteEvents] = useState<Event[]>([]); // Initialize state
|
||||||
const [isExpanded, setIsExpanded] = useState(false);
|
const [isExpanded, setIsExpanded] = useState(false);
|
||||||
const [url, setUrl] = useState("");
|
const [url, setUrl] = useState("");
|
||||||
|
const [emojiMap, setEmojiMap] = useState<Record<string, any>>({});
|
||||||
|
|
||||||
// Define your callback function for subGlobalFeed
|
// Define your callback function for subGlobalFeed
|
||||||
const onEvent = (event: Event, relay: string) => {
|
const onEvent = (event: Event, relay: string) => {
|
||||||
@ -31,6 +52,16 @@ const ContentPreview = ({ key, comment }: { key: string; comment: string }) => {
|
|||||||
subNoteOnce(id_to_hex, onEvent);
|
subNoteOnce(id_to_hex, onEvent);
|
||||||
setFinalComment(finalComment.replace("nostr:" + nostrQuoteID, "").trim());
|
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]);
|
}, [comment, finalComment]);
|
||||||
|
|
||||||
const getMetadataEvent = (event: Event) => {
|
const getMetadataEvent = (event: Event) => {
|
||||||
@ -44,11 +75,8 @@ const ContentPreview = ({ key, comment }: { key: string; comment: string }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="gap-2 flex flex-col break-words text-sm">
|
<div className="gap-2 flex flex-col break-words font-normal">
|
||||||
{isExpanded
|
<RichText text={finalComment} isExpanded={isExpanded} emojiMap={emojiMap} />
|
||||||
? 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>)
|
|
||||||
}
|
|
||||||
{finalComment.length > 350 && (
|
{finalComment.length > 350 && (
|
||||||
<button
|
<button
|
||||||
className="text-sm text-neutral-500"
|
className="text-sm text-neutral-500"
|
||||||
|
Loading…
Reference in New Issue
Block a user