fix emoji

This commit is contained in:
PhilReact 2024-11-12 00:33:11 +02:00
parent c68924af04
commit e02ccd0edf
5 changed files with 118 additions and 50 deletions

25
src/common/Portal.tsx Normal file
View File

@ -0,0 +1,25 @@
import React, { useEffect, useState } from 'react'
import { createPortal } from 'react-dom'
interface PortalProps {
children: React.ReactNode
}
const Portal: React.FC<PortalProps> = ({ children }) => {
const [mounted, setMounted] = useState(false)
useEffect(() => {
setMounted(true)
return () => setMounted(false)
}, [])
return mounted
? createPortal(
children,
document.querySelector('#modal-root') as HTMLElement
)
: null
}
export default Portal

View File

@ -4,11 +4,11 @@ import './styles.css';
import { executeEvent } from '../../utils/events'; import { executeEvent } from '../../utils/events';
const extractComponents = (url) => { const extractComponents = (url) => {
if (!url.startsWith("qortal://")) { if (!url || !url.startsWith("qortal://")) { // Check if url exists and starts with "qortal://"
return null; return null;
} }
url = url.replace(/^(qortal\:\/\/)/, ""); url = url.replace(/^(qortal\:\/\/)/, ""); // Safe to use replace now
if (url.includes("/")) { if (url.includes("/")) {
let parts = url.split("/"); let parts = url.split("/");
const service = parts[0].toUpperCase(); const service = parts[0].toUpperCase();
@ -23,6 +23,7 @@ const extractComponents = (url) => {
return null; return null;
}; };
function processText(input) { function processText(input) {
const linkRegex = /(qortal:\/\/\S+)/g; const linkRegex = /(qortal:\/\/\S+)/g;
function processNode(node) { function processNode(node) {
@ -58,6 +59,8 @@ function processText(input) {
export const MessageDisplay = ({ htmlContent, isReply }) => { export const MessageDisplay = ({ htmlContent, isReply }) => {
const linkify = (text) => { const linkify = (text) => {
if (!text) return ""; // Return an empty string if text is null or undefined
let textFormatted = text; let textFormatted = text;
const urlPattern = /(\bhttps?:\/\/[^\s<]+|\bwww\.[^\s<]+)/g; const urlPattern = /(\bhttps?:\/\/[^\s<]+|\bwww\.[^\s<]+)/g;
textFormatted = text.replace(urlPattern, (url) => { textFormatted = text.replace(urlPattern, (url) => {
@ -66,6 +69,7 @@ export const MessageDisplay = ({ htmlContent, isReply }) => {
}); });
return processText(textFormatted); return processText(textFormatted);
}; };
const sanitizedContent = DOMPurify.sanitize(linkify(htmlContent), { const sanitizedContent = DOMPurify.sanitize(linkify(htmlContent), {
ALLOWED_TAGS: [ ALLOWED_TAGS: [

View File

@ -5,10 +5,23 @@
.emoji-picker { .emoji-picker {
position: absolute; /* Picker positioned absolutely relative to the parent */ position: absolute; /* Picker positioned absolutely relative to the parent */
right: 0; right: 0;
z-index: 1000; /* Ensure picker appears above other content */ z-index: 9000000000; /* Ensure picker appears above other content */
} }
.message-container { .message-container {
overflow: visible; /* Ensure the message container doesn't cut off the picker */ overflow: visible; /* Ensure the message container doesn't cut off the picker */
} }
.reaction-container {
position: relative;
}
.emoji-picker {
overflow: hidden;
width: auto
}
.EmojiPickerReact.epr-dark-theme {
--epr-emoji-size: 18px; /* Adjust emoji size for dark mode */
}

View File

@ -1,39 +1,65 @@
import React, { useState, useRef, useEffect } from 'react'; import React, { useState, useRef, useEffect } from 'react';
import Picker, { Theme } from 'emoji-picker-react'; import ReactDOM from 'react-dom';
import './ReactionPicker.css'; // CSS for proper positioning import Picker, { EmojiStyle, Theme } from 'emoji-picker-react';
import './ReactionPicker.css';
import { ButtonBase } from '@mui/material'; import { ButtonBase } from '@mui/material';
import { isMobile } from '../App'; import { isMobile } from '../App';
export const ReactionPicker = ({ onReaction }) => { export const ReactionPicker = ({ onReaction }) => {
const [showPicker, setShowPicker] = useState(false); // Manage picker visibility const [showPicker, setShowPicker] = useState(false);
const pickerRef = useRef(null); // Reference to the picker const [pickerPosition, setPickerPosition] = useState({ top: 0, left: 0 });
const pickerRef = useRef(null);
const buttonRef = useRef(null);
const handleReaction = (emojiObject) => { const handleReaction = (emojiObject) => {
onReaction(emojiObject.emoji); // Handle the selected emoji reaction onReaction(emojiObject.emoji);
setShowPicker(false); // Close picker after selection setShowPicker(false);
}; };
const handlePicker = (emojiObject) => { const handlePicker = (emojiObject) => {
onReaction(emojiObject.emoji);
onReaction(emojiObject.emoji); // Handle the selected emoji reaction setShowPicker(false);
setShowPicker(false); // Close picker after selection };
const togglePicker = (e) => {
e.preventDefault();
e.stopPropagation();
if (showPicker) {
setShowPicker(false);
} else {
// Get the button's position
const buttonRect = buttonRef.current.getBoundingClientRect();
const pickerWidth = isMobile ? 300 : 350; // Adjust based on picker width
// Calculate position to align the right edge of the picker with the button's right edge
setPickerPosition({
top: buttonRect.bottom + window.scrollY, // Position below the button
left: buttonRect.right + window.scrollX - pickerWidth, // Align right edges
});
setShowPicker(true);
}
}; };
// Close picker if clicked outside // Close picker if clicked outside
useEffect(() => { useEffect(() => {
const handleClickOutside = (event) => { const handleClickOutside = (event) => {
if (pickerRef.current && !pickerRef.current.contains(event.target)) { if (
setShowPicker(false); // Close picker pickerRef.current &&
!pickerRef.current.contains(event.target) &&
buttonRef.current &&
!buttonRef.current.contains(event.target)
) {
setShowPicker(false);
} }
}; };
// Add event listener when picker is shown
if (showPicker) { if (showPicker) {
document.addEventListener('mousedown', handleClickOutside); document.addEventListener('mousedown', handleClickOutside);
} else { } else {
document.removeEventListener('mousedown', handleClickOutside); document.removeEventListener('mousedown', handleClickOutside);
} }
// Clean up the event listener on unmount
return () => { return () => {
document.removeEventListener('mousedown', handleClickOutside); document.removeEventListener('mousedown', handleClickOutside);
}; };
@ -42,42 +68,41 @@ export const ReactionPicker = ({ onReaction }) => {
return ( return (
<div className="reaction-container"> <div className="reaction-container">
{/* Emoji CTA */} {/* Emoji CTA */}
<ButtonBase sx={{ <ButtonBase
fontSize: '22px' sx={{ fontSize: '22px' }}
}} ref={buttonRef}
// onTouchStart={(e) => { onClick={togglePicker}
// e.preventDefault(); // Prevent mobile keyboard
// e.stopPropagation();
// if(!isMobile) return
// setShowPicker(!showPicker);
// }}
onClick={(e) => {
e.preventDefault(); // Prevents any focus issues
e.stopPropagation();
// if(isMobile) return
setShowPicker(!showPicker);
}}
> >
😃 😃
</ButtonBase> </ButtonBase>
{/* Emoji Picker with dark theme */} {/* Emoji Picker rendered in a portal with calculated position */}
{showPicker && ( {showPicker &&
<div className="emoji-picker" ref={pickerRef} onClick={(e) => e.preventDefault()}> ReactDOM.createPortal(
<Picker <div
height={isMobile ? 350 : 450} className="emoji-picker"
width={isMobile ? 300 : 350 } ref={pickerRef}
reactionsDefaultOpen={true} style={{
onReactionClick={handleReaction} position: 'absolute',
onEmojiClick={handlePicker} top: pickerPosition.top,
allowExpandReactions={true} left: pickerPosition.left,
autoFocusSearch={false} zIndex: 1000,
theme={Theme.DARK} }}
/> >
</div> <Picker
)} height={isMobile ? 350 : 450}
width={isMobile ? 300 : 350}
reactionsDefaultOpen={true}
onReactionClick={handleReaction}
onEmojiClick={handlePicker}
allowExpandReactions={true}
autoFocusSearch={false}
theme={Theme.DARK}
emojiStyle={EmojiStyle.NATIVE}
/>
</div>,
document.body
)}
</div> </div>
); );
}; };

View File

@ -107,4 +107,5 @@ html, body {
.swiper { .swiper {
width: 100%; width: 100%;
} }