@$2';
-
+const TWITTER_USERNAME_REPLACEMENT = '$1[@$2](http://twitter.com/$2)';
const TWITTER_HASH_REGEX = /(^|\s)#(\w+)/g;
-const TWITTER_HASH_REPLACEMENT =
- '$1#$2';
-
-const USER_MENTIONS_REGEX = /mention:\/\/(user|team)\/(\d+)\/(.+)/gm;
+const TWITTER_HASH_REPLACEMENT = '$1[#$2](https://twitter.com/hashtag/$2)';
class MessageFormatter {
constructor(message, isATweet = false, isAPrivateNote = false) {
- this.message = DOMPurify.sanitize(escapeHtml(message || ''));
+ this.message = message || '';
this.isAPrivateNote = isAPrivateNote;
this.isATweet = isATweet;
- this.marked = marked;
-
- const renderer = {
- heading(text) {
- return `${text}`;
- },
- link(url, title, text) {
- const mentionRegex = new RegExp(USER_MENTIONS_REGEX);
- if (url.match(mentionRegex)) {
- return `${text}`;
- }
- return `${text}`;
- },
- };
- this.marked.use({ renderer });
}
formatMessage() {
+ let updatedMessage = this.message;
if (this.isATweet && !this.isAPrivateNote) {
- const withUserName = this.message.replace(
+ updatedMessage = updatedMessage.replace(
TWITTER_USERNAME_REGEX,
TWITTER_USERNAME_REPLACEMENT
);
- const withHash = withUserName.replace(
+ updatedMessage = updatedMessage.replace(
TWITTER_HASH_REGEX,
TWITTER_HASH_REPLACEMENT
);
- const markedDownOutput = marked(withHash);
- return markedDownOutput;
}
- DOMPurify.addHook('afterSanitizeAttributes', afterSanitizeAttributes);
- return DOMPurify.sanitize(
- marked(this.message, { breaks: true, gfm: true })
- );
+ return md.render(updatedMessage);
}
get formattedMessage() {
diff --git a/app/javascript/shared/helpers/markdownIt/link.js b/app/javascript/shared/helpers/markdownIt/link.js
new file mode 100644
index 000000000..c5300391b
--- /dev/null
+++ b/app/javascript/shared/helpers/markdownIt/link.js
@@ -0,0 +1,69 @@
+// Process [@mention](mention://user/1/Pranav)
+const USER_MENTIONS_REGEX = /mention:\/\/(user|team)\/(\d+)\/(.+)/gm;
+
+const buildMentionTokens = () => (state, silent) => {
+ var label;
+ var labelEnd;
+ var labelStart;
+ var pos;
+ var res;
+ var token;
+ var href = '';
+ var max = state.posMax;
+
+ if (state.src.charCodeAt(state.pos) !== 0x5b /* [ */) {
+ return false;
+ }
+
+ labelStart = state.pos + 1;
+ labelEnd = state.md.helpers.parseLinkLabel(state, state.pos, true);
+
+ // parser failed to find ']', so it's not a valid link
+ if (labelEnd < 0) {
+ return false;
+ }
+
+ label = state.src.slice(labelStart, labelEnd);
+ pos = labelEnd + 1;
+
+ if (pos < max && state.src.charCodeAt(pos) === 0x28 /* ( */) {
+ pos += 1;
+ res = state.md.helpers.parseLinkDestination(state.src, pos, state.posMax);
+ if (res.ok) {
+ href = state.md.normalizeLink(res.str);
+ if (state.md.validateLink(href)) {
+ pos = res.pos;
+ } else {
+ href = '';
+ }
+ }
+ pos += 1;
+ }
+
+ if (!href.match(new RegExp(USER_MENTIONS_REGEX))) {
+ return false;
+ }
+
+ if (!silent) {
+ state.pos = labelStart;
+ state.posMax = labelEnd;
+
+ token = state.push('mention', '');
+ token.href = href;
+ token.content = label;
+ }
+
+ state.pos = pos;
+ state.posMax = max;
+
+ return true;
+};
+
+const renderMentions = () => (tokens, idx) => {
+ return `${tokens[idx].content}`;
+};
+
+export default function mentionPlugin(md) {
+ md.renderer.rules.mention = renderMentions(md);
+ md.inline.ruler.before('link', 'mention', buildMentionTokens(md));
+}
diff --git a/app/javascript/shared/helpers/specs/MessageFormatter.spec.js b/app/javascript/shared/helpers/specs/MessageFormatter.spec.js
index 1f2209b71..60c875c8a 100644
--- a/app/javascript/shared/helpers/specs/MessageFormatter.spec.js
+++ b/app/javascript/shared/helpers/specs/MessageFormatter.spec.js
@@ -6,14 +6,14 @@ describe('#MessageFormatter', () => {
const message =
'Chatwoot is an opensource tool. [Chatwoot](https://www.chatwoot.com)';
expect(new MessageFormatter(message).formattedMessage).toMatch(
- 'Chatwoot is an opensource tool. Chatwoot
'
+ 'Chatwoot is an opensource tool. Chatwoot
'
);
});
it('should format correctly', () => {
const message =
'Chatwoot is an opensource tool. https://www.chatwoot.com';
expect(new MessageFormatter(message).formattedMessage).toMatch(
- 'Chatwoot is an opensource tool. https://www.chatwoot.com
'
+ 'Chatwoot is an opensource tool. https://www.chatwoot.com
'
);
});
});
@@ -22,7 +22,8 @@ describe('#MessageFormatter', () => {
it('should format correctly', () => {
const message = '### opensource \n ## tool';
expect(new MessageFormatter(message).formattedMessage).toMatch(
- 'opensourcetool'
+ `opensource
+tool
`
);
});
});
@@ -39,7 +40,7 @@ describe('#MessageFormatter', () => {
expect(
new MessageFormatter(message, true, false).formattedMessage
).toMatch(
- '@chatwootapp is an opensource tool thanks @longnonexistenttwitterusername
'
+ '@chatwootapp is an opensource tool thanks @longnonexistenttwitterusername
'
);
});
@@ -48,7 +49,7 @@ describe('#MessageFormatter', () => {
expect(
new MessageFormatter(message, true, false).formattedMessage
).toMatch(
- '#chatwootapp is an opensource tool
'
+ '#chatwootapp is an opensource tool
'
);
});
});
@@ -90,7 +91,8 @@ describe('#MessageFormatter', () => {
const message =
'[xssLink](javascript:alert(document.cookie))\n[normalLink](https://google.com)**I am a bold text paragraph**';
expect(new MessageFormatter(message).formattedMessage).toMatch(
- 'xssLink
normalLinkI am a bold text paragraph
'
+ `[xssLink](javascript:alert(document.cookie))
+normalLinkI am a bold text paragraph
`
);
});
});
diff --git a/app/javascript/widget/assets/scss/_variables.scss b/app/javascript/widget/assets/scss/_variables.scss
index d3cf6cc1c..73c4fce1b 100755
--- a/app/javascript/widget/assets/scss/_variables.scss
+++ b/app/javascript/widget/assets/scss/_variables.scss
@@ -26,6 +26,9 @@ $space-big: 4rem;
$space-jumbo: 5rem;
$space-mega: 6.25rem;
+$border-radius-small: 0.1875rem;
+$border-radius-normal: 0.3125rem;
+
// font-weight
$font-weight-feather: 100;
$font-weight-light: 300;
@@ -74,8 +77,16 @@ $line-height: 1;
$footer-height: 11.2rem;
$header-expanded-height: $space-medium * 10;
-$font-family: 'Inter', -apple-system, system-ui, BlinkMacSystemFont, 'Segoe UI',
- Roboto, 'Helvetica Neue', Tahoma, Arial, sans-serif;
+$font-family: 'Inter',
+-apple-system,
+system-ui,
+BlinkMacSystemFont,
+'Segoe UI',
+Roboto,
+'Helvetica Neue',
+Tahoma,
+Arial,
+sans-serif;
// Break points
$break-point-medium: 667px;
diff --git a/app/javascript/widget/assets/scss/views/_conversation.scss b/app/javascript/widget/assets/scss/views/_conversation.scss
index 7124c52fb..24f4701fc 100644
--- a/app/javascript/widget/assets/scss/views/_conversation.scss
+++ b/app/javascript/widget/assets/scss/views/_conversation.scss
@@ -1,4 +1,4 @@
-.file-uploads .attachment-button+label {
+.file-uploads .attachment-button + label {
cursor: pointer;
}
@@ -61,7 +61,7 @@
}
.agent-message-wrap {
- +.agent-message-wrap {
+ + .agent-message-wrap {
margin-top: $space-micro;
.agent-message .chat-bubble {
@@ -69,11 +69,11 @@
}
}
- +.user-message-wrap {
+ + .user-message-wrap {
margin-top: $space-normal;
}
- &.has-response+.user-message-wrap {
+ &.has-response + .user-message-wrap {
margin-top: $space-micro;
.chat-bubble {
@@ -81,7 +81,7 @@
}
}
- &.has-response+.agent-message-wrap {
+ &.has-response + .agent-message-wrap {
margin-top: $space-normal;
}
}
@@ -117,7 +117,6 @@
}
}
-
.user.has-attachment {
.icon-wrap {
color: $color-white;
@@ -129,7 +128,7 @@
}
.user-message-wrap {
- +.user-message-wrap {
+ + .user-message-wrap {
margin-top: $space-micro;
.user-message .chat-bubble {
@@ -137,7 +136,7 @@
}
}
- +.agent-message-wrap {
+ + .agent-message-wrap {
margin-top: $space-normal;
}
}
@@ -147,7 +146,6 @@
}
}
-
.unread-messages {
display: flex;
flex-direction: column;
@@ -168,7 +166,7 @@
border: 1px solid $color-border-dark;
}
- +.chat-bubble-wrap {
+ + .chat-bubble-wrap {
.chat-bubble {
border-top-left-radius: $space-smaller;
}
@@ -190,7 +188,7 @@
border-radius: $space-two;
}
- +.chat-bubble-wrap {
+ + .chat-bubble-wrap {
.chat-bubble {
border-top-right-radius: $space-smaller;
}
@@ -206,7 +204,6 @@
}
}
-
.chat-bubble {
@include light-shadow;
border-radius: $space-two;
@@ -214,6 +211,7 @@
display: inline-block;
font-size: $font-size-default;
line-height: 1.5;
+ max-width: 100%;
padding: $space-slab $space-normal;
text-align: left;
word-break: break-word;
@@ -222,7 +220,7 @@
max-width: 100%;
}
- >a {
+ > a {
color: $color-primary;
word-break: break-all;
}
@@ -234,7 +232,7 @@
&.user {
border-bottom-right-radius: $space-smaller;
- >a {
+ > a {
color: $color-white;
}
}
diff --git a/app/javascript/widget/components/AgentMessageBubble.vue b/app/javascript/widget/components/AgentMessageBubble.vue
index 01fbe6faa..04b0808e5 100755
--- a/app/javascript/widget/components/AgentMessageBubble.vue
+++ b/app/javascript/widget/components/AgentMessageBubble.vue
@@ -5,7 +5,7 @@
!isCards && !isOptions && !isForm && !isArticle && !isCards && !isCSAT
"
class="chat-bubble agent"
- :class="$dm('bg-white', 'dark:bg-slate-700')"
+ :class="$dm('bg-white', 'dark:bg-slate-700 has-dark-mode')"
>
-
-
diff --git a/app/javascript/widget/components/ChatMessage.vue b/app/javascript/widget/components/ChatMessage.vue
index b63525242..b70c3b2c2 100755
--- a/app/javascript/widget/components/ChatMessage.vue
+++ b/app/javascript/widget/components/ChatMessage.vue
@@ -35,3 +35,56 @@ export default {
max-width: 90%;
}
+
+
diff --git a/app/javascript/widget/components/UserMessageBubble.vue b/app/javascript/widget/components/UserMessageBubble.vue
index 50bc99a3b..eed90865f 100755
--- a/app/javascript/widget/components/UserMessageBubble.vue
+++ b/app/javascript/widget/components/UserMessageBubble.vue
@@ -38,10 +38,31 @@ export default {
diff --git a/package.json b/package.json
index 62d2c63da..8b1ce3b45 100644
--- a/package.json
+++ b/package.json
@@ -43,9 +43,10 @@
"highlight.js": "~10.4.1",
"ionicons": "~2.0.1",
"js-cookie": "^2.2.1",
+ "markdown-it": "^13.0.1",
+ "markdown-it-link-attributes": "^4.0.1",
"logrocket": "^3.0.1",
"logrocket-vuex": "^0.0.3",
- "marked": "4.0.10",
"md5": "^2.3.0",
"ninja-keys": "^1.1.9",
"opus-recorder": "^8.0.5",
diff --git a/yarn.lock b/yarn.lock
index 2f78d9446..d31a238c1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4823,6 +4823,11 @@ argparse@^1.0.7:
dependencies:
sprintf-js "~1.0.2"
+argparse@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
+ integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
+
aria-query@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b"
@@ -7491,6 +7496,11 @@ entities@~2.0.0:
resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.3.tgz#5c487e5742ab93c15abb5da22759b8590ec03b7f"
integrity sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==
+entities@~3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/entities/-/entities-3.0.1.tgz#2b887ca62585e96db3903482d336c1006c3001d4"
+ integrity sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==
+
errno@^0.1.3, errno@~0.1.7:
version "0.1.8"
resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f"
@@ -10927,6 +10937,13 @@ linkify-it@^2.0.0:
dependencies:
uc.micro "^1.0.1"
+linkify-it@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-4.0.1.tgz#01f1d5e508190d06669982ba31a7d9f56a5751ec"
+ integrity sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==
+ dependencies:
+ uc.micro "^1.0.1"
+
lint-staged@10.5.4:
version "10.5.4"
resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-10.5.4.tgz#cd153b5f0987d2371fc1d2847a409a2fe705b665"
@@ -11337,6 +11354,11 @@ markdown-escapes@^1.0.0:
resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.4.tgz#c95415ef451499d7602b91095f3c8e8975f78535"
integrity sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==
+markdown-it-link-attributes@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/markdown-it-link-attributes/-/markdown-it-link-attributes-4.0.1.tgz#25751f2cf74fd91f0a35ba7b3247fa45f2056d88"
+ integrity sha512-pg5OK0jPLg62H4k7M9mRJLT61gUp9nvG0XveKYHMOOluASo9OEF13WlXrpAp2aj35LbedAy3QOCgQCw0tkLKAQ==
+
markdown-it@^10.0.0:
version "10.0.0"
resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-10.0.0.tgz#abfc64f141b1722d663402044e43927f1f50a8dc"
@@ -11348,10 +11370,16 @@ markdown-it@^10.0.0:
mdurl "^1.0.1"
uc.micro "^1.0.5"
-marked@4.0.10:
- version "4.0.10"
- resolved "https://registry.yarnpkg.com/marked/-/marked-4.0.10.tgz#423e295385cc0c3a70fa495e0df68b007b879423"
- integrity sha512-+QvuFj0nGgO970fySghXGmuw+Fd0gD2x3+MqCWLIPf5oxdv1Ka6b2q+z9RP01P/IaKPMEramy+7cNy/Lw8c3hw==
+markdown-it@^13.0.1:
+ version "13.0.1"
+ resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-13.0.1.tgz#c6ecc431cacf1a5da531423fc6a42807814af430"
+ integrity sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==
+ dependencies:
+ argparse "^2.0.1"
+ entities "~3.0.1"
+ linkify-it "^4.0.1"
+ mdurl "^1.0.1"
+ uc.micro "^1.0.5"
material-colors@^1.0.0:
version "1.2.6"