mirror of
https://github.com/Qortal/Qortal-Hub.git
synced 2025-05-08 10:47:52 +00:00
Rename style file and add theme to chat
This commit is contained in:
parent
2013561db7
commit
69ff35b776
@ -1,28 +1,28 @@
|
|||||||
import React, { useEffect, useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import DOMPurify from 'dompurify';
|
import DOMPurify from 'dompurify';
|
||||||
import './styles.css';
|
import './chat.css';
|
||||||
import { executeEvent } from '../../utils/events';
|
import { executeEvent } from '../../utils/events';
|
||||||
import { Embed } from '../Embeds/Embed';
|
import { Embed } from '../Embeds/Embed';
|
||||||
|
|
||||||
export const extractComponents = (url) => {
|
export const extractComponents = (url) => {
|
||||||
if (!url || !url.startsWith("qortal://")) {
|
if (!url || !url.startsWith('qortal://')) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip links starting with "qortal://use-"
|
// Skip links starting with "qortal://use-"
|
||||||
if (url.startsWith("qortal://use-")) {
|
if (url.startsWith('qortal://use-')) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
url = url.replace(/^(qortal\:\/\/)/, "");
|
url = url.replace(/^(qortal\:\/\/)/, '');
|
||||||
if (url.includes("/")) {
|
if (url.includes('/')) {
|
||||||
let parts = url.split("/");
|
let parts = url.split('/');
|
||||||
const service = parts[0].toUpperCase();
|
const service = parts[0].toUpperCase();
|
||||||
parts.shift();
|
parts.shift();
|
||||||
const name = parts[0];
|
const name = parts[0];
|
||||||
parts.shift();
|
parts.shift();
|
||||||
let identifier;
|
let identifier;
|
||||||
const path = parts.join("/");
|
const path = parts.join('/');
|
||||||
return { service, name, identifier, path };
|
return { service, name, identifier, path };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,8 +64,7 @@ function processText(input) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const linkify = (text) => {
|
const linkify = (text) => {
|
||||||
if (!text) return ""; // Return an empty string if text is null or undefined
|
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) => {
|
||||||
@ -75,22 +74,66 @@ const linkify = (text) => {
|
|||||||
return processText(textFormatted);
|
return processText(textFormatted);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const MessageDisplay = ({ htmlContent, isReply }) => {
|
export const MessageDisplay = ({ htmlContent, isReply }) => {
|
||||||
|
const sanitizedContent = useMemo(() => {
|
||||||
|
|
||||||
const sanitizedContent = useMemo(()=> {
|
|
||||||
return DOMPurify.sanitize(linkify(htmlContent), {
|
return DOMPurify.sanitize(linkify(htmlContent), {
|
||||||
ALLOWED_TAGS: [
|
ALLOWED_TAGS: [
|
||||||
'a', 'b', 'i', 'em', 'strong', 'p', 'br', 'div', 'span', 'img',
|
'a',
|
||||||
'ul', 'ol', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'code', 'pre', 'table', 'thead', 'tbody', 'tr', 'th', 'td', 's', 'hr'
|
'b',
|
||||||
|
'i',
|
||||||
|
'em',
|
||||||
|
'strong',
|
||||||
|
'p',
|
||||||
|
'br',
|
||||||
|
'div',
|
||||||
|
'span',
|
||||||
|
'img',
|
||||||
|
'ul',
|
||||||
|
'ol',
|
||||||
|
'li',
|
||||||
|
'h1',
|
||||||
|
'h2',
|
||||||
|
'h3',
|
||||||
|
'h4',
|
||||||
|
'h5',
|
||||||
|
'h6',
|
||||||
|
'blockquote',
|
||||||
|
'code',
|
||||||
|
'pre',
|
||||||
|
'table',
|
||||||
|
'thead',
|
||||||
|
'tbody',
|
||||||
|
'tr',
|
||||||
|
'th',
|
||||||
|
'td',
|
||||||
|
's',
|
||||||
|
'hr',
|
||||||
],
|
],
|
||||||
ALLOWED_ATTR: [
|
ALLOWED_ATTR: [
|
||||||
'href', 'target', 'rel', 'class', 'src', 'alt', 'title',
|
'href',
|
||||||
'width', 'height', 'style', 'align', 'valign', 'colspan', 'rowspan', 'border', 'cellpadding', 'cellspacing', 'data-url'
|
'target',
|
||||||
|
'rel',
|
||||||
|
'class',
|
||||||
|
'src',
|
||||||
|
'alt',
|
||||||
|
'title',
|
||||||
|
'width',
|
||||||
|
'height',
|
||||||
|
'style',
|
||||||
|
'align',
|
||||||
|
'valign',
|
||||||
|
'colspan',
|
||||||
|
'rowspan',
|
||||||
|
'border',
|
||||||
|
'cellpadding',
|
||||||
|
'cellspacing',
|
||||||
|
'data-url',
|
||||||
],
|
],
|
||||||
}).replace(/<span[^>]*data-url="qortal:\/\/use-embed\/[^"]*"[^>]*>.*?<\/span>/g, '');
|
}).replace(
|
||||||
}, [htmlContent])
|
/<span[^>]*data-url="qortal:\/\/use-embed\/[^"]*"[^>]*>.*?<\/span>/g,
|
||||||
|
''
|
||||||
|
);
|
||||||
|
}, [htmlContent]);
|
||||||
|
|
||||||
const handleClick = async (e) => {
|
const handleClick = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -98,7 +141,7 @@ export const MessageDisplay = ({ htmlContent, isReply }) => {
|
|||||||
const target = e.target;
|
const target = e.target;
|
||||||
if (target.tagName === 'A') {
|
if (target.tagName === 'A') {
|
||||||
const href = target.getAttribute('href');
|
const href = target.getAttribute('href');
|
||||||
if(window?.electronAPI){
|
if (window?.electronAPI) {
|
||||||
window.electronAPI.openExternal(href);
|
window.electronAPI.openExternal(href);
|
||||||
} else {
|
} else {
|
||||||
window.open(href, '_system');
|
window.open(href, '_system');
|
||||||
@ -106,22 +149,22 @@ export const MessageDisplay = ({ htmlContent, isReply }) => {
|
|||||||
} else if (target.getAttribute('data-url')) {
|
} else if (target.getAttribute('data-url')) {
|
||||||
const url = target.getAttribute('data-url');
|
const url = target.getAttribute('data-url');
|
||||||
|
|
||||||
let copyUrl = url
|
let copyUrl = url;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
copyUrl = copyUrl.replace(/^(qortal:\/\/)/, '')
|
copyUrl = copyUrl.replace(/^(qortal:\/\/)/, '');
|
||||||
if (copyUrl.startsWith('use-')) {
|
if (copyUrl.startsWith('use-')) {
|
||||||
// Handle the new 'use' format
|
// Handle the new 'use' format
|
||||||
const parts = copyUrl.split('/')
|
const parts = copyUrl.split('/');
|
||||||
const type = parts[0].split('-')[1] // e.g., 'group' from 'use-group'
|
const type = parts[0].split('-')[1]; // e.g., 'group' from 'use-group'
|
||||||
parts.shift()
|
parts.shift();
|
||||||
const action = parts.length > 0 ? parts[0].split('-')[1] : null // e.g., 'invite' from 'action-invite'
|
const action = parts.length > 0 ? parts[0].split('-')[1] : null; // e.g., 'invite' from 'action-invite'
|
||||||
parts.shift()
|
parts.shift();
|
||||||
const idPrefix = parts.length > 0 ? parts[0].split('-')[0] : null // e.g., 'groupid' from 'groupid-321'
|
const idPrefix = parts.length > 0 ? parts[0].split('-')[0] : null; // e.g., 'groupid' from 'groupid-321'
|
||||||
const id = parts.length > 0 ? parts[0].split('-')[1] : null // e.g., '321' from 'groupid-321'
|
const id = parts.length > 0 ? parts[0].split('-')[1] : null; // e.g., '321' from 'groupid-321'
|
||||||
if(action === 'join'){
|
if (action === 'join') {
|
||||||
executeEvent("globalActionJoinGroup", { groupId: id});
|
executeEvent('globalActionJoinGroup', { groupId: id });
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -130,8 +173,8 @@ export const MessageDisplay = ({ htmlContent, isReply }) => {
|
|||||||
const res = extractComponents(url);
|
const res = extractComponents(url);
|
||||||
if (res) {
|
if (res) {
|
||||||
const { service, name, identifier, path } = res;
|
const { service, name, identifier, path } = res;
|
||||||
executeEvent("addTab", { data: { service, name, identifier, path } });
|
executeEvent('addTab', { data: { service, name, identifier, path } });
|
||||||
executeEvent("open-apps-mode", { });
|
executeEvent('open-apps-mode', {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -141,14 +184,12 @@ export const MessageDisplay = ({ htmlContent, isReply }) => {
|
|||||||
let embedData = null;
|
let embedData = null;
|
||||||
|
|
||||||
if (embedLink) {
|
if (embedLink) {
|
||||||
embedData = embedLink[0]
|
embedData = embedLink[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{embedLink && (
|
{embedLink && <Embed embedLink={embedData} />}
|
||||||
<Embed embedLink={embedData} />
|
|
||||||
)}
|
|
||||||
<div
|
<div
|
||||||
className={`tiptap ${isReply ? 'isReply' : ''}`}
|
className={`tiptap ${isReply ? 'isReply' : ''}`}
|
||||||
dangerouslySetInnerHTML={{ __html: sanitizedContent }}
|
dangerouslySetInnerHTML={{ __html: sanitizedContent }}
|
||||||
|
@ -1,56 +1,82 @@
|
|||||||
import { Message } from "@chatscope/chat-ui-kit-react";
|
import React, {
|
||||||
import React, { useCallback, useContext, useEffect, useMemo, useState } from "react";
|
useCallback,
|
||||||
import { useInView } from "react-intersection-observer";
|
useContext,
|
||||||
import { MessageDisplay } from "./MessageDisplay";
|
useEffect,
|
||||||
import { Avatar, Box, Button, ButtonBase, List, ListItem, ListItemText, Popover, Tooltip, Typography } from "@mui/material";
|
useMemo,
|
||||||
import { formatTimestamp } from "../../utils/time";
|
useState,
|
||||||
import { getBaseApi } from "../../background";
|
} from 'react';
|
||||||
import { MyContext, getBaseApiReact } from "../../App";
|
import { useInView } from 'react-intersection-observer';
|
||||||
import { generateHTML } from "@tiptap/react";
|
import { MessageDisplay } from './MessageDisplay';
|
||||||
import Highlight from "@tiptap/extension-highlight";
|
import {
|
||||||
import Mention from "@tiptap/extension-mention";
|
Avatar,
|
||||||
import StarterKit from "@tiptap/starter-kit";
|
Box,
|
||||||
import Underline from "@tiptap/extension-underline";
|
Button,
|
||||||
import { executeEvent } from "../../utils/events";
|
ButtonBase,
|
||||||
import { WrapperUserAction } from "../WrapperUserAction";
|
List,
|
||||||
import ReplyIcon from "@mui/icons-material/Reply";
|
ListItem,
|
||||||
import { Spacer } from "../../common/Spacer";
|
ListItemText,
|
||||||
import { ReactionPicker } from "../ReactionPicker";
|
Popover,
|
||||||
|
Tooltip,
|
||||||
|
Typography,
|
||||||
|
useTheme,
|
||||||
|
} from '@mui/material';
|
||||||
|
import { formatTimestamp } from '../../utils/time';
|
||||||
|
import { MyContext, getBaseApiReact } from '../../App';
|
||||||
|
import { generateHTML } from '@tiptap/react';
|
||||||
|
import Highlight from '@tiptap/extension-highlight';
|
||||||
|
import Mention from '@tiptap/extension-mention';
|
||||||
|
import StarterKit from '@tiptap/starter-kit';
|
||||||
|
import Underline from '@tiptap/extension-underline';
|
||||||
|
import { WrapperUserAction } from '../WrapperUserAction';
|
||||||
|
import ReplyIcon from '@mui/icons-material/Reply';
|
||||||
|
import { Spacer } from '../../common/Spacer';
|
||||||
|
import { ReactionPicker } from '../ReactionPicker';
|
||||||
import KeyOffIcon from '@mui/icons-material/KeyOff';
|
import KeyOffIcon from '@mui/icons-material/KeyOff';
|
||||||
import EditIcon from '@mui/icons-material/Edit';
|
import EditIcon from '@mui/icons-material/Edit';
|
||||||
import TextStyle from '@tiptap/extension-text-style';
|
import TextStyle from '@tiptap/extension-text-style';
|
||||||
import { addressInfoKeySelector } from "../../atoms/global";
|
import level0Img from '../../assets/badges/level-0.png';
|
||||||
import { useRecoilValue } from "recoil";
|
import level1Img from '../../assets/badges/level-1.png';
|
||||||
import level0Img from "../../assets/badges/level-0.png"
|
import level2Img from '../../assets/badges/level-2.png';
|
||||||
import level1Img from "../../assets/badges/level-1.png"
|
import level3Img from '../../assets/badges/level-3.png';
|
||||||
import level2Img from "../../assets/badges/level-2.png"
|
import level4Img from '../../assets/badges/level-4.png';
|
||||||
import level3Img from "../../assets/badges/level-3.png"
|
import level5Img from '../../assets/badges/level-5.png';
|
||||||
import level4Img from "../../assets/badges/level-4.png"
|
import level6Img from '../../assets/badges/level-6.png';
|
||||||
import level5Img from "../../assets/badges/level-5.png"
|
import level7Img from '../../assets/badges/level-7.png';
|
||||||
import level6Img from "../../assets/badges/level-6.png"
|
import level8Img from '../../assets/badges/level-8.png';
|
||||||
import level7Img from "../../assets/badges/level-7.png"
|
import level9Img from '../../assets/badges/level-9.png';
|
||||||
import level8Img from "../../assets/badges/level-8.png"
|
import level10Img from '../../assets/badges/level-10.png';
|
||||||
import level9Img from "../../assets/badges/level-9.png"
|
|
||||||
import level10Img from "../../assets/badges/level-10.png"
|
|
||||||
|
|
||||||
const getBadgeImg = (level)=> {
|
const getBadgeImg = (level) => {
|
||||||
switch(level?.toString()){
|
switch (level?.toString()) {
|
||||||
|
case '0':
|
||||||
case '0': return level0Img
|
return level0Img;
|
||||||
case '1': return level1Img
|
case '1':
|
||||||
case '2': return level2Img
|
return level1Img;
|
||||||
case '3': return level3Img
|
case '2':
|
||||||
case '4': return level4Img
|
return level2Img;
|
||||||
case '5': return level5Img
|
case '3':
|
||||||
case '6': return level6Img
|
return level3Img;
|
||||||
case '7': return level7Img
|
case '4':
|
||||||
case '8': return level8Img
|
return level4Img;
|
||||||
case '9': return level9Img
|
case '5':
|
||||||
case '10': return level10Img
|
return level5Img;
|
||||||
default: return level0Img
|
case '6':
|
||||||
|
return level6Img;
|
||||||
|
case '7':
|
||||||
|
return level7Img;
|
||||||
|
case '8':
|
||||||
|
return level8Img;
|
||||||
|
case '9':
|
||||||
|
return level9Img;
|
||||||
|
case '10':
|
||||||
|
return level10Img;
|
||||||
|
default:
|
||||||
|
return level0Img;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
export const MessageItem = React.memo(({
|
|
||||||
|
export const MessageItem = React.memo(
|
||||||
|
({
|
||||||
message,
|
message,
|
||||||
onSeen,
|
onSeen,
|
||||||
isLast,
|
isLast,
|
||||||
@ -66,69 +92,65 @@ export const MessageItem = React.memo(({
|
|||||||
isUpdating,
|
isUpdating,
|
||||||
lastSignature,
|
lastSignature,
|
||||||
onEdit,
|
onEdit,
|
||||||
isPrivate
|
isPrivate,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { getIndividualUserInfo } = useContext(MyContext);
|
||||||
const {getIndividualUserInfo} = useContext(MyContext)
|
|
||||||
const [anchorEl, setAnchorEl] = useState(null);
|
const [anchorEl, setAnchorEl] = useState(null);
|
||||||
const [selectedReaction, setSelectedReaction] = useState(null);
|
const [selectedReaction, setSelectedReaction] = useState(null);
|
||||||
const [userInfo, setUserInfo] = useState(null)
|
const [userInfo, setUserInfo] = useState(null);
|
||||||
|
|
||||||
useEffect(()=> {
|
useEffect(() => {
|
||||||
const getInfo = async ()=> {
|
const getInfo = async () => {
|
||||||
if(!message?.sender) return
|
if (!message?.sender) return;
|
||||||
try {
|
try {
|
||||||
const res = await getIndividualUserInfo(message?.sender)
|
const res = await getIndividualUserInfo(message?.sender);
|
||||||
if(!res) return null
|
if (!res) return null;
|
||||||
setUserInfo(res)
|
setUserInfo(res);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
getInfo()
|
getInfo();
|
||||||
}, [message?.sender, getIndividualUserInfo])
|
}, [message?.sender, getIndividualUserInfo]);
|
||||||
|
|
||||||
const htmlText = useMemo(()=> {
|
const htmlText = useMemo(() => {
|
||||||
|
if (message?.messageText) {
|
||||||
if(message?.messageText){
|
|
||||||
return generateHTML(message?.messageText, [
|
return generateHTML(message?.messageText, [
|
||||||
StarterKit,
|
StarterKit,
|
||||||
Underline,
|
Underline,
|
||||||
Highlight,
|
Highlight,
|
||||||
Mention,
|
Mention,
|
||||||
TextStyle
|
TextStyle,
|
||||||
])
|
]);
|
||||||
}
|
}
|
||||||
|
}, [message?.editTimestamp]);
|
||||||
|
|
||||||
}, [message?.editTimestamp])
|
const htmlReply = useMemo(() => {
|
||||||
|
if (reply?.messageText) {
|
||||||
|
|
||||||
|
|
||||||
const htmlReply = useMemo(()=> {
|
|
||||||
|
|
||||||
if(reply?.messageText){
|
|
||||||
return generateHTML(reply?.messageText, [
|
return generateHTML(reply?.messageText, [
|
||||||
StarterKit,
|
StarterKit,
|
||||||
Underline,
|
Underline,
|
||||||
Highlight,
|
Highlight,
|
||||||
Mention,
|
Mention,
|
||||||
TextStyle
|
TextStyle,
|
||||||
])
|
]);
|
||||||
}
|
}
|
||||||
|
}, [reply?.editTimestamp]);
|
||||||
|
|
||||||
}, [reply?.editTimestamp])
|
const userAvatarUrl = useMemo(() => {
|
||||||
|
return message?.senderName
|
||||||
const userAvatarUrl = useMemo(()=> {
|
? `${getBaseApiReact()}/arbitrary/THUMBNAIL/${
|
||||||
return message?.senderName ? `${getBaseApiReact()}/arbitrary/THUMBNAIL/${
|
|
||||||
message?.senderName
|
message?.senderName
|
||||||
}/qortal_avatar?async=true` : ''
|
}/qortal_avatar?async=true`
|
||||||
}, [])
|
: '';
|
||||||
|
}, []);
|
||||||
|
|
||||||
const onSeenFunc = useCallback(()=> {
|
const onSeenFunc = useCallback(() => {
|
||||||
onSeen(message.id);
|
onSeen(message.id);
|
||||||
}, [message?.id])
|
}, [message?.id]);
|
||||||
|
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -138,81 +160,83 @@ const onSeenFunc = useCallback(()=> {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<MessageWragger lastMessage={lastSignature === message?.signature} isLast={isLast} onSeen={onSeenFunc}>
|
<MessageWragger
|
||||||
|
lastMessage={lastSignature === message?.signature}
|
||||||
|
isLast={isLast}
|
||||||
|
onSeen={onSeenFunc}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
padding: "10px",
|
backgroundColor: theme.palette.background.default,
|
||||||
backgroundColor: "#232428",
|
borderRadius: '7px',
|
||||||
borderRadius: "7px",
|
display: 'flex',
|
||||||
width: "95%",
|
gap: '7px',
|
||||||
display: "flex",
|
opacity: isTemp || isUpdating ? 0.5 : 1,
|
||||||
gap: "7px",
|
padding: '10px',
|
||||||
opacity: (isTemp || isUpdating) ? 0.5 : 1,
|
width: '95%',
|
||||||
}}
|
}}
|
||||||
id={message?.signature}
|
id={message?.signature}
|
||||||
>
|
>
|
||||||
{isShowingAsReply ? (
|
{isShowingAsReply ? (
|
||||||
<ReplyIcon
|
<ReplyIcon
|
||||||
sx={{
|
sx={{
|
||||||
fontSize: "30px",
|
fontSize: '30px',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Box sx={{
|
<Box
|
||||||
|
sx={{
|
||||||
|
alignItems: 'center',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
gap: '20px',
|
gap: '20px',
|
||||||
alignItems: 'center'
|
}}
|
||||||
}}>
|
>
|
||||||
<WrapperUserAction
|
<WrapperUserAction
|
||||||
disabled={myAddress === message?.sender}
|
disabled={myAddress === message?.sender}
|
||||||
address={message?.sender}
|
address={message?.sender}
|
||||||
name={message?.senderName}
|
name={message?.senderName}
|
||||||
>
|
>
|
||||||
|
|
||||||
<Avatar
|
<Avatar
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: "#27282c",
|
backgroundColor: theme.palette.background.default,
|
||||||
color: "white",
|
color: theme.palette.text.primary,
|
||||||
height: '40px',
|
height: '40px',
|
||||||
width: '40px'
|
width: '40px',
|
||||||
}}
|
}}
|
||||||
alt={message?.senderName}
|
alt={message?.senderName}
|
||||||
src={userAvatarUrl}
|
src={userAvatarUrl}
|
||||||
>
|
>
|
||||||
{message?.senderName?.charAt(0)}
|
{message?.senderName?.charAt(0)}
|
||||||
</Avatar>
|
</Avatar>
|
||||||
|
|
||||||
|
|
||||||
</WrapperUserAction>
|
</WrapperUserAction>
|
||||||
<Tooltip disableFocusListener title={`level ${userInfo ?? 0}`}>
|
<Tooltip disableFocusListener title={`level ${userInfo ?? 0}`}>
|
||||||
|
<img
|
||||||
|
style={{
|
||||||
<img style={{
|
|
||||||
visibility: userInfo !== undefined ? 'visible' : 'hidden',
|
visibility: userInfo !== undefined ? 'visible' : 'hidden',
|
||||||
width: '30px',
|
width: '30px',
|
||||||
height: 'auto'
|
height: 'auto',
|
||||||
}} src={getBadgeImg(userInfo)} />
|
}}
|
||||||
|
src={getBadgeImg(userInfo)}
|
||||||
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
flexDirection: "column",
|
flexDirection: 'column',
|
||||||
gap: "7px",
|
gap: '7px',
|
||||||
width: "100%",
|
height: isShowingAsReply && '40px',
|
||||||
height: isShowingAsReply && "40px",
|
width: '100%',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
width: "100%",
|
justifyContent: 'space-between',
|
||||||
justifyContent: "space-between",
|
width: '100%',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<WrapperUserAction
|
<WrapperUserAction
|
||||||
@ -223,20 +247,22 @@ const onSeenFunc = useCallback(()=> {
|
|||||||
<Typography
|
<Typography
|
||||||
sx={{
|
sx={{
|
||||||
fontWight: 600,
|
fontWight: 600,
|
||||||
fontFamily: "Inter",
|
fontFamily: 'Inter',
|
||||||
color: "cadetBlue",
|
color: 'cadetBlue',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{message?.senderName || message?.sender}
|
{message?.senderName || message?.sender}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
</WrapperUserAction>
|
</WrapperUserAction>
|
||||||
<Box sx={{
|
<Box
|
||||||
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
gap: '10px',
|
gap: '10px',
|
||||||
alignItems: 'center'
|
alignItems: 'center',
|
||||||
}}>
|
}}
|
||||||
{message?.sender === myAddress && (!message?.isNotEncrypted || isPrivate === false) && (
|
>
|
||||||
|
{message?.sender === myAddress &&
|
||||||
|
(!message?.isNotEncrypted || isPrivate === false) && (
|
||||||
<ButtonBase
|
<ButtonBase
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onEdit(message);
|
onEdit(message);
|
||||||
@ -255,15 +281,21 @@ const onSeenFunc = useCallback(()=> {
|
|||||||
</ButtonBase>
|
</ButtonBase>
|
||||||
)}
|
)}
|
||||||
{!isShowingAsReply && handleReaction && (
|
{!isShowingAsReply && handleReaction && (
|
||||||
<ReactionPicker onReaction={(val)=> {
|
<ReactionPicker
|
||||||
|
onReaction={(val) => {
|
||||||
if(reactions && reactions[val] && reactions[val]?.find((item)=> item?.sender === myAddress)){
|
if (
|
||||||
handleReaction(val, message, false)
|
reactions &&
|
||||||
|
reactions[val] &&
|
||||||
|
reactions[val]?.find(
|
||||||
|
(item) => item?.sender === myAddress
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
handleReaction(val, message, false);
|
||||||
} else {
|
} else {
|
||||||
handleReaction(val, message, true)
|
handleReaction(val, message, true);
|
||||||
}
|
}
|
||||||
|
}}
|
||||||
}} />
|
/>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
@ -272,40 +304,46 @@ const onSeenFunc = useCallback(()=> {
|
|||||||
<Spacer height="20px" />
|
<Spacer height="20px" />
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
width: "100%",
|
backgroundColor: theme.palette.background.default,
|
||||||
borderRadius: "5px",
|
borderRadius: '5px',
|
||||||
backgroundColor: "var(--bg-primary)",
|
cursor: 'pointer',
|
||||||
overflow: 'hidden',
|
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
gap: '20px',
|
gap: '20px',
|
||||||
maxHeight: '90px',
|
maxHeight: '90px',
|
||||||
cursor: 'pointer'
|
overflow: 'hidden',
|
||||||
|
width: '100%',
|
||||||
}}
|
}}
|
||||||
onClick={()=> {
|
onClick={() => {
|
||||||
scrollToItem(replyIndex)
|
scrollToItem(replyIndex);
|
||||||
|
|
||||||
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{
|
<Box
|
||||||
|
sx={{
|
||||||
|
background: theme.palette.background.default,
|
||||||
height: '100%',
|
height: '100%',
|
||||||
width: '5px',
|
width: '5px',
|
||||||
background: 'white'
|
}}
|
||||||
}} />
|
|
||||||
<Box sx={{
|
|
||||||
padding: '5px'
|
|
||||||
}}>
|
|
||||||
<Typography sx={{
|
|
||||||
fontSize: '12px',
|
|
||||||
fontWeight: 600
|
|
||||||
}}>Replied to {reply?.senderName || reply?.senderAddress}</Typography>
|
|
||||||
{reply?.messageText && (
|
|
||||||
<MessageDisplay
|
|
||||||
htmlContent={htmlReply}
|
|
||||||
/>
|
/>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
padding: '5px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontSize: '12px',
|
||||||
|
fontWeight: 600,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Replied to {reply?.senderName || reply?.senderAddress}
|
||||||
|
</Typography>
|
||||||
|
{reply?.messageText && (
|
||||||
|
<MessageDisplay htmlContent={htmlReply} />
|
||||||
)}
|
)}
|
||||||
{reply?.decryptedData?.type === "notification" ? (
|
{reply?.decryptedData?.type === 'notification' ? (
|
||||||
<MessageDisplay htmlContent={reply.decryptedData?.data?.message} />
|
<MessageDisplay
|
||||||
|
htmlContent={reply.decryptedData?.data?.message}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<MessageDisplay isReply htmlContent={reply.text} />
|
<MessageDisplay isReply htmlContent={reply.text} />
|
||||||
)}
|
)}
|
||||||
@ -313,56 +351,72 @@ const onSeenFunc = useCallback(()=> {
|
|||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{htmlText && (
|
{htmlText && <MessageDisplay htmlContent={htmlText} />}
|
||||||
|
|
||||||
|
{message?.decryptedData?.type === 'notification' ? (
|
||||||
<MessageDisplay
|
<MessageDisplay
|
||||||
htmlContent={htmlText}
|
htmlContent={message.decryptedData?.data?.message}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
|
|
||||||
|
|
||||||
{message?.decryptedData?.type === "notification" ? (
|
|
||||||
<MessageDisplay htmlContent={message.decryptedData?.data?.message} />
|
|
||||||
) : (
|
) : (
|
||||||
<MessageDisplay htmlContent={message.text} />
|
<MessageDisplay htmlContent={message.text} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
justifyContent: "space-between",
|
justifyContent: 'space-between',
|
||||||
width: "100%",
|
width: '100%',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{
|
<Box
|
||||||
display: 'flex',
|
sx={{
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: '5px'
|
display: 'flex',
|
||||||
}}>
|
gap: '5px',
|
||||||
{reactions && Object.keys(reactions).map((reaction)=> {
|
}}
|
||||||
const numberOfReactions = reactions[reaction]?.length
|
>
|
||||||
|
{reactions &&
|
||||||
|
Object.keys(reactions).map((reaction) => {
|
||||||
|
const numberOfReactions = reactions[reaction]?.length;
|
||||||
// const myReaction = reactions
|
// const myReaction = reactions
|
||||||
if(numberOfReactions === 0) return null
|
if (numberOfReactions === 0) return null;
|
||||||
return (
|
return (
|
||||||
<ButtonBase key={reaction} sx={{
|
<ButtonBase
|
||||||
|
key={reaction}
|
||||||
|
sx={{
|
||||||
|
background: theme.palette.background.paper,
|
||||||
|
borderRadius: '7px',
|
||||||
height: '30px',
|
height: '30px',
|
||||||
minWidth: '45px',
|
minWidth: '45px',
|
||||||
background: 'var(--bg-2)',
|
}}
|
||||||
borderRadius: '7px'
|
onClick={(event) => {
|
||||||
}} onClick={(event) => {
|
|
||||||
event.stopPropagation(); // Prevent event bubbling
|
event.stopPropagation(); // Prevent event bubbling
|
||||||
setAnchorEl(event.currentTarget);
|
setAnchorEl(event.currentTarget);
|
||||||
setSelectedReaction(reaction);
|
setSelectedReaction(reaction);
|
||||||
}}>
|
}}
|
||||||
<div style={{
|
>
|
||||||
fontSize: '16px'
|
<div
|
||||||
}}>{reaction}</div> {numberOfReactions > 1 && (
|
style={{
|
||||||
<Typography sx={{
|
fontSize: '16px',
|
||||||
marginLeft: '4px'
|
}}
|
||||||
}}>{' '} {numberOfReactions}</Typography>
|
>
|
||||||
|
{reaction}
|
||||||
|
</div>{' '}
|
||||||
|
{numberOfReactions > 1 && (
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
marginLeft: '4px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{' '}
|
||||||
|
{numberOfReactions}
|
||||||
|
</Typography>
|
||||||
)}
|
)}
|
||||||
</ButtonBase>
|
</ButtonBase>
|
||||||
)
|
);
|
||||||
})}
|
})}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{selectedReaction && (
|
{selectedReaction && (
|
||||||
<Popover
|
<Popover
|
||||||
open={Boolean(anchorEl)}
|
open={Boolean(anchorEl)}
|
||||||
@ -372,17 +426,18 @@ const onSeenFunc = useCallback(()=> {
|
|||||||
setSelectedReaction(null);
|
setSelectedReaction(null);
|
||||||
}}
|
}}
|
||||||
anchorOrigin={{
|
anchorOrigin={{
|
||||||
vertical: "top",
|
vertical: 'top',
|
||||||
horizontal: "center",
|
horizontal: 'center',
|
||||||
}}
|
}}
|
||||||
transformOrigin={{
|
transformOrigin={{
|
||||||
vertical: "bottom",
|
vertical: 'bottom',
|
||||||
horizontal: "center",
|
horizontal: 'center',
|
||||||
}}
|
}}
|
||||||
PaperProps={{
|
PaperProps={{
|
||||||
|
// TODO: deprecated
|
||||||
style: {
|
style: {
|
||||||
backgroundColor: "#232428",
|
backgroundColor: theme.palette.background.default,
|
||||||
color: "white",
|
color: theme.palette.text.primary,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -390,15 +445,20 @@ const onSeenFunc = useCallback(()=> {
|
|||||||
<Typography variant="subtitle1" sx={{ marginBottom: 1 }}>
|
<Typography variant="subtitle1" sx={{ marginBottom: 1 }}>
|
||||||
People who reacted with {selectedReaction}
|
People who reacted with {selectedReaction}
|
||||||
</Typography>
|
</Typography>
|
||||||
<List sx={{
|
|
||||||
|
<List
|
||||||
|
sx={{
|
||||||
overflow: 'auto',
|
overflow: 'auto',
|
||||||
maxWidth: '300px',
|
maxWidth: '300px',
|
||||||
maxHeight: '300px'
|
maxHeight: '300px',
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
{reactions[selectedReaction]?.map((reactionItem) => (
|
{reactions[selectedReaction]?.map((reactionItem) => (
|
||||||
<ListItem key={reactionItem.sender}>
|
<ListItem key={reactionItem.sender}>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={reactionItem.senderName || reactionItem.sender}
|
primary={
|
||||||
|
reactionItem.senderName || reactionItem.sender
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
))}
|
))}
|
||||||
@ -424,53 +484,61 @@ const onSeenFunc = useCallback(()=> {
|
|||||||
{reactions[selectedReaction]?.find(
|
{reactions[selectedReaction]?.find(
|
||||||
(item) => item?.sender === myAddress
|
(item) => item?.sender === myAddress
|
||||||
)
|
)
|
||||||
? "Remove Reaction"
|
? 'Remove Reaction'
|
||||||
: "Add Reaction"}
|
: 'Add Reaction'}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Popover>
|
</Popover>
|
||||||
)}
|
)}
|
||||||
<Box sx={{
|
<Box
|
||||||
display: 'flex',
|
sx={{
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: '15px'
|
display: 'flex',
|
||||||
}}>
|
gap: '15px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
{message?.isNotEncrypted && isPrivate && (
|
{message?.isNotEncrypted && isPrivate && (
|
||||||
<KeyOffIcon sx={{
|
<KeyOffIcon
|
||||||
color: 'white',
|
sx={{
|
||||||
marginLeft: '10px'
|
color: theme.palette.text.primary,
|
||||||
}} />
|
marginLeft: '10px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isUpdating ? (
|
{isUpdating ? (
|
||||||
<Typography
|
<Typography
|
||||||
sx={{
|
sx={{
|
||||||
fontSize: "14px",
|
fontSize: '14px',
|
||||||
color: "gray",
|
color: theme.palette.text.secondary,
|
||||||
fontFamily: "Inter",
|
fontFamily: 'Inter',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{message?.status === 'failed-permanent' ? 'Failed to update' : 'Updating...'}
|
{message?.status === 'failed-permanent'
|
||||||
|
? 'Failed to update'
|
||||||
|
: 'Updating...'}
|
||||||
</Typography>
|
</Typography>
|
||||||
) : isTemp ? (
|
) : isTemp ? (
|
||||||
<Typography
|
<Typography
|
||||||
sx={{
|
sx={{
|
||||||
fontSize: "14px",
|
fontSize: '14px',
|
||||||
color: "gray",
|
color: theme.palette.text.secondary,
|
||||||
fontFamily: "Inter",
|
fontFamily: 'Inter',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{message?.status === 'failed-permanent' ? 'Failed to send' : 'Sending...'}
|
{message?.status === 'failed-permanent'
|
||||||
|
? 'Failed to send'
|
||||||
|
: 'Sending...'}
|
||||||
</Typography>
|
</Typography>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{message?.isEdit && (
|
{message?.isEdit && (
|
||||||
<Typography
|
<Typography
|
||||||
sx={{
|
sx={{
|
||||||
fontSize: "14px",
|
fontSize: '14px',
|
||||||
color: "gray",
|
color: theme.palette.text.secondary,
|
||||||
fontFamily: "Inter",
|
fontFamily: 'Inter',
|
||||||
fontStyle: 'italic'
|
fontStyle: 'italic',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Edited
|
Edited
|
||||||
@ -478,9 +546,9 @@ const onSeenFunc = useCallback(()=> {
|
|||||||
)}
|
)}
|
||||||
<Typography
|
<Typography
|
||||||
sx={{
|
sx={{
|
||||||
fontSize: "14px",
|
fontSize: '14px',
|
||||||
color: "gray",
|
color: theme.palette.text.secondary,
|
||||||
fontFamily: "Inter",
|
fontFamily: 'Inter',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{formatTimestamp(message.timestamp)}
|
{formatTimestamp(message.timestamp)}
|
||||||
@ -490,49 +558,60 @@ const onSeenFunc = useCallback(()=> {
|
|||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</MessageWragger>
|
</MessageWragger>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const ReplyPreview = ({ message, isEdit }) => {
|
||||||
export const ReplyPreview = ({message, isEdit})=> {
|
const theme = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
marginTop: '20px',
|
backgroundColor: theme.palette.background.default,
|
||||||
width: "100%",
|
borderRadius: '5px',
|
||||||
borderRadius: "5px",
|
cursor: 'pointer',
|
||||||
backgroundColor: "var(--bg-primary)",
|
|
||||||
overflow: 'hidden',
|
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
gap: '20px',
|
gap: '20px',
|
||||||
|
marginTop: '20px',
|
||||||
maxHeight: '90px',
|
maxHeight: '90px',
|
||||||
cursor: 'pointer'
|
overflow: 'hidden',
|
||||||
|
width: '100%',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{
|
<Box
|
||||||
|
sx={{
|
||||||
|
background: theme.palette.background.default,
|
||||||
height: '100%',
|
height: '100%',
|
||||||
width: '5px',
|
width: '5px',
|
||||||
background: 'white'
|
}}
|
||||||
}} />
|
/>
|
||||||
<Box sx={{
|
<Box
|
||||||
padding: '5px'
|
sx={{
|
||||||
}}>
|
padding: '5px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
{isEdit ? (
|
{isEdit ? (
|
||||||
<Typography sx={{
|
<Typography
|
||||||
|
sx={{
|
||||||
fontSize: '12px',
|
fontSize: '12px',
|
||||||
fontWeight: 600
|
fontWeight: 600,
|
||||||
}}>Editing Message</Typography>
|
}}
|
||||||
|
>
|
||||||
|
Editing Message
|
||||||
|
</Typography>
|
||||||
) : (
|
) : (
|
||||||
<Typography sx={{
|
<Typography
|
||||||
|
sx={{
|
||||||
fontSize: '12px',
|
fontSize: '12px',
|
||||||
fontWeight: 600
|
fontWeight: 600,
|
||||||
}}>Replied to {message?.senderName || message?.senderAddress}</Typography>
|
}}
|
||||||
|
>
|
||||||
|
Replied to {message?.senderName || message?.senderAddress}
|
||||||
|
</Typography>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{message?.messageText && (
|
{message?.messageText && (
|
||||||
@ -542,32 +621,33 @@ export const ReplyPreview = ({message, isEdit})=> {
|
|||||||
Underline,
|
Underline,
|
||||||
Highlight,
|
Highlight,
|
||||||
Mention,
|
Mention,
|
||||||
TextStyle
|
TextStyle,
|
||||||
])}
|
])}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{message?.decryptedData?.type === "notification" ? (
|
|
||||||
|
{message?.decryptedData?.type === 'notification' ? (
|
||||||
<MessageDisplay htmlContent={message.decryptedData?.data?.message} />
|
<MessageDisplay htmlContent={message.decryptedData?.data?.message} />
|
||||||
) : (
|
) : (
|
||||||
<MessageDisplay isReply htmlContent={message.text} />
|
<MessageDisplay isReply htmlContent={message.text} />
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
)
|
const MessageWragger = ({ lastMessage, onSeen, isLast, children }) => {
|
||||||
}
|
if (lastMessage) {
|
||||||
|
|
||||||
const MessageWragger = ({lastMessage, onSeen, isLast, children})=> {
|
|
||||||
|
|
||||||
if(lastMessage){
|
|
||||||
return (
|
return (
|
||||||
<WatchComponent onSeen={onSeen} isLast={isLast}>{children}</WatchComponent>
|
<WatchComponent onSeen={onSeen} isLast={isLast}>
|
||||||
)
|
{children}
|
||||||
|
</WatchComponent>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return children
|
return children;
|
||||||
}
|
};
|
||||||
|
|
||||||
const WatchComponent = ({onSeen, isLast, children})=> {
|
const WatchComponent = ({ onSeen, isLast, children }) => {
|
||||||
const { ref, inView } = useInView({
|
const { ref, inView } = useInView({
|
||||||
threshold: 0.7, // Fully visible
|
threshold: 0.7, // Fully visible
|
||||||
triggerOnce: true, // Only trigger once when it becomes visible
|
triggerOnce: true, // Only trigger once when it becomes visible
|
||||||
@ -577,20 +657,22 @@ const WatchComponent = ({onSeen, isLast, children})=> {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (inView && isLast && onSeen) {
|
if (inView && isLast && onSeen) {
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
onSeen();
|
onSeen();
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
}
|
}
|
||||||
}, [inView, isLast, onSeen]);
|
}, [inView, isLast, onSeen]);
|
||||||
|
|
||||||
return <div ref={ref} style={{
|
return (
|
||||||
width: '100%',
|
<div
|
||||||
|
ref={ref}
|
||||||
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'center'
|
justifyContent: 'center',
|
||||||
}}>
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
}
|
};
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
.tiptap {
|
.tiptap {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
color: white; /* Set default font color to white */
|
color: ''; /* Set default font color to white */
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,21 +105,21 @@
|
|||||||
color: white; /* Ensure paragraph text color is white */
|
color: white; /* Ensure paragraph text color is white */
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
}
|
}
|
||||||
.tiptap p.is-editor-empty:first-child::before {
|
.tiptap p.is-editor-empty:first-child::before {
|
||||||
color: #adb5bd;
|
color: #adb5bd;
|
||||||
content: attr(data-placeholder);
|
content: attr(data-placeholder);
|
||||||
float: left;
|
float: left;
|
||||||
height: 0;
|
height: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tiptap p:empty::before {
|
.tiptap p:empty::before {
|
||||||
content: '';
|
content: '';
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tiptap a {
|
.tiptap a {
|
||||||
color: cadetblue
|
color: cadetblue;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tiptap img {
|
.tiptap img {
|
||||||
@ -131,13 +131,12 @@
|
|||||||
font-size: 12px !important;
|
font-size: 12px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tiptap [data-type="mention"] {
|
.tiptap [data-type='mention'] {
|
||||||
box-decoration-break: clone;
|
box-decoration-break: clone;
|
||||||
color: lightblue;
|
color: lightblue;
|
||||||
padding: 0.1rem 0.3rem;
|
padding: 0.1rem 0.3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.unread-divider {
|
.unread-divider {
|
||||||
width: 90%;
|
width: 90%;
|
||||||
color: white;
|
color: white;
|
Loading…
x
Reference in New Issue
Block a user