From a441baf0815d1703080355a8fcc928b5a99d0cc0 Mon Sep 17 00:00:00 2001 From: Ahmed yasser <26207361+1AhmedYasser@users.noreply.github.com> Date: Wed, 23 Oct 2024 11:30:49 +0300 Subject: [PATCH] Merging V2.0.1 to dev (#321) * Fixed Chat tab focus & messages if the user is already focused (#311) * Refactor message-to-bot internal endpoint (#308) * Show selected button title instead of payload in chat * Handle empty message content * Update .env * Update .env * Update ci-build-image.yml * Update .env * Added bold text detection * Added bold text detection (#312) * Update .env * Added Markdown Support * Handled Bold Text Detection in scss * Update .env * Updated chat style for small screens. * Update .env * Fix parsing percentages in chat (#318) * Update .env --------- Co-authored-by: Bahaa Alsharif Co-authored-by: jaX10bt <132996313+jaX10bt@users.noreply.github.com> Co-authored-by: Varmo <101868197+varmoh@users.noreply.github.com> Co-authored-by: danyil Co-authored-by: Mihhail Kohhantsuk <32133759+Minwasko@users.noreply.github.com> --- .env | 2 +- .github/workflows/ci-build-image.yml | 2 +- package.json | 3 ++- src/components/chat-content/chat-content.tsx | 3 ++- .../chat-keypad/chat-keypad.module.scss | 3 +++ src/components/chat-keypad/chat-keypad.tsx | 4 +-- .../chat-message/chat-message.module.scss | 1 - src/components/chat-message/chat-message.tsx | 10 ++++++- .../message-types/Markdownify.tsx | 15 +++++++++++ .../message-types/admin-message.tsx | 10 ++++--- .../message-types/client-message.tsx | 10 +++---- .../message-types/linkifier.test.tsx | 2 +- .../chat-message/message-types/linkifier.tsx | 27 ------------------- src/components/chat/chat.module.scss | 9 ++++++- src/components/chat/chat.tsx | 13 ++++++++- src/scss/_mixins.scss | 5 +++- src/scss/_typography.scss | 3 +++ src/translations/en/common.json | 5 ++-- src/translations/et/common.json | 5 ++-- src/utils/chat-utils.ts | 4 +-- 20 files changed, 82 insertions(+), 54 deletions(-) create mode 100644 src/components/chat-message/message-types/Markdownify.tsx delete mode 100644 src/components/chat-message/message-types/linkifier.tsx diff --git a/.env b/.env index e4b443b..adbf39b 100644 --- a/.env +++ b/.env @@ -1,5 +1,5 @@ RELEASE=PRE-ALPHA-test VERSION=1 BUILD=1 -FIX=12 +FIX=17 diff --git a/.github/workflows/ci-build-image.yml b/.github/workflows/ci-build-image.yml index a5333c7..c7ca2f0 100644 --- a/.github/workflows/ci-build-image.yml +++ b/.github/workflows/ci-build-image.yml @@ -3,7 +3,7 @@ name: Build and publish Chat-Widget on: push: branches: - - dev + - v2.0.1 paths: - '.env' diff --git a/package.json b/package.json index 3d6f59b..d2e8fcd 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "@babel/runtime": "^7.12.13", "@reduxjs/toolkit": "^1.6.2", "@types/styled-components": "^5.1.19", + "@xmldom/xmldom": "^0.7.5", "axios": "^1.6.8", "classnames": "^2.3.1", "cross-env": "^7.0.3", @@ -17,8 +18,8 @@ "linkify-react": "^3.0.3", "linkifyjs": "^3.0.3", "luxon": "^2.5.2", + "markdown-to-jsx": "^7.5.0", "msw": "^1.3.3", - "@xmldom/xmldom": "^0.7.5", "overlayscrollbars": "^1.13.1", "overlayscrollbars-react": "^0.2.3", "primeicons": "^4.1.0", diff --git a/src/components/chat-content/chat-content.tsx b/src/components/chat-content/chat-content.tsx index c380090..47deecc 100644 --- a/src/components/chat-content/chat-content.tsx +++ b/src/components/chat-content/chat-content.tsx @@ -33,7 +33,7 @@ const ChatContent = (): JSX.Element => { scrollbars: { visibility: 'auto', autoHide: 'leave' }, }} > - {messages.map((message) => { + {messages.map((message, index) => { if(message.id === "estimatedWaiting" && message.content === "hidden") return <>; if(message.id === "estimatedWaiting") @@ -43,6 +43,7 @@ const ChatContent = (): JSX.Element => { 0 ? messages[index - 1] : undefined} /> ); })} diff --git a/src/components/chat-keypad/chat-keypad.module.scss b/src/components/chat-keypad/chat-keypad.module.scss index 320275a..511e240 100644 --- a/src/components/chat-keypad/chat-keypad.module.scss +++ b/src/components/chat-keypad/chat-keypad.module.scss @@ -25,6 +25,9 @@ border-bottom: 1px solid $color-primary; border-radius: 0; padding-bottom: 5px; + height: 1.5em; + resize: none; + overflow: hidden; color: inherit; font: inherit; &::placeholder { diff --git a/src/components/chat-keypad/chat-keypad.tsx b/src/components/chat-keypad/chat-keypad.tsx index 00fcc3b..59e6d57 100644 --- a/src/components/chat-keypad/chat-keypad.tsx +++ b/src/components/chat-keypad/chat-keypad.tsx @@ -109,7 +109,7 @@ const ChatKeyPad = (): JSX.Element => { if (!isInputValid()) return; const message: Message = { chatId: chatId ?? "", - content: encodeURIComponent(userInput), + content: userInput, file: userInputFile, authorTimestamp: new Date().toISOString(), authorRole: AUTHOR_ROLES.END_USER, @@ -168,7 +168,7 @@ const ChatKeyPad = (): JSX.Element => {
{errorMessage}
- { +const ChatMessage = (props: { message: Message, previousMessage?: Message }): JSX.Element => { const { t } = useTranslation(); const { message, message: { authorRole, event }, + previousMessage, } = props; const endChatMessage = useMemo( @@ -26,6 +28,12 @@ const ChatMessage = (props: { message: Message }): JSX.Element => { [] ); + if(authorRole === AUTHOR_ROLES.END_USER && previousMessage && previousMessage.authorRole !== AUTHOR_ROLES.END_USER && previousMessage?.buttons) { + const selectedButton = parseButtons(previousMessage).find(x => x.payload === message.content); + if(selectedButton) + return + } + switch (event) { case CHAT_EVENTS.MESSAGE_READ: case CHAT_EVENTS.USER_AUTHENTICATED: diff --git a/src/components/chat-message/message-types/Markdownify.tsx b/src/components/chat-message/message-types/Markdownify.tsx new file mode 100644 index 0000000..f2fb7a3 --- /dev/null +++ b/src/components/chat-message/message-types/Markdownify.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import Markdown from "markdown-to-jsx"; + +interface MarkdownifyProps { + message: string | undefined; +} +const Markdownify: React.FC = ({ message }) => ( +
+ + { message ?? '' } + +
+); + +export default Markdownify; diff --git a/src/components/chat-message/message-types/admin-message.tsx b/src/components/chat-message/message-types/admin-message.tsx index 98d528a..1e3bf24 100644 --- a/src/components/chat-message/message-types/admin-message.tsx +++ b/src/components/chat-message/message-types/admin-message.tsx @@ -10,7 +10,6 @@ import { RATING_TYPES, isHiddenFeatureEnabled, } from "../../../constants"; -import Linkifier from "./linkifier"; import Thumbs from "../../../static/icons/thumbs.svg"; import { sendMessageWithRating, @@ -22,6 +21,7 @@ import ChatOptionGroup from "./chat-option-group"; import { parseButtons, parseOptions } from "../../../utils/chat-utils"; import useChatSelector from "../../../hooks/use-chat-selector"; import { useTranslation } from "react-i18next"; +import Markdownify from "./Markdownify"; const leftAnimation = { animate: { opacity: 1, x: 0 }, @@ -87,8 +87,12 @@ const AdminMessage = ({ message }: { message: Message }): JSX.Element => { styles.emergency_content }`} > - - {hasOptions && !message.content && t('widget.action.select')} + + {!message.content && ( + hasOptions || hasButtons + ? t('widget.action.select') + : {t('widget.error.empty')} + )}
{ - const { - message: { content }, - } = props; +const ClientMessage = (props: { message?: Message, content?: string }): JSX.Element => { + const content = props.message?.content || props.content; if (props.message?.file) { return ( @@ -53,7 +51,7 @@ const ClientMessage = (props: { message: Message }): JSX.Element => { Person icon
- +
diff --git a/src/components/chat-message/message-types/linkifier.test.tsx b/src/components/chat-message/message-types/linkifier.test.tsx index 38bd113..49ce6a4 100644 --- a/src/components/chat-message/message-types/linkifier.test.tsx +++ b/src/components/chat-message/message-types/linkifier.test.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; -import Linkifier from './linkifier'; +import Linkifier from './Markdownify'; describe('Linkifier', () => { it('should render message link between strings', () => { diff --git a/src/components/chat-message/message-types/linkifier.tsx b/src/components/chat-message/message-types/linkifier.tsx deleted file mode 100644 index fec1f57..0000000 --- a/src/components/chat-message/message-types/linkifier.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; -import Linkify from 'linkify-react'; - -interface LinkifierProps { - message: string | undefined; -} - -const URL_REGEX = /^(https?:\/\/|www\.)[a-zA-ZõäöüÕÄÖÜ0-9.-]+[\\/]?$/; - -const Linkifier: React.FC = ({ message }) => ( -
- URL_REGEX.test(value), - email: false, - }, - }} - > - {message || ''} - -
-); - -export default Linkifier; diff --git a/src/components/chat/chat.module.scss b/src/components/chat/chat.module.scss index eef0295..22dcccf 100644 --- a/src/components/chat/chat.module.scss +++ b/src/components/chat/chat.module.scss @@ -27,6 +27,10 @@ border-radius: 8px; font-size: 14px; line-height: 1.5; + + b, strong { + font-family: $font-chat-bold; + } } .authenticated { @@ -35,7 +39,10 @@ @media screen and (max-width: 480px) { .chat { - height: auto; + position: fixed; + top: 0; + right: 0; + height: 100vh; width: 100%; } } diff --git a/src/components/chat/chat.tsx b/src/components/chat/chat.tsx index 727ba9e..80960a4 100644 --- a/src/components/chat/chat.tsx +++ b/src/components/chat/chat.tsx @@ -43,6 +43,7 @@ import useReloadChatEndEffect from "../../hooks/use-reload-chat-end-effect"; import useWindowDimensions from "../../hooks/useWindowDimensions"; import ResponseErrorNotification from "../response-error-notification/response-error-notification"; import useTabActive from '../../hooks/useTabActive'; +import { use } from "i18next"; const RESIZABLE_HANDLES = { topLeft: true, @@ -78,6 +79,8 @@ const Chat = (): JSX.Element => { const isTabActive = useTabActive(); + const [isFocused, setIsFocused] = useState(true); + const { burokrattOnlineStatus, showConfirmationModal } = useAppSelector((state) => state.widget); useEffect(() => { @@ -169,6 +172,7 @@ const Chat = (): JSX.Element => { useEffect(() => { if ( isTabActive && + isFocused && messages.length > 0 && !messages[messages.length - 1].event && messages[messages.length - 1].authorRole === AUTHOR_ROLES.BACKOFFICE_USER @@ -181,7 +185,14 @@ const Chat = (): JSX.Element => { }; dispatch(sendNewMessage(message)); } - }, [isTabActive]); + }, [isTabActive, isFocused, messages]); + + window.onfocus = function () { + setIsFocused(true); + }; + window.onblur = function () { + setIsFocused(false); + }; return (
diff --git a/src/scss/_mixins.scss b/src/scss/_mixins.scss index c348043..7661cd0 100644 --- a/src/scss/_mixins.scss +++ b/src/scss/_mixins.scss @@ -12,6 +12,7 @@ @mixin text-m-bold { @include text-m; + font-family: typography.$font-chat-bold; font-weight: 700; } @@ -25,6 +26,7 @@ @mixin text-s-bold { @include text-s; + font-family: typography.$font-chat-bold; font-weight: 700; } @@ -39,10 +41,11 @@ @mixin text-xs-bold { @include text-xs; + font-family: typography.$font-chat-bold; font-weight: 700; } @mixin button-s { @include text-xs; text-transform: uppercase; -} \ No newline at end of file +} diff --git a/src/scss/_typography.scss b/src/scss/_typography.scss index 812df59..2f2682d 100644 --- a/src/scss/_typography.scss +++ b/src/scss/_typography.scss @@ -22,4 +22,7 @@ $font-title: 'Aino Headline', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Ro $font-chat: 'Aino Regular', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; +$font-chat-bold: 'Aino Bold', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', + 'Helvetica Neue', sans-serif; + $font-size: 16px; diff --git a/src/translations/en/common.json b/src/translations/en/common.json index 0763dea..ff0fe35 100644 --- a/src/translations/en/common.json +++ b/src/translations/en/common.json @@ -77,7 +77,7 @@ "widget.action.download-chat": "Download conversation", "widget.action.forward-chat": "Forward to an email", "widget.action.forward-email": "Your email address", - "widget.action.select": "Choose one of the following options", + "widget.action.select": "Choose one of the following options:", "widget.form.phone": "Your phone number", "widget.form.email": "Your email address", "widget.form.message": "Type your question here", @@ -89,5 +89,6 @@ "widget.action.restartChat": "Start a new chat", "widget.action.continueChat": "Continue current chat", "widget.error.botError": "Sorry, the bot stopped working due to technical issues", - "widget.error.technicalProblems": "Sorry, there were technical problems" + "widget.error.technicalProblems": "Sorry, there were technical problems", + "widget.error.empty": "Empty message" } diff --git a/src/translations/et/common.json b/src/translations/et/common.json index 6ea5342..4fa431e 100644 --- a/src/translations/et/common.json +++ b/src/translations/et/common.json @@ -77,7 +77,7 @@ "widget.action.download-chat": "Lae vestlus alla", "widget.action.forward-chat": "Edasta vestlus e-posti", "widget.action.forward-email": "Teie e-posti aadress", - "widget.action.select": "Valige üks järgmistest valikutest", + "widget.action.select": "Valige üks järgmistest valikutest:", "widget.form.phone": "Teie telefon", "widget.form.email": "Teie e-posti aadress", "widget.form.message": "Täpsustage siin oma küsimust", @@ -89,5 +89,6 @@ "widget.action.restartChat": "Alustage uut vestlust", "widget.action.continueChat": "Jätka praegust vestlust", "widget.error.botError": "Vabandame, vestlus katkes tehniliste probleemide tõttu.", - "widget.error.technicalProblems": "Vabandame, tekkisid tehnilised probleemid" + "widget.error.technicalProblems": "Vabandame, tekkisid tehnilised probleemid", + "widget.error.empty": "Tühi sõnum" } diff --git a/src/utils/chat-utils.ts b/src/utils/chat-utils.ts index 30af3cd..387b546 100644 --- a/src/utils/chat-utils.ts +++ b/src/utils/chat-utils.ts @@ -6,7 +6,7 @@ export const parseButtons = (message: Message): MessageButton[] => { try { if(!message?.buttons || message.buttons === '') return []; - return JSON.parse(decodeURIComponent(message.buttons)) as MessageButton[]; + return JSON.parse(message.buttons) as MessageButton[]; } catch(e) { console.error(e); return []; @@ -17,7 +17,7 @@ export const parseOptions = (message: Message): string[] => { try { if(!message?.options || message.options === '') return []; - return JSON.parse(decodeURIComponent(message.options)) as string[]; + return JSON.parse(message.options) as string[]; } catch(e) { console.error(e); return [];