diff --git a/config/static-manifest/manifest.json b/config/static-manifest/manifest.json
index 0bb1b16..f2145d6 100644
--- a/config/static-manifest/manifest.json
+++ b/config/static-manifest/manifest.json
@@ -1,6 +1,6 @@
{
- "dist/chat-client.js": "dist/chat-client.f7134231.js",
- "dist/chat-client.css": "dist/chat-client.99e10656.css",
+ "dist/chat-client.js": "dist/chat-client.f8ee4d5a.js",
+ "dist/chat-client.css": "dist/chat-client.2fffefae.css",
"dist/main.js": "dist/main.6a617099.js",
"dist/main.css": "dist/main.7182aac3.css"
}
\ No newline at end of file
diff --git a/eveai_chat_client/static/assets/css/chat.css b/eveai_chat_client/static/assets/css/chat.css
index 11daad1..6bb3633 100644
--- a/eveai_chat_client/static/assets/css/chat.css
+++ b/eveai_chat_client/static/assets/css/chat.css
@@ -9,6 +9,13 @@
--message-bot-bg: #f8f9fa;
--border-radius: 8px;
--spacing: 16px;
+
+ /* Nieuwe, veilige viewport-variabelen voor mobiele lay-out
+ - --vvh blijft als fallback uit viewport.js
+ - --safe-vh wordt gezet door useChatViewport()
+ - --safe-bottom-inset wordt gebruikt voor ondermarge bij de chat input */
+ --safe-vh: var(--vvh, 1vh);
+ --safe-bottom-inset: 0px;
}
/* App container layout */
@@ -16,7 +23,7 @@
display: flex;
/* Use visual viewport variable when available */
min-height: 0;
- height: calc(var(--vvh, 1vh) * 100);
+ height: calc(var(--safe-vh, var(--vvh, 1vh)) * 100);
width: 100%;
}
@@ -97,7 +104,8 @@
}
html, body {
- height: calc(var(--vvh, 1vh) * 100); min-height: 0;
+ height: calc(var(--safe-vh, var(--vvh, 1vh)) * 100);
+ min-height: 0;
}
/* Base reset & overflow control */
diff --git a/eveai_chat_client/static/assets/js/composables/useChatViewport.js b/eveai_chat_client/static/assets/js/composables/useChatViewport.js
new file mode 100644
index 0000000..1de2f8d
--- /dev/null
+++ b/eveai_chat_client/static/assets/js/composables/useChatViewport.js
@@ -0,0 +1,176 @@
+// useChatViewport.js
+// Centrale viewport-/keyboard-laag voor de chat client.
+// - Gebruikt visualViewport + VueUse (via window.VueUse) om veilige hoogte
+// en keyboard-status te bepalen
+// - Stelt CSS-variabelen --safe-vh en --safe-bottom-inset in op
+// - Beheert body-klassen zoals chat-keyboard-open en ios-safari
+
+import { ref, computed, watchEffect, onMounted, onBeforeUnmount } from 'vue';
+
+// Haal VueUse-composables uit window.VueUse, die in de chat-bundel
+// worden geïnitialiseerd via nginx/frontend_src/js/vueuse-setup.js.
+// Als ze ontbreken (bv. in een testsituatie), vallen we terug op
+// eenvoudige lokale shims.
+
+const vueUse = (typeof window !== 'undefined' && window.VueUse) || {};
+let { useEventListener, useWindowSize } = vueUse;
+
+// Fallback-shim voor useEventListener
+if (!useEventListener) {
+ useEventListener = (target, event, handler, options) => {
+ if (!target || !target.addEventListener) return () => {};
+ const opts = options || { passive: true };
+ target.addEventListener(event, handler, opts);
+ return () => target.removeEventListener(event, handler, opts);
+ };
+}
+
+// Fallback-shim voor useWindowSize
+if (!useWindowSize) {
+ useWindowSize = () => {
+ const height = ref(typeof window !== 'undefined' ? window.innerHeight : 0);
+ if (typeof window !== 'undefined') {
+ window.addEventListener('resize', () => {
+ height.value = window.innerHeight;
+ }, { passive: true });
+ }
+ return { height };
+ };
+}
+
+function detectIosSafari() {
+ if (typeof navigator === 'undefined') return false;
+ const ua = navigator.userAgent || navigator.vendor || (window && window.opera) || '';
+ const isIOS = /iP(ad|hone|od)/.test(ua);
+ const isSafari = /Safari/.test(ua) && !/Chrome|CriOS|FxiOS/.test(ua);
+ return isIOS && isSafari;
+}
+
+// Drempel voor het detecteren van keyboard-open op basis van hoogteverschil
+const KEYBOARD_DELTA_THRESHOLD = 120; // px, afgestemd op bestaande viewport.js heuristiek
+
+export function useChatViewport() {
+ const isClient = typeof window !== 'undefined' && typeof document !== 'undefined';
+
+ const safeHeight = ref(isClient ? window.innerHeight : 0);
+ const baselineHeight = ref(null); // baseline voor keyboard-detectie
+ const keyboardOpen = ref(false);
+ const safeBottomInset = ref(0);
+
+ const { height: windowHeight } = useWindowSize();
+ const isIosSafari = ref(isClient ? detectIosSafari() : false);
+
+ const isMobile = computed(() => {
+ if (!isClient) return false;
+ // Houd de definitie in lijn met bestaande CSS (max-width: 768px)
+ return window.innerWidth <= 768;
+ });
+
+ function applyToCss() {
+ if (!isClient) return;
+ const root = document.documentElement;
+ const vhUnit = (safeHeight.value || windowHeight.value || 0) / 100;
+
+ if (vhUnit > 0) {
+ root.style.setProperty('--safe-vh', `${vhUnit}px`);
+ }
+
+ root.style.setProperty('--safe-bottom-inset', `${safeBottomInset.value || 0}px`);
+
+ // Body-klassen
+ document.body.classList.toggle('chat-keyboard-open', !!keyboardOpen.value);
+
+ if (isIosSafari.value) {
+ root.classList.add('ios-safari');
+ } else {
+ root.classList.remove('ios-safari');
+ }
+ }
+
+ function updateFromVisualViewport() {
+ if (!isClient) return;
+ const vv = window.visualViewport;
+
+ const currentHeight = vv ? vv.height : window.innerHeight;
+ safeHeight.value = currentHeight;
+
+ // Bepaal een eenvoudige bottom-inset: verschil tussen layout- en visual viewport
+ const layoutHeight = window.innerHeight || currentHeight;
+ const inset = Math.max(0, layoutHeight - currentHeight);
+ safeBottomInset.value = inset;
+
+ // Keyboard-detectie: vergelijk met baseline
+ if (baselineHeight.value == null) {
+ baselineHeight.value = currentHeight;
+ }
+ const delta = (baselineHeight.value || currentHeight) - currentHeight;
+ keyboardOpen.value = delta > KEYBOARD_DELTA_THRESHOLD;
+ }
+
+ onMounted(() => {
+ if (!isClient) return;
+
+ // Initiale meting
+ updateFromVisualViewport();
+ applyToCss();
+
+ const vv = window.visualViewport;
+
+ // Luister naar relevante events via VueUse
+ const stopResize = vv
+ ? useEventListener(vv, 'resize', () => {
+ updateFromVisualViewport();
+ applyToCss();
+ })
+ : useEventListener(window, 'resize', () => {
+ updateFromVisualViewport();
+ applyToCss();
+ });
+
+ const stopScroll = vv
+ ? useEventListener(vv, 'scroll', () => {
+ updateFromVisualViewport();
+ applyToCss();
+ })
+ : () => {};
+
+ const stopOrientation = useEventListener(window, 'orientationchange', () => {
+ // Bij oriëntatie-wijziging baseline resetten
+ baselineHeight.value = null;
+ updateFromVisualViewport();
+ applyToCss();
+ });
+
+ onBeforeUnmount(() => {
+ stopResize && stopResize();
+ stopScroll && stopScroll();
+ stopOrientation && stopOrientation();
+ });
+ });
+
+ // Reageer ook op generieke windowHeight veranderingen (fallback)
+ watchEffect(() => {
+ if (!isClient) return;
+ if (!window.visualViewport) {
+ safeHeight.value = windowHeight.value;
+ safeBottomInset.value = 0;
+ keyboardOpen.value = false;
+ applyToCss();
+ }
+ });
+
+ // Initiale toepassing voor SSR / eerste render
+ if (isClient) {
+ applyToCss();
+ }
+
+ return {
+ safeHeight,
+ safeBottomInset,
+ keyboardOpen,
+ isMobile,
+ isIosSafari,
+ };
+}
+
+export default useChatViewport;
diff --git a/eveai_chat_client/static/assets/vue-components/ChatRoot.vue b/eveai_chat_client/static/assets/vue-components/ChatRoot.vue
new file mode 100644
index 0000000..ab3e903
--- /dev/null
+++ b/eveai_chat_client/static/assets/vue-components/ChatRoot.vue
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
diff --git a/eveai_chat_client/static/assets/vue-components/SafeViewport.vue b/eveai_chat_client/static/assets/vue-components/SafeViewport.vue
new file mode 100644
index 0000000..79d63c2
--- /dev/null
+++ b/eveai_chat_client/static/assets/vue-components/SafeViewport.vue
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
diff --git a/nginx/frontend_src/js/chat-client.js b/nginx/frontend_src/js/chat-client.js
index 6917659..901911f 100644
--- a/nginx/frontend_src/js/chat-client.js
+++ b/nginx/frontend_src/js/chat-client.js
@@ -10,7 +10,6 @@ import '../../../eveai_chat_client/static/assets/css/form-message.css';
// Dependencies
import { createApp, version } from 'vue';
import { marked } from 'marked';
-import { FormField } from '../../../../../../../../../Users/josako/Library/Application Support/JetBrains/PyCharm2025.1/scratches/old js files/FormField.js';
// Import LanguageProvider for sidebar translation support
import { createLanguageProvider, LANGUAGE_PROVIDER_KEY } from '../../../eveai_chat_client/static/assets/js/services/LanguageProvider.js';
@@ -29,9 +28,13 @@ console.log('Components loaded:', Object.keys(Components));
// Import specifieke componenten
import LanguageSelector from '../../../eveai_chat_client/static/assets/vue-components/LanguageSelector.vue';
import ChatApp from '../../../eveai_chat_client/static/assets/vue-components/ChatApp.vue';
+import ChatRoot from '../../../eveai_chat_client/static/assets/vue-components/ChatRoot.vue';
import SideBar from '../../../eveai_chat_client/static/assets/vue-components/SideBar.vue';
import MobileHeader from '../../../eveai_chat_client/static/assets/vue-components/MobileHeader.vue';
+// VueUse-setup voor de chatclient (maakt composables beschikbaar via window.VueUse)
+import './vueuse-setup.js';
+
// Globale Vue error tracking
window.addEventListener('error', function(event) {
console.error('🚨 [Global Error]', event.error);
@@ -230,8 +233,8 @@ function initializeChatApp() {
allowedLanguages: window.chatConfig.allowedLanguages || ['nl', 'en', 'fr', 'de']
};
- // Mount de component met alle nodige componenten
- const app = createApp(ChatApp, props);
+ // Mount de component via ChatRoot zodat SafeViewport de layout kan beheren
+ const app = createApp(ChatRoot, props);
// SSE verbinding configuratie - injecteren in ChatApp component
app.provide('sseConfig', {
diff --git a/nginx/frontend_src/js/vueuse-setup.js b/nginx/frontend_src/js/vueuse-setup.js
new file mode 100644
index 0000000..f4cecc0
--- /dev/null
+++ b/nginx/frontend_src/js/vueuse-setup.js
@@ -0,0 +1,15 @@
+// vueuse-setup.js
+// Setup-module voor VueUse in de chat client.
+// - Importeert benodigde composables uit '@vueuse/core'
+// - Maakt ze beschikbaar via window.VueUse zodat static assets
+// onder eveai_chat_client/static ze kunnen gebruiken zonder
+// directe npm-imports.
+
+import { useEventListener, useWindowSize } from '@vueuse/core';
+
+if (typeof window !== 'undefined') {
+ window.VueUse = Object.assign({}, window.VueUse, {
+ useEventListener,
+ useWindowSize,
+ });
+}
diff --git a/nginx/package-lock.json b/nginx/package-lock.json
index ce48449..037034f 100644
--- a/nginx/package-lock.json
+++ b/nginx/package-lock.json
@@ -6,12 +6,13 @@
"": {
"dependencies": {
"@popperjs/core": "^2.11.8",
+ "@vueuse/core": "^14.0.0",
"animejs": "^4.0.2",
"bootstrap": "^5.3.6",
"datatables.net": "^2.3.1",
"highlight.js": "^11.11.1",
"jquery": "^3.7.1",
- "marked": "^16.0.0",
+ "marked": "16.3.0",
"nouislider": "^15.8.1",
"parallax": "^0.0.0",
"prismjs": "^1.30.0",
@@ -22,7 +23,6 @@
"vue": "^3.5.17"
},
"devDependencies": {
- "@parcel/reporter-bundle-analyzer": "^2.15.2",
"@parcel/transformer-sass": "^2.15.2",
"@parcel/transformer-vue": "^2.15.2",
"parcel": "^2.15.2"
@@ -1360,26 +1360,6 @@
"url": "https://opencollective.com/parcel"
}
},
- "node_modules/@parcel/reporter-bundle-analyzer": {
- "version": "2.16.0",
- "resolved": "https://registry.npmjs.org/@parcel/reporter-bundle-analyzer/-/reporter-bundle-analyzer-2.16.0.tgz",
- "integrity": "sha512-81EazkM7YjeFvzRRlKKV8kmLfLXfWlfLqlpC8jFRXthC43aZtdqMnzRgi9Q+6FIB7Ft3vnMXZPqRs4cn7+XgUg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@parcel/plugin": "2.16.0",
- "@parcel/utils": "2.16.0",
- "nullthrows": "^1.1.1"
- },
- "engines": {
- "node": ">= 16.0.0",
- "parcel": "^2.16.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
"node_modules/@parcel/reporter-cli": {
"version": "2.16.0",
"resolved": "https://registry.npmjs.org/@parcel/reporter-cli/-/reporter-cli-2.16.0.tgz",
@@ -2722,6 +2702,12 @@
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
"license": "MIT"
},
+ "node_modules/@types/web-bluetooth": {
+ "version": "0.0.21",
+ "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz",
+ "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==",
+ "license": "MIT"
+ },
"node_modules/@vue/compiler-core": {
"version": "3.5.21",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.21.tgz",
@@ -2822,6 +2808,44 @@
"integrity": "sha512-+2k1EQpnYuVuu3N7atWyG3/xoFWIVJZq4Mz8XNOdScFI0etES75fbny/oU4lKWk/577P1zmg0ioYvpGEDZ3DLw==",
"license": "MIT"
},
+ "node_modules/@vueuse/core": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.0.0.tgz",
+ "integrity": "sha512-d6tKRWkZE8IQElX2aHBxXOMD478fHIYV+Dzm2y9Ag122ICBpNKtGICiXKOhWU3L1kKdttDD9dCMS4bGP3jhCTQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/web-bluetooth": "^0.0.21",
+ "@vueuse/metadata": "14.0.0",
+ "@vueuse/shared": "14.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "vue": "^3.5.0"
+ }
+ },
+ "node_modules/@vueuse/metadata": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-14.0.0.tgz",
+ "integrity": "sha512-6yoGqbJcMldVCevkFiHDBTB1V5Hq+G/haPlGIuaFZHpXC0HADB0EN1ryQAAceiW+ryS3niUwvdFbGiqHqBrfVA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/@vueuse/shared": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-14.0.0.tgz",
+ "integrity": "sha512-mTCA0uczBgurRlwVaQHfG0Ja7UdGe4g9mwffiJmvLiTtp1G4AQyIjej6si/k8c8pUwTfVpNufck+23gXptPAkw==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "vue": "^3.5.0"
+ }
+ },
"node_modules/acorn": {
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
diff --git a/nginx/package.json b/nginx/package.json
index dc12848..20f3e60 100644
--- a/nginx/package.json
+++ b/nginx/package.json
@@ -14,7 +14,8 @@
"tabulator-tables": "^6.3.1",
"typed.js": "^2.1.0",
"vanilla-jsoneditor": "^3.5.0",
- "vue": "^3.5.17"
+ "vue": "^3.5.17",
+ "@vueuse/core": "^14.0.0"
},
"devDependencies": {
"@parcel/transformer-sass": "^2.15.2",