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