mirror of
https://github.com/cheveguerra/whaticket-community.git
synced 2026-04-18 03:39:29 +00:00
feat: added markdown styles in messaages
feat: added option to show / hide agent name
This commit is contained in:
@@ -15,11 +15,11 @@
|
||||
"formik": "^2.2.0",
|
||||
"i18next": "^19.8.2",
|
||||
"i18next-browser-languagedetector": "^6.0.1",
|
||||
"markdown-to-jsx": "^7.1.0",
|
||||
"mic-recorder-to-mp3": "^2.2.2",
|
||||
"qrcode.react": "^1.0.0",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-linkify": "^1.0.0-alpha",
|
||||
"react-modal-image": "^2.5.0",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-scripts": "3.4.3",
|
||||
|
||||
@@ -12,9 +12,10 @@ import Button from "@material-ui/core/Button";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
|
||||
import { i18n } from "../../translate/i18n";
|
||||
import LinkifyWithTargetBlank from "../LinkifyWithTargetBlank";
|
||||
|
||||
import ContactModal from "../ContactModal";
|
||||
import ContactDrawerSkeleton from "../ContactDrawerSkeleton";
|
||||
import MarkdownWrapper from "../MarkdownWrapper";
|
||||
|
||||
const drawerWidth = 320;
|
||||
|
||||
@@ -149,11 +150,9 @@ const ContactDrawer = ({ open, handleDrawerClose, contact, loading }) => {
|
||||
className={classes.contactExtraInfo}
|
||||
>
|
||||
<InputLabel>{info.name}</InputLabel>
|
||||
<LinkifyWithTargetBlank>
|
||||
<Typography noWrap style={{ paddingTop: 2 }}>
|
||||
{info.value}
|
||||
</Typography>
|
||||
</LinkifyWithTargetBlank>
|
||||
<Typography component="div" noWrap style={{ paddingTop: 2 }}>
|
||||
<MarkdownWrapper>{info.value}</MarkdownWrapper>
|
||||
</Typography>
|
||||
</Paper>
|
||||
))}
|
||||
</Paper>
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import React from "react";
|
||||
import Linkify from "react-linkify";
|
||||
|
||||
const componentDecorator = (href, text, key) => (
|
||||
<a href={href} key={key} target="_blank" rel="noopener noreferrer">
|
||||
{text}
|
||||
</a>
|
||||
);
|
||||
|
||||
const LinkifyWithTargetBlank = ({ children }) => {
|
||||
return <Linkify componentDecorator={componentDecorator}>{children}</Linkify>;
|
||||
};
|
||||
|
||||
export default LinkifyWithTargetBlank;
|
||||
46
frontend/src/components/MarkdownWrapper/index.js
Normal file
46
frontend/src/components/MarkdownWrapper/index.js
Normal file
@@ -0,0 +1,46 @@
|
||||
import React from "react";
|
||||
import Markdown from "markdown-to-jsx";
|
||||
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
markdownP: {
|
||||
marginBlockStart: 0,
|
||||
marginBlockEnd: 0,
|
||||
},
|
||||
}));
|
||||
|
||||
const CustomLink = ({ children, ...props }) => (
|
||||
<a {...props} target="_blank" rel="noopener noreferrer">
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
|
||||
const MarkdownWrapper = ({ children }) => {
|
||||
const classes = useStyles();
|
||||
const boldRegex = /\*(.*?)\*/g;
|
||||
|
||||
if (children && boldRegex.test(children)) {
|
||||
children = children.replace(boldRegex, "**$1**");
|
||||
}
|
||||
|
||||
return (
|
||||
<Markdown
|
||||
options={{
|
||||
disableParsingRawHTML: true,
|
||||
overrides: {
|
||||
a: { component: CustomLink },
|
||||
p: {
|
||||
props: {
|
||||
className: classes.markdownP,
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Markdown>
|
||||
);
|
||||
};
|
||||
|
||||
export default MarkdownWrapper;
|
||||
@@ -20,6 +20,7 @@ import ClearIcon from "@material-ui/icons/Clear";
|
||||
import MicIcon from "@material-ui/icons/Mic";
|
||||
import CheckCircleOutlineIcon from "@material-ui/icons/CheckCircleOutline";
|
||||
import HighlightOffIcon from "@material-ui/icons/HighlightOff";
|
||||
import { FormControlLabel, Switch } from "@material-ui/core";
|
||||
|
||||
import { i18n } from "../../translate/i18n";
|
||||
import api from "../../services/api";
|
||||
@@ -175,6 +176,17 @@ const MessageInput = ({ ticketStatus }) => {
|
||||
ReplyMessageContext
|
||||
);
|
||||
|
||||
const [signMessage, setSignMessage] = useState(false);
|
||||
const storedSignOption = localStorage.getItem("signOption");
|
||||
|
||||
useEffect(() => {
|
||||
if (storedSignOption === "true") setSignMessage(true);
|
||||
}, [storedSignOption]);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem("signOption", signMessage);
|
||||
}, [signMessage]);
|
||||
|
||||
useEffect(() => {
|
||||
inputRef.current.focus();
|
||||
}, [replyingMessage]);
|
||||
@@ -251,7 +263,9 @@ const MessageInput = ({ ticketStatus }) => {
|
||||
read: 1,
|
||||
fromMe: true,
|
||||
mediaUrl: "",
|
||||
body: `${username}: ${inputMessage.trim()}`,
|
||||
body: signMessage
|
||||
? `*${username}:*\n${inputMessage.trim()}`
|
||||
: inputMessage.trim(),
|
||||
quotedMsg: replyingMessage,
|
||||
};
|
||||
try {
|
||||
@@ -433,6 +447,22 @@ const MessageInput = ({ ticketStatus }) => {
|
||||
<AttachFileIcon className={classes.sendMessageIcons} />
|
||||
</IconButton>
|
||||
</label>
|
||||
<FormControlLabel
|
||||
style={{ marginRight: 7, color: "gray" }}
|
||||
label={i18n.t("messagesInput.signMessage")}
|
||||
labelPlacement="start"
|
||||
control={
|
||||
<Switch
|
||||
size="small"
|
||||
checked={signMessage}
|
||||
onChange={() => {
|
||||
setSignMessage(prevState => !prevState);
|
||||
}}
|
||||
name="showAllTickets"
|
||||
color="primary"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<div className={classes.messageInputWrapper}>
|
||||
<InputBase
|
||||
inputRef={input => {
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
GetApp,
|
||||
} from "@material-ui/icons";
|
||||
|
||||
import LinkifyWithTargetBlank from "../LinkifyWithTargetBlank";
|
||||
import MarkdownWrapper from "../MarkdownWrapper";
|
||||
import ModalImageCors from "../ModalImageCors";
|
||||
import MessageOptionsMenu from "../MessageOptionsMenu";
|
||||
import whatsBackground from "../../assets/wa-background.png";
|
||||
@@ -556,7 +556,7 @@ const MessagesList = ({ ticketId, isGroup, setReplyingMessage }) => {
|
||||
const viewMessagesList = messagesList.map((message, index) => {
|
||||
if (!message.fromMe) {
|
||||
return (
|
||||
<LinkifyWithTargetBlank key={message.id}>
|
||||
<React.Fragment key={message.id}>
|
||||
{renderDailyTimestamps(message, index)}
|
||||
{renderMessageDivider(message, index)}
|
||||
<div className={classes.messageLeft}>
|
||||
@@ -578,17 +578,17 @@ const MessagesList = ({ ticketId, isGroup, setReplyingMessage }) => {
|
||||
{message.mediaUrl && checkMessageMedia(message)}
|
||||
<div className={classes.textContentItem}>
|
||||
{message.quotedMsg && renderQuotedMessage(message)}
|
||||
{message.body}
|
||||
<MarkdownWrapper>{message.body}</MarkdownWrapper>
|
||||
<span className={classes.timestamp}>
|
||||
{format(parseISO(message.createdAt), "HH:mm")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</LinkifyWithTargetBlank>
|
||||
</React.Fragment>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<LinkifyWithTargetBlank key={message.id}>
|
||||
<React.Fragment key={message.id}>
|
||||
{renderDailyTimestamps(message, index)}
|
||||
{renderMessageDivider(message, index)}
|
||||
<div className={classes.messageRight}>
|
||||
@@ -616,14 +616,14 @@ const MessagesList = ({ ticketId, isGroup, setReplyingMessage }) => {
|
||||
/>
|
||||
)}
|
||||
{message.quotedMsg && renderQuotedMessage(message)}
|
||||
{message.body}
|
||||
<MarkdownWrapper>{message.body}</MarkdownWrapper>
|
||||
<span className={classes.timestamp}>
|
||||
{format(parseISO(message.createdAt), "HH:mm")}
|
||||
{renderMessageAck(message)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</LinkifyWithTargetBlank>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -18,6 +18,7 @@ import { i18n } from "../../translate/i18n";
|
||||
|
||||
import api from "../../services/api";
|
||||
import ButtonWithSpinner from "../ButtonWithSpinner";
|
||||
import MarkdownWrapper from "../MarkdownWrapper";
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
ticket: {
|
||||
@@ -143,6 +144,7 @@ const TicketListItem = ({ ticket }) => {
|
||||
></Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
disableTypography
|
||||
primary={
|
||||
<span className={classes.contactNameWrapper}>
|
||||
<Typography
|
||||
@@ -185,7 +187,11 @@ const TicketListItem = ({ ticket }) => {
|
||||
variant="body2"
|
||||
color="textSecondary"
|
||||
>
|
||||
{ticket.lastMessage || <br />}
|
||||
{ticket.lastMessage ? (
|
||||
<MarkdownWrapper>{ticket.lastMessage}</MarkdownWrapper>
|
||||
) : (
|
||||
<br />
|
||||
)}
|
||||
</Typography>
|
||||
|
||||
<Badge
|
||||
|
||||
@@ -287,6 +287,7 @@ const messages = {
|
||||
messagesInput: {
|
||||
placeholderOpen: "Type a message",
|
||||
placeholderClosed: "Reopen or accept this ticket to send a message.",
|
||||
signMessage: "Sign",
|
||||
},
|
||||
contactDrawer: {
|
||||
header: "Contact details",
|
||||
@@ -323,6 +324,8 @@ const messages = {
|
||||
},
|
||||
},
|
||||
backendErrors: {
|
||||
ERR_NO_OTHER_WHATSAPP:
|
||||
"There must be at lest one default WhatsApp connection.",
|
||||
ERR_NO_DEF_WAPP_FOUND:
|
||||
"No default WhatsApp found. Check connections page.",
|
||||
ERR_WAPP_NOT_INITIALIZED:
|
||||
|
||||
@@ -292,6 +292,7 @@ const messages = {
|
||||
placeholderOpen: "Escribe un mensaje",
|
||||
placeholderClosed:
|
||||
"Vuelva a abrir o acepte este ticket para enviar un mensaje.",
|
||||
signMessage: "Firmar",
|
||||
},
|
||||
contactDrawer: {
|
||||
header: "Detalles del contacto",
|
||||
@@ -329,6 +330,8 @@ const messages = {
|
||||
},
|
||||
},
|
||||
backendErrors: {
|
||||
ERR_NO_OTHER_WHATSAPP:
|
||||
"Debe haber al menos una conexión de WhatsApp predeterminada.",
|
||||
ERR_NO_DEF_WAPP_FOUND:
|
||||
"No se encontró WhatsApp predeterminado. Verifique la página de conexiones.",
|
||||
ERR_WAPP_NOT_INITIALIZED:
|
||||
|
||||
@@ -290,6 +290,7 @@ const messages = {
|
||||
placeholderOpen: "Digite uma mensagem",
|
||||
placeholderClosed:
|
||||
"Reabra ou aceite esse ticket para enviar uma mensagem.",
|
||||
signMessage: "Assinar",
|
||||
},
|
||||
contactDrawer: {
|
||||
header: "Dados do contato",
|
||||
@@ -302,8 +303,7 @@ const messages = {
|
||||
delete: "Deletar",
|
||||
transfer: "Transferir",
|
||||
confirmationModal: {
|
||||
title: "Deletar o ticket #",
|
||||
titleFrom: "do contato ",
|
||||
title: "Deletar o ticket do contato",
|
||||
message:
|
||||
"Atenção! Todas as mensagens relacionadas ao ticket serão perdidas.",
|
||||
},
|
||||
@@ -327,6 +327,7 @@ const messages = {
|
||||
},
|
||||
},
|
||||
backendErrors: {
|
||||
ERR_NO_OTHER_WHATSAPP: "Deve haver pelo menos um WhatsApp padrão.",
|
||||
ERR_NO_DEF_WAPP_FOUND:
|
||||
"Nenhum WhatsApp padrão encontrado. Verifique a página de conexões.",
|
||||
ERR_WAPP_NOT_INITIALIZED:
|
||||
|
||||
Reference in New Issue
Block a user