{this.translationManager.get('modal.newChatTitle')}
-
- {this.translationManager.get('modal.newChatBody', this.newChatConfirmationMessage)}
-
+
{this.translationManager.get('modal.newChatBody', this.newChatConfirmationMessage)}
-
@@ -1638,20 +1649,14 @@ export class OcsChat {
)}
{/* Messages */}
- {(
-
this.messageListRef = el}
- class="messages-container"
- >
+ {
+
(this.messageListRef = el)} class="messages-container">
{this.messages.length === 0 && this.getWelcomeMessages().length > 0 && (
{this.getWelcomeMessages().map((message, index) => (
))}
@@ -1659,25 +1664,13 @@ export class OcsChat {
)}
{/* Regular Chat Messages */}
{this.messages.map((message, index) => (
-
+
-
+
{message.attachments && message.attachments.length > 0 && (
{message.attachments.map((attachment, attachmentIndex) => (
@@ -1690,9 +1683,7 @@ export class OcsChat {
))}
)}
-
- {this.formatTime(message.created_at)}
-
+
{this.formatTime(message.created_at)}
))}
@@ -1709,17 +1700,14 @@ export class OcsChat {
)}
- )}
+ }
{/* Starter Questions */}
{this.messages.length === 0 && this.getStarterQuestions().length > 0 && (
{this.getStarterQuestions().map((question, index) => (
- this.handleStarterQuestionClick(question)}
- >
+ this.handleStarterQuestionClick(question)}>
{question}
@@ -1735,22 +1723,19 @@ export class OcsChat {
-
+
{selectedFile.file.name}
({this.formatFileSize(selectedFile.file.size)})
- {selectedFile.error && (
-
{selectedFile.error}
- )}
+ {selectedFile.error &&
{selectedFile.error}}
{selectedFile.uploaded && (
-
+
+
+
)}
-
this.removeSelectedFile(index)}
- class="selected-file-remove-button"
- aria-label={this.translationManager.get('attach.remove')}
- >
+ this.removeSelectedFile(index)} class="selected-file-remove-button" aria-label={this.translationManager.get('attach.remove')}>
+
))}
@@ -1762,57 +1747,59 @@ export class OcsChat {
+
-
{this.translationManager.get('branding.poweredBy')}{' '} Dimagi
+
+ {this.translationManager.get('branding.poweredBy')}{' '}
+
+ Dimagi
+
+
diff --git a/components/chat_widget/src/components/ocs-chat/ocs-chat_session_handling.spec.tsx b/components/chat_widget/src/components/ocs-chat/ocs-chat_session_handling.spec.tsx
index e090bb2a59..985a52324a 100644
--- a/components/chat_widget/src/components/ocs-chat/ocs-chat_session_handling.spec.tsx
+++ b/components/chat_widget/src/components/ocs-chat/ocs-chat_session_handling.spec.tsx
@@ -1,5 +1,5 @@
-import {newSpecPage} from '@stencil/core/testing';
-import {OcsChat} from './ocs-chat';
+import { newSpecPage } from '@stencil/core/testing';
+import { OcsChat } from './ocs-chat';
// Create mock functions at the module level
const mockStartSession = jest.fn();
@@ -27,20 +27,22 @@ function setupFetchMock(sessionId = 'test-session-id', taskId = 'test-task-id')
if (url.includes('/api/chat/start/')) {
return Promise.resolve({
ok: true,
- json: () => Promise.resolve({
- session_id: sessionId,
- chatbot: {},
- participant: {},
- }),
+ json: () =>
+ Promise.resolve({
+ session_id: sessionId,
+ chatbot: {},
+ participant: {},
+ }),
} as Response);
}
if (url.includes('/api/chat/send/')) {
return Promise.resolve({
ok: true,
- json: () => Promise.resolve({
- task_id: taskId,
- status: 'processing',
- }),
+ json: () =>
+ Promise.resolve({
+ task_id: taskId,
+ status: 'processing',
+ }),
} as Response);
}
return Promise.reject(new Error('Unexpected fetch call'));
@@ -456,10 +458,10 @@ describe('ocs-chat localStorage blocked (SecurityError)', () => {
beforeEach(() => {
jest.clearAllMocks();
- mockStartSession.mockResolvedValue({session_id: 'test-session-id'});
- mockSendMessage.mockResolvedValue({status: 'success', task_id: 'test-task-id'});
- mockPollTask.mockReturnValue({cancel: jest.fn()});
- mockStartMessagePolling.mockReturnValue({stop: jest.fn()});
+ mockStartSession.mockResolvedValue({ session_id: 'test-session-id' });
+ mockSendMessage.mockResolvedValue({ status: 'success', task_id: 'test-task-id' });
+ mockPollTask.mockReturnValue({ cancel: jest.fn() });
+ mockStartMessagePolling.mockReturnValue({ stop: jest.fn() });
global.fetch = setupFetchMock();
@@ -525,9 +527,11 @@ describe('ocs-chat localStorage blocked (SecurityError)', () => {
});
it('does not throw during componentWillLoad when localStorage is blocked', async () => {
- await expect(newSpecPage({
- components: [OcsChat],
- html: '
',
- })).resolves.toBeDefined();
+ await expect(
+ newSpecPage({
+ components: [OcsChat],
+ html: '
',
+ }),
+ ).resolves.toBeDefined();
});
});
diff --git a/components/chat_widget/src/services/chat-session-service.ts b/components/chat_widget/src/services/chat-session-service.ts
index c888d6fc5c..da6abfdfcb 100644
--- a/components/chat_widget/src/services/chat-session-service.ts
+++ b/components/chat_widget/src/services/chat-session-service.ts
@@ -207,10 +207,7 @@ export class ChatSessionService {
return response.json() as Promise
;
}
- startMessagePolling(
- sessionId: string,
- callbacks: MessagePollingCallbacks,
- ): MessagePollingHandle {
+ startMessagePolling(sessionId: string, callbacks: MessagePollingCallbacks): MessagePollingHandle {
const poll = async () => {
try {
const since = callbacks.getSince();
@@ -246,7 +243,7 @@ export class ChatSessionService {
private getJsonHeaders(): Record {
const headers = this.getCommonHeaders();
- headers['Content-Type'] = 'application/json'
+ headers['Content-Type'] = 'application/json';
const csrfToken = this.csrfTokenProvider(this.apiBaseUrl);
if (csrfToken) {
diff --git a/components/chat_widget/src/services/file-attachment-manager.ts b/components/chat_widget/src/services/file-attachment-manager.ts
index e1f21af80e..daeeafed56 100644
--- a/components/chat_widget/src/services/file-attachment-manager.ts
+++ b/components/chat_widget/src/services/file-attachment-manager.ts
@@ -48,8 +48,8 @@ export class FileAttachmentManager {
for (const file of fileArray) {
const extension = this.getFileExtension(file.name);
- const contentType = file.type.split("/")[0];
- if (contentType != "text" && !this.supportedExtensions.includes(extension)) {
+ const contentType = file.type.split('/')[0];
+ if (contentType != 'text' && !this.supportedExtensions.includes(extension)) {
newSelected.push({ file, error: `File type ${extension} not supported` });
continue;
}
@@ -86,18 +86,13 @@ export class FileAttachmentManager {
});
}
- async uploadPendingFiles(
- existingFiles: SelectedFile[],
- context: UploadContext
- ): Promise {
+ async uploadPendingFiles(existingFiles: SelectedFile[], context: UploadContext): Promise {
if (existingFiles.length === 0) {
return { selectedFiles: existingFiles, uploadedIds: [] };
}
const uploadCandidates = existingFiles.filter(file => !file.error && !file.uploaded);
- const uploadedIds: number[] = existingFiles
- .filter(file => file.uploaded)
- .map(file => file.uploaded!.id);
+ const uploadedIds: number[] = existingFiles.filter(file => file.uploaded).map(file => file.uploaded!.id);
if (uploadCandidates.length === 0) {
return { selectedFiles: existingFiles, uploadedIds };
@@ -120,9 +115,7 @@ export class FileAttachmentManager {
if (!response.ok) {
const errorData = await this.safeJson(response);
- const errorMessage =
- (errorData && typeof errorData === 'object' && 'error' in errorData && (errorData as { error?: string }).error) ||
- 'Failed to upload files';
+ const errorMessage = (errorData && typeof errorData === 'object' && 'error' in errorData && (errorData as { error?: string }).error) || 'Failed to upload files';
return {
selectedFiles: this.markPendingFilesWithError(existingFiles, errorMessage),
uploadedIds,
diff --git a/components/chat_widget/src/utils/cookies.ts b/components/chat_widget/src/utils/cookies.ts
index 89447ff9bb..94051e92d1 100644
--- a/components/chat_widget/src/utils/cookies.ts
+++ b/components/chat_widget/src/utils/cookies.ts
@@ -1,5 +1,4 @@
-import Cookies from "js-cookie";
-
+import Cookies from 'js-cookie';
/**
* Get CSRF token from cookies if the current domain matches the API base URL
@@ -9,7 +8,7 @@ export function getCSRFToken(apiBaseUrl: string): string | undefined {
return undefined;
}
- return Cookies.get('csrftoken')
+ return Cookies.get('csrftoken');
}
function currentDomainMatchesApiBaseUrl(apiBaseUrl: string): boolean {
diff --git a/components/chat_widget/src/utils/markdown.ts b/components/chat_widget/src/utils/markdown.ts
index 6ff4e4452f..1520bc7f52 100644
--- a/components/chat_widget/src/utils/markdown.ts
+++ b/components/chat_widget/src/utils/markdown.ts
@@ -22,7 +22,7 @@ export function postProcessMarkdownHTML(html: string): string {
// Add target="_blank" and rel="noopener noreferrer" to external links
const links = tempDiv.querySelectorAll('a[href]');
- links.forEach((link) => {
+ links.forEach(link => {
const href = link.getAttribute('href');
if (href && (href.startsWith('http://') || href.startsWith('https://'))) {
link.setAttribute('target', '_blank');
@@ -37,18 +37,42 @@ export function postProcessMarkdownHTML(html: string): string {
}
}
-
export const SANITIZE_CONFIG = {
ALLOWED_TAGS: [
- 'p', 'br', 'strong', 'b', 'em', 'i', 'u', 'code', 'pre',
- 'ul', 'ol', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
- 'blockquote', 'a', 'img', 'hr', 'table', 'thead', 'tbody',
- 'tr', 'td', 'th', 'del', 'ins', 'sub', 'sup'
- ],
- ALLOWED_ATTR: [
- 'href', 'target', 'rel', 'class', 'src', 'alt', 'title',
- 'width', 'height', 'align', 'colspan', 'rowspan'
+ 'p',
+ 'br',
+ 'strong',
+ 'b',
+ 'em',
+ 'i',
+ 'u',
+ 'code',
+ 'pre',
+ 'ul',
+ 'ol',
+ 'li',
+ 'h1',
+ 'h2',
+ 'h3',
+ 'h4',
+ 'h5',
+ 'h6',
+ 'blockquote',
+ 'a',
+ 'img',
+ 'hr',
+ 'table',
+ 'thead',
+ 'tbody',
+ 'tr',
+ 'td',
+ 'th',
+ 'del',
+ 'ins',
+ 'sub',
+ 'sup',
],
+ ALLOWED_ATTR: ['href', 'target', 'rel', 'class', 'src', 'alt', 'title', 'width', 'height', 'align', 'colspan', 'rowspan'],
ALLOWED_URI_REGEXP: /^(?:(?:https?):|[^a-z]|[a-z+.-]+(?:[^a-z+.\-:]|$))/i,
ADD_ATTR: ['target'],
FORBID_TAGS: ['script', 'style', 'form', 'input', 'button', 'iframe', 'object', 'embed', 'svg', 'math'],
diff --git a/components/chat_widget/src/utils/translations.spec.ts b/components/chat_widget/src/utils/translations.spec.ts
index 9cf9f80446..032aff171a 100644
--- a/components/chat_widget/src/utils/translations.spec.ts
+++ b/components/chat_widget/src/utils/translations.spec.ts
@@ -88,7 +88,7 @@ describe('mergeTranslations', () => {
});
describe('TranslationManager', () => {
- const waitForAsyncLoad = () => new Promise((resolve) => setTimeout(resolve, 0));
+ const waitForAsyncLoad = () => new Promise(resolve => setTimeout(resolve, 0));
it('initializes with resolved language', async () => {
const manager = new translations.TranslationManager('FR');
@@ -119,12 +119,12 @@ describe('TranslationManager', () => {
it('translations return keys if value is blank', async () => {
const manager = new translations.TranslationManager('de', {
- "window.close": "",
+ 'window.close': '',
});
await waitForAsyncLoad();
- expect(manager.get('window.close')).toBe("");
+ expect(manager.get('window.close')).toBe('');
});
it('returns arrays from getArray and wraps strings', async () => {
diff --git a/components/chat_widget/src/utils/translations.ts b/components/chat_widget/src/utils/translations.ts
index 4f9c990e50..9928841a5c 100644
--- a/components/chat_widget/src/utils/translations.ts
+++ b/components/chat_widget/src/utils/translations.ts
@@ -30,7 +30,6 @@ const translationFiles: Record = {
uk: uk as TranslationStrings,
};
-
export function getBrowserLanguage(): string {
if (typeof navigator !== 'undefined') {
const lang = navigator.language || (navigator as any).userLanguage;
@@ -55,10 +54,7 @@ export async function loadTranslations(language: string): Promise
-): TranslationStrings {
+export function mergeTranslations(baseTranslations: TranslationStrings, customTranslations: Partial): TranslationStrings {
return { ...baseTranslations, ...customTranslations };
}
@@ -80,9 +76,7 @@ export class TranslationManager {
baseTranslations = defaultTranslations;
}
- this.translations = customTranslations
- ? mergeTranslations(baseTranslations, customTranslations)
- : baseTranslations;
+ this.translations = customTranslations ? mergeTranslations(baseTranslations, customTranslations) : baseTranslations;
}
get(key: keyof TranslationStrings, override?: string | null): string | undefined {
diff --git a/components/chat_widget/src/utils/utils.ts b/components/chat_widget/src/utils/utils.ts
index f84753bb45..f1799df6b5 100644
--- a/components/chat_widget/src/utils/utils.ts
+++ b/components/chat_widget/src/utils/utils.ts
@@ -5,20 +5,20 @@
* @param defaultValue The default value if the CSS value is neither a percentage nor a pixel value.
*/
export const varToPixels = (value: string, maxValue: number, defaultValue: number) => {
- value = value.trim()
- if (value.includes("%")) {
- const percent = percentToFloat(value);
- if (!isNaN(percent)) {
- return maxValue * percent;
- }
- } else if (value.includes("px")) {
- const pixels = parseFloat(value);
- if (!isNaN(pixels)) {
- return pixels;
- }
+ value = value.trim();
+ if (value.includes('%')) {
+ const percent = percentToFloat(value);
+ if (!isNaN(percent)) {
+ return maxValue * percent;
}
- return defaultValue;
-}
+ } else if (value.includes('px')) {
+ const pixels = parseFloat(value);
+ if (!isNaN(pixels)) {
+ return pixels;
+ }
+ }
+ return defaultValue;
+};
const percentToFloat = (percentageString: string) => {
const numericValue = parseFloat(percentageString);