Skip to content

Commit 53fcb07

Browse files
committed
update links color
1 parent 4f99d2b commit 53fcb07

File tree

6 files changed

+88
-47
lines changed

6 files changed

+88
-47
lines changed

dist/components/bubbles/BotBubble.d.ts.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/components/bubbles/GuestBubble.d.ts.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/web.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/web.umd.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/bubbles/BotBubble.tsx

Lines changed: 66 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ const defaultFontSize = 16;
4040
const defaultFeedbackColor = '#3B81F6';
4141

4242
export const BotBubble = (props: Props) => {
43-
let botMessageEl: HTMLDivElement | undefined;
4443
let botDetailsEl: HTMLDetailsElement | undefined;
4544

4645
Marked.setOptions({ isNoP: true, sanitize: props.renderHTML !== undefined ? !props.renderHTML : true });
@@ -51,6 +50,55 @@ export const BotBubble = (props: Props) => {
5150
const [copiedMessage, setCopiedMessage] = createSignal(false);
5251
const [thumbsUpColor, setThumbsUpColor] = createSignal(props.feedbackColor ?? defaultFeedbackColor); // default color
5352
const [thumbsDownColor, setThumbsDownColor] = createSignal(props.feedbackColor ?? defaultFeedbackColor); // default color
53+
54+
// Store a reference to the bot message element for the copyMessageToClipboard function
55+
const [botMessageElement, setBotMessageElement] = createSignal<HTMLElement | null>(null);
56+
57+
const setBotMessageRef = (el: HTMLSpanElement) => {
58+
if (el) {
59+
el.innerHTML = Marked.parse(props.message.message);
60+
61+
// Apply textColor to all links, headings, and other markdown elements
62+
const textColor = props.textColor ?? defaultTextColor;
63+
el.querySelectorAll('a, h1, h2, h3, h4, h5, h6, strong, em, blockquote, li, code, pre').forEach((element) => {
64+
(element as HTMLElement).style.color = textColor;
65+
});
66+
67+
// Set target="_blank" for links
68+
el.querySelectorAll('a').forEach((link) => {
69+
link.target = '_blank';
70+
});
71+
72+
// Store the element ref for the copy function
73+
setBotMessageElement(el);
74+
75+
if (props.message.rating) {
76+
setRating(props.message.rating);
77+
if (props.message.rating === 'THUMBS_UP') {
78+
setThumbsUpColor('#006400');
79+
} else if (props.message.rating === 'THUMBS_DOWN') {
80+
setThumbsDownColor('#8B0000');
81+
}
82+
}
83+
if (props.fileAnnotations && props.fileAnnotations.length) {
84+
for (const annotations of props.fileAnnotations) {
85+
const button = document.createElement('button');
86+
button.textContent = annotations.fileName;
87+
button.className =
88+
'py-2 px-4 mb-2 justify-center font-semibold text-white focus:outline-none flex items-center disabled:opacity-50 disabled:cursor-not-allowed disabled:brightness-100 transition-all filter hover:brightness-90 active:brightness-75 file-annotation-button';
89+
button.addEventListener('click', function () {
90+
downloadFile(annotations);
91+
});
92+
const svgContainer = document.createElement('div');
93+
svgContainer.className = 'ml-2';
94+
svgContainer.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-download" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="#ffffff" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2v-2" /><path d="M7 11l5 5l5 -5" /><path d="M12 4l0 12" /></svg>`;
95+
96+
button.appendChild(svgContainer);
97+
el.appendChild(button);
98+
}
99+
}
100+
}
101+
};
54102

55103
const downloadFile = async (fileAnnotation: any) => {
56104
try {
@@ -74,7 +122,7 @@ export const BotBubble = (props: Props) => {
74122

75123
const copyMessageToClipboard = async () => {
76124
try {
77-
const text = botMessageEl ? botMessageEl?.textContent : '';
125+
const text = botMessageElement() ? botMessageElement()?.textContent : '';
78126
await navigator.clipboard.writeText(text || '');
79127
setCopiedMessage(true);
80128
setTimeout(() => {
@@ -201,38 +249,6 @@ export const BotBubble = (props: Props) => {
201249
};
202250

203251
onMount(() => {
204-
if (botMessageEl) {
205-
botMessageEl.innerHTML = Marked.parse(props.message.message);
206-
botMessageEl.querySelectorAll('a').forEach((link) => {
207-
link.target = '_blank';
208-
});
209-
if (props.message.rating) {
210-
setRating(props.message.rating);
211-
if (props.message.rating === 'THUMBS_UP') {
212-
setThumbsUpColor('#006400');
213-
} else if (props.message.rating === 'THUMBS_DOWN') {
214-
setThumbsDownColor('#8B0000');
215-
}
216-
}
217-
if (props.fileAnnotations && props.fileAnnotations.length) {
218-
for (const annotations of props.fileAnnotations) {
219-
const button = document.createElement('button');
220-
button.textContent = annotations.fileName;
221-
button.className =
222-
'py-2 px-4 mb-2 justify-center font-semibold text-white focus:outline-none flex items-center disabled:opacity-50 disabled:cursor-not-allowed disabled:brightness-100 transition-all filter hover:brightness-90 active:brightness-75 file-annotation-button';
223-
button.addEventListener('click', function () {
224-
downloadFile(annotations);
225-
});
226-
const svgContainer = document.createElement('div');
227-
svgContainer.className = 'ml-2';
228-
svgContainer.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-download" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="#ffffff" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2v-2" /><path d="M7 11l5 5l5 -5" /><path d="M12 4l0 12" /></svg>`;
229-
230-
button.appendChild(svgContainer);
231-
botMessageEl.appendChild(button);
232-
}
233-
}
234-
}
235-
236252
if (botDetailsEl && props.isLoading) {
237253
botDetailsEl.open = true;
238254
}
@@ -247,6 +263,20 @@ export const BotBubble = (props: Props) => {
247263
});
248264

249265
const renderArtifacts = (item: Partial<FileUpload>) => {
266+
// Instead of onMount, we'll use a callback ref to apply styles
267+
const setArtifactRef = (el: HTMLSpanElement) => {
268+
if (el) {
269+
const textColor = props.textColor ?? defaultTextColor;
270+
el.querySelectorAll('a, h1, h2, h3, h4, h5, h6, strong, em, blockquote, li, code, pre').forEach((element) => {
271+
(element as HTMLElement).style.color = textColor;
272+
});
273+
274+
el.querySelectorAll('a').forEach((link) => {
275+
link.target = '_blank';
276+
});
277+
}
278+
};
279+
250280
return (
251281
<>
252282
<Show when={item.type === 'png' || item.type === 'jpeg'}>
@@ -271,6 +301,7 @@ export const BotBubble = (props: Props) => {
271301
</Show>
272302
<Show when={item.type !== 'png' && item.type !== 'jpeg' && item.type !== 'html'}>
273303
<span
304+
ref={setArtifactRef}
274305
innerHTML={Marked.parse(item.data as string)}
275306
class="prose"
276307
style={{
@@ -383,7 +414,7 @@ export const BotBubble = (props: Props) => {
383414
)}
384415
{props.message.message && (
385416
<span
386-
ref={botMessageEl}
417+
ref={setBotMessageRef}
387418
class="px-4 py-2 ml-2 max-w-full chatbot-host-bubble prose"
388419
data-testid="host-bubble"
389420
style={{

src/components/bubbles/GuestBubble.tsx

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { For, Show, onMount } from 'solid-js';
1+
import { For, Show } from 'solid-js';
22
import { Avatar } from '../avatars/Avatar';
33
import { Marked } from '@ts-stack/markdown';
44
import { FileUpload, MessageType } from '../Bot';
@@ -22,15 +22,25 @@ const defaultTextColor = '#ffffff';
2222
const defaultFontSize = 16;
2323

2424
export const GuestBubble = (props: Props) => {
25-
let userMessageEl: HTMLDivElement | undefined;
26-
2725
Marked.setOptions({ isNoP: true, sanitize: props.renderHTML !== undefined ? !props.renderHTML : true });
2826

29-
onMount(() => {
30-
if (userMessageEl) {
31-
userMessageEl.innerHTML = Marked.parse(props.message.message);
27+
// Callback ref to set innerHTML and apply text color to all Markdown elements
28+
const setUserMessageRef = (el: HTMLSpanElement) => {
29+
if (el) {
30+
el.innerHTML = Marked.parse(props.message.message);
31+
32+
// Apply textColor to all links, headings, and other markdown elements
33+
const textColor = props.textColor ?? defaultTextColor;
34+
el.querySelectorAll('a, h1, h2, h3, h4, h5, h6, strong, em, blockquote, li, code, pre').forEach((element) => {
35+
(element as HTMLElement).style.color = textColor;
36+
});
37+
38+
// Set target="_blank" for links
39+
el.querySelectorAll('a').forEach((link) => {
40+
link.target = '_blank';
41+
});
3242
}
33-
});
43+
};
3444

3545
const renderFileUploads = (item: Partial<FileUpload>) => {
3646
if (item?.mime?.startsWith('image/')) {
@@ -82,7 +92,7 @@ export const GuestBubble = (props: Props) => {
8292
)}
8393
{props.message.message && (
8494
<span
85-
ref={userMessageEl}
95+
ref={setUserMessageRef}
8696
class="mr-2 whitespace-pre-wrap"
8797
style={{ 'font-size': props.fontSize ? `${props.fontSize}px` : `${defaultFontSize}px` }}
8898
/>

0 commit comments

Comments
 (0)