diff --git a/DSL/Resql/analytics/POST/overview-total-chats.sql b/DSL/Resql/analytics/POST/overview-total-chats.sql
index 2cae5934..ca484952 100644
--- a/DSL/Resql/analytics/POST/overview-total-chats.sql
+++ b/DSL/Resql/analytics/POST/overview-total-chats.sql
@@ -36,8 +36,8 @@ SELECT ts.ended,
FROM (
SELECT date_trunc(:group_period, period) AS ended
FROM generate_series(
- date_trunc(:group_period, (current_date AT TIME ZONE :timezone - concat('1 ', :group_period)::INTERVAL)),
- date_trunc(:group_period, (current_date AT TIME ZONE :timezone)),
+ date_trunc(:group_period, (NOW() AT TIME ZONE :timezone) - concat('1 ', :group_period)::INTERVAL),
+ date_trunc(:group_period, (NOW() AT TIME ZONE :timezone)),
concat('1 ', :group_period)::INTERVAL
) AS period
) ts
diff --git a/GUI/package.json b/GUI/package.json
index 541f87af..fbe11ba2 100644
--- a/GUI/package.json
+++ b/GUI/package.json
@@ -7,7 +7,7 @@
"@buerokratt-ria/header": "^0.1.47",
"@buerokratt-ria/menu": "^0.2.10",
"@buerokratt-ria/styles": "^0.0.1",
- "@buerokratt-ria/common-gui-components": "^0.0.42",
+ "@buerokratt-ria/common-gui-components": "^0.0.43",
"@fontsource/roboto": "^4.5.8",
"@formkit/auto-animate": "^1.0.0-beta.6",
"@hookform/resolvers": "^2.9.11",
diff --git a/GUI/src/components/ChatEvent/Markdownify.tsx b/GUI/src/components/ChatEvent/Markdownify.tsx
index 7eb9821b..5d38103a 100644
--- a/GUI/src/components/ChatEvent/Markdownify.tsx
+++ b/GUI/src/components/ChatEvent/Markdownify.tsx
@@ -7,6 +7,27 @@ interface MarkdownifyProps {
sanitizeLinks?: boolean;
}
+const isValidImageUrl = (s: string): boolean => {
+ try {
+ const u = new URL(s);
+ if (!/^(https?|data):$/i.test(u.protocol)) return false;
+ if (s.startsWith('data:image/')) {
+ return /^data:image\/(png|jpe?g|gif|webp|svg\+xml|bmp);(base64,|charset=utf-8;)/i.test(s);
+ }
+ const path = u.pathname.toLowerCase();
+ const search = u.search.toLowerCase();
+ if (/\.(png|jpe?g|gif|webp|svg|ico|bmp|tiff?|avif|heic|heif|apng)([?#]|$)/i.test(path)) {
+ return true;
+ }
+ if (/\/(images?|img|photos?|pictures?|media|uploads?|thumb|avatar)\//i.test(path)) {
+ return true;
+ }
+ return /[?&](format|type|image|img|photo)=(png|jpe?g|gif|webp|svg|ico)/i.test(search);
+ } catch {
+ return false;
+ }
+};
+
const LinkPreview: React.FC<{
href: string;
children: React.ReactNode;
@@ -31,14 +52,19 @@ const LinkPreview: React.FC<{
);
}
- return !hasError ? (
-
setHasError(true)}
- />
- ) : (
+ if (!isValidImageUrl(href)) {
+ return (
+
+ {children}
+
+ );
+ }
+
+ return hasError ? (
{children}
+ ) : (
+
setHasError(true)}
+ />
);
};
@@ -61,8 +94,14 @@ function formatMessage(message?: string): string {
.replaceAll(/\\?\$v\w*/g, '')
.replaceAll(/\\?\$g\w*/g, '');
- return filteredMessage
- .replaceAll(/([0-9A-Fa-f]+);/g, (_, hex: string) => String.fromCharCode(parseInt(hex, 16)))
+ const dataImagePattern = /((?:^|\s))(data:image\/[a-z0-9+]+;[^)\s]+)/gi;
+ const finalMessage = filteredMessage.replaceAll(
+ dataImagePattern,
+ (_, prefix, dataUrl) => `${prefix}[image](${dataUrl})`
+ );
+
+ return finalMessage
+ .replaceAll(/([0-9A-F]+);/gi, (_, hex: string) => String.fromCharCode(parseInt(hex, 16)))
.replaceAll('&', '&')
.replaceAll('>', '>')
.replaceAll('<', '<')
@@ -70,7 +109,7 @@ function formatMessage(message?: string): string {
.replaceAll(''', "'")
.replaceAll(''', "'")
.replaceAll(/(^|\n)(\d{4})\.\s/g, (match, prefix, year) => {
- const remainingText = filteredMessage.substring(filteredMessage.indexOf(match) + match.length);
+ const remainingText = finalMessage.substring(finalMessage.indexOf(match) + match.length);
const sentenceEnd = remainingText.indexOf('\n\n');
if (sentenceEnd !== -1) {
const currentSentence = remainingText.substring(0, sentenceEnd);
@@ -80,7 +119,7 @@ function formatMessage(message?: string): string {
}
return `${prefix}${year}\\. `;
})
- .replaceAll(/(?<=\n)\d+\.\s/g, hasSpecialFormat(filteredMessage) ? '\n\n$&' : '$&')
+ .replaceAll(/(?<=\n)\d+\.\s/g, hasSpecialFormat(finalMessage) ? '\n\n$&' : '$&')
.replaceAll(/^(\s+)/g, (match) => match.replaceAll(' ', ' '));
}
diff --git a/GUI/src/components/services/user.ts b/GUI/src/components/services/user.ts
index 7d4d67c8..a6961b7e 100644
--- a/GUI/src/components/services/user.ts
+++ b/GUI/src/components/services/user.ts
@@ -1,11 +1,11 @@
-import {DomainSelection} from "../../types/widgetModels";
-import {api} from "./api";
+import { DomainSelection } from '../../types/widgetModels';
+import { api } from './api';
export async function getWidgetData(userId: string) {
- const { data } = await api.get('accounts/widget-data', {
- params: {
- user_id: userId,
- },
- });
- return data;
+ const { data } = await api.get('accounts/widget-data', {
+ params: {
+ user_id: userId,
+ },
+ });
+ return data;
}