UNPKG

besper-frontend-toolkit-dev-8786

Version:

Professional B-esper Frontend Toolkit - Modular bot integration for WordPress plugins and web applications

1 lines 1.72 MB
{"version":3,"file":"besperbot.mjs","sources":["../src/services/centralizedApi.js","../src/utils/i18n.js","../node_modules/dompurify/dist/purify.es.mjs","../src/utils/sanitizer.js","../src/components/chat-widget.js","../src/utils/managementTranslations.js","../src/utils/managementI18n.js","../src/components/management/tabs/ConfigurationTab.js","../src/utils/DOMReady.js","../src/components/management/tabs/StylingTab.js","../src/components/management/tabs/BehaviourTab.js","../src/components/management/tabs/KnowledgeTab.js","../src/components/management/tabs/ImplementationTab.js","../src/components/management/tabs/ConversationsTab.js","../src/components/management/widgets/StorageIndicator.js","../src/components/management/widgets/CredentialsDisplay.js","../src/components/chat/ChatHeader.js","../src/components/chat/Message.js","../src/components/chat/ChatMessages.js","../src/components/chat/ChatInput.js","../src/components/chat/TypingIndicator.js","../src/components/chat/ChatWidget.js","../src/components/management/BotManagement.js","../src/components/b-esper-bot-management.js","../src/components/demo-interface.js","../src/components/custom-styling-manager.js","../src/index.js"],"sourcesContent":["/**\n * Centralized API Service for B-esper Frontend Toolkit\n * Consolidates all API endpoint definitions and provides consistent API calling interface\n * Uses dynamic endpoints from build-time environment variables for APIM deployment flexibility\n */\n\n// Dynamic base URL from build-time environment (NO FALLBACKS ALLOWED)\nconst APIM_BASE_ENDPOINT = process.env.API_ENDPOINT;\n\n// Validate API_ENDPOINT is set during build - fail fast if not configured\nif (!APIM_BASE_ENDPOINT) {\n console.error(\n '❌ CRITICAL: API_ENDPOINT environment variable is required but not set!'\n );\n console.error('❌ This indicates a build configuration error.');\n console.error(\n '❌ Production deployments must set API_ENDPOINT to match infrastructure: {env}{branch}apim.azure-api.net'\n );\n console.error('❌ Build will fail - no hardcoded fallback endpoints allowed');\n throw new Error(\n 'API_ENDPOINT environment variable is required for deployment - no fallbacks allowed'\n );\n}\n\n/**\n * Get the base APIM endpoint (dynamic only, no fallbacks)\n */\nfunction getBaseAPIEndpoint() {\n // Return only the dynamic endpoint - no fallback logic allowed\n return APIM_BASE_ENDPOINT;\n}\n\n// API service paths\nconst API_PATHS = {\n // Bot operations (chat, messaging)\n botOperations: '/api/bot-operations',\n\n // Bot management (configuration, settings)\n botManagement: '/api/bot-management',\n\n // Root API for general operations\n root: '/api',\n\n // Management operations (legacy mgmt path if needed)\n management: '/mgmt',\n};\n\n/**\n * Get the complete API endpoint URL for a specific service\n * Uses ONLY the dynamic base endpoint from build-time environment variables\n * NO FALLBACKS ALLOWED - ensures consistent infrastructure naming\n * @param {string} service - Service name from API_PATHS\n * @returns {string} Complete API endpoint URL\n */\nexport function getServiceEndpoint(service = 'botOperations') {\n // Use ONLY the dynamic base endpoint - no fallbacks allowed\n const baseUrl = getBaseAPIEndpoint();\n const path = API_PATHS[service] || API_PATHS.botOperations;\n\n // Ensure no double slashes\n const endpoint = `${baseUrl}${path}`\n .replace(/\\/+/g, '/')\n .replace('http:/', 'http://')\n .replace('https:/', 'https://');\n\n // Only log in development/debug mode to reduce console noise\n if (\n typeof window !== 'undefined' &&\n window.location?.hostname?.includes('localhost')\n ) {\n console.log(`🌐 API Endpoint for ${service}:`, endpoint);\n }\n return endpoint;\n}\n\n/**\n * Get bot operations endpoint (for chat functionality)\n * Uses dynamic endpoint from build-time configuration\n * @returns {string} Bot operations API endpoint\n */\nexport function getBotOperationsEndpoint() {\n return getServiceEndpoint('botOperations');\n}\n\n/**\n * Get bot management endpoint (for configuration management)\n * Uses dynamic endpoint from build-time configuration\n * @returns {string} Bot management API endpoint\n */\nexport function getBotManagementEndpoint() {\n return getServiceEndpoint('botManagement');\n}\n\n/**\n * Get root API endpoint (for general operations)\n * Uses dynamic endpoint from build-time configuration\n * @returns {string} Root API endpoint\n */\nexport function getRootApiEndpoint() {\n return getServiceEndpoint('root');\n}\n\n/**\n * Generic API call helper with enhanced error handling and logging\n * @param {string} endpoint - Full API endpoint URL\n * @param {string} method - HTTP method (GET, POST, PUT, DELETE)\n * @param {Object} body - Request body for POST/PUT requests\n * @param {Object} headers - Additional headers\n * @returns {Promise<Object>} API response data\n */\nexport async function apiCall(\n endpoint,\n method = 'GET',\n body = null,\n headers = {}\n) {\n const config = {\n method,\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n 'X-Requested-With': 'XMLHttpRequest',\n 'Cache-Control': 'no-cache',\n Pragma: 'no-cache',\n ...headers,\n },\n };\n\n if (body && method !== 'GET') {\n config.body = JSON.stringify(body);\n }\n\n // Debug logging (reduced in production)\n const isDebugMode =\n typeof window !== 'undefined' &&\n (window.location?.hostname?.includes('localhost') ||\n window.location?.search?.includes('debug=true'));\n\n if (isDebugMode) {\n console.log(`🌐 Making ${method} request to:`, endpoint);\n if (body) {\n console.log(`📦 Request payload:`, body);\n }\n }\n\n try {\n const response = await fetch(endpoint, config);\n\n if (isDebugMode) {\n console.log(\n `📡 Response status: ${response.status} ${response.statusText}`\n );\n }\n\n if (!response.ok) {\n let errorMessage = `HTTP ${response.status}: ${response.statusText}`;\n\n // Try to get error details from response body\n try {\n const errorData = await response.text();\n if (errorData) {\n if (isDebugMode) {\n console.log(`❌ Error response body:`, errorData);\n }\n errorMessage += ` - ${errorData}`;\n }\n } catch (parseError) {\n if (isDebugMode) {\n console.log(`❌ Could not parse error response:`, parseError);\n }\n }\n\n throw new Error(errorMessage);\n }\n\n const data = await response.json();\n if (isDebugMode) {\n console.log(`✅ Response data:`, data);\n }\n return data;\n } catch (error) {\n // Always log errors for debugging, but make them less verbose in production\n if (isDebugMode) {\n console.error(`❌ API call failed:`, error);\n } else {\n console.error(`❌ API call failed: ${error.message}`);\n }\n throw error;\n }\n}\n\n/**\n * Make a bot management API call\n * @param {string} path - API path (e.g., 'get_config_json', 'config')\n * @param {string} method - HTTP method\n * @param {Object} body - Request body\n * @param {Object} headers - Additional headers\n * @returns {Promise<Object>} API response\n */\nexport async function managementApiCall(\n path,\n method = 'GET',\n body = null,\n headers = {}\n) {\n const baseEndpoint = getBotManagementEndpoint();\n const fullEndpoint = `${baseEndpoint}/${path}`\n .replace(/\\/+/g, '/')\n .replace('http:/', 'http://')\n .replace('https:/', 'https://');\n\n return apiCall(fullEndpoint, method, body, headers);\n}\n\n/**\n * Make a bot operations API call\n * @param {string} path - API path (e.g., 'create_session', 'generate_response')\n * @param {string} method - HTTP method\n * @param {Object} body - Request body\n * @param {Object} headers - Additional headers\n * @returns {Promise<Object>} API response\n */\nexport async function operationsApiCall(\n path,\n method = 'GET',\n body = null,\n headers = {}\n) {\n const baseEndpoint = getBotOperationsEndpoint();\n const fullEndpoint = `${baseEndpoint}/${path}`\n .replace(/\\/+/g, '/')\n .replace('http:/', 'http://')\n .replace('https:/', 'https://');\n\n return apiCall(fullEndpoint, method, body, headers);\n}\n\n/**\n * Export legacy function names for backward compatibility\n * These will be deprecated in favor of the more specific functions above\n */\n\n// Legacy compatibility exports\nexport { getBotOperationsEndpoint as getApiEndpoint };\nexport { getBotManagementEndpoint as getManagementEndpoint };\n\n// Export all endpoints for direct access if needed\nexport const API_ENDPOINTS = {\n getBotOperations: getBotOperationsEndpoint,\n getBotManagement: getBotManagementEndpoint,\n getRoot: getRootApiEndpoint,\n};\n\n// Export constants for external use\nexport { API_PATHS, getBaseAPIEndpoint };\n","// Internationalization for chat widget - European languages and major languages\nexport const translations = {\n // Input placeholder text\n typeYourMessage: {\n en: 'Type your message here...',\n de: 'Geben Sie hier Ihre Nachricht ein...',\n fr: 'Tapez votre message ici...',\n es: 'Escribe tu mensaje aquí...',\n it: 'Digita il tuo messaggio qui...',\n pt: 'Digite sua mensagem aqui...',\n nl: 'Typ hier uw bericht...',\n pl: 'Wpisz tutaj swoją wiadomość...',\n sv: 'Skriv ditt meddelande här...',\n da: 'Skriv din besked her...',\n fi: 'Kirjoita viestisi tähän...',\n no: 'Skriv meldingen din her...',\n cs: 'Zde napište svou zprávu...',\n hu: 'Írja ide üzenetét...',\n ro: 'Introduceți mesajul aici...',\n sk: 'Sem napíšte svoju správu...',\n hr: 'Ovdje unesite poruku...',\n bg: 'Въведете съобщението си тук...',\n sl: 'Tukaj vnesite svoje sporočilo...',\n et: 'Sisestage oma sõnum siia...',\n lv: 'Ievadiet savu ziņu šeit...',\n lt: 'Čia įveskite savo žinutę...',\n el: 'Πληκτρολογήστε το μήνυμά σας εδώ...',\n ru: 'Введите ваше сообщение здесь...',\n uk: 'Введіть ваше повідомлення тут...',\n // Major Asian and world languages\n ja: 'ここにメッセージを入力してください...',\n zh: '在此输入您的消息...',\n ko: '여기에 메시지를 입력하세요...',\n ar: 'اكتب رسالتك هنا...',\n hi: 'अपना संदेश यहाँ टाइप करें...',\n id: 'Ketik pesan Anda di sini...',\n tr: 'Mesajınızı buraya yazın...',\n th: 'พิมพ์ข้อความของคุณที่นี่...',\n vi: 'Nhập tin nhắn của bạn tại đây...',\n he: 'הקלד את ההודעה שלך כאן...',\n fa: 'پیام خود را اینجا تایپ کنید...',\n ms: 'Taip mesej anda di sini...',\n tl: 'I-type ang inyong mensahe dito...',\n },\n\n // Tooltip texts\n tooltips: {\n downloadConversation: {\n en: 'Download Conversation',\n de: 'Unterhaltung herunterladen',\n fr: 'Télécharger la conversation',\n es: 'Descargar conversación',\n it: 'Scarica conversazione',\n pt: 'Baixar conversa',\n nl: 'Gesprek downloaden',\n pl: 'Pobierz rozmowę',\n sv: 'Ladda ner konversation',\n da: 'Download samtale',\n fi: 'Lataa keskustelu',\n no: 'Last ned samtale',\n cs: 'Stáhnout konverzaci',\n hu: 'Beszélgetés letöltése',\n ro: 'Descarcă conversația',\n sk: 'Stiahnuť konverzáciu',\n hr: 'Preuzmi razgovor',\n bg: 'Изтегли разговор',\n sl: 'Prenesi pogovor',\n et: 'Laadi alla vestlus',\n lv: 'Lejupielādēt sarunu',\n lt: 'Atsisiųsti pokalbį',\n el: 'Λήψη συνομιλίας',\n ru: 'Скачать беседу',\n uk: 'Завантажити розмову',\n // Major Asian and world languages\n ja: '会話をダウンロード',\n zh: '下载对话',\n ko: '대화 다운로드',\n ar: 'تحميل المحادثة',\n hi: 'बातचीत डाउनलोड करें',\n id: 'Unduh Percakapan',\n tr: 'Konuşmayı İndir',\n th: 'ดาวน์โหลดการสนทนา',\n vi: 'Tải cuộc trò chuyện',\n he: 'הורד שיחה',\n fa: 'دانلود مکالمه',\n ms: 'Muat turun Perbualan',\n tl: 'I-download ang Pag-uusap',\n },\n restartConversation: {\n en: 'Restart Conversation',\n de: 'Unterhaltung neu starten',\n fr: 'Redémarrer la conversation',\n es: 'Reiniciar conversación',\n it: 'Riavvia conversazione',\n pt: 'Reiniciar conversa',\n nl: 'Gesprek herstarten',\n pl: 'Uruchom ponownie rozmowę',\n sv: 'Starta om konversation',\n da: 'Genstart samtale',\n fi: 'Aloita keskustelu uudelleen',\n no: 'Start samtale på nytt',\n cs: 'Restartovat konverzaci',\n hu: 'Beszélgetés újraindítása',\n ro: 'Repornește conversația',\n sk: 'Reštartovať konverzáciu',\n hr: 'Ponovno pokreni razgovor',\n bg: 'Рестартирай разговор',\n sl: 'Ponovno zaženi pogovor',\n et: 'Taaskäivita vestlus',\n lv: 'Atsākt sarunu',\n lt: 'Paleisti pokalbį iš naujo',\n el: 'Επανεκκίνηση συνομιλίας',\n ru: 'Перезапустить беседу',\n uk: 'Перезапустити розмову',\n // Major Asian and world languages\n ja: '会話を再開',\n zh: '重新开始对话',\n ko: '대화 재시작',\n ar: 'إعادة تشغيل المحادثة',\n hi: 'बातचीत पुनः आरंभ करें',\n id: 'Mulai Ulang Percakapan',\n tr: 'Konuşmayı Yeniden Başlat',\n th: 'เริ่มการสนทนาใหม่',\n vi: 'Khởi động lại cuộc trò chuyện',\n he: 'הפעל מחדש שיחה',\n fa: 'راه اندازی مجدد مکالمه',\n ms: 'Mulakan semula Perbualan',\n tl: 'Simulan muli ang Pag-uusap',\n },\n deleteConversation: {\n en: 'Delete Conversation',\n de: 'Unterhaltung löschen',\n fr: 'Supprimer la conversation',\n es: 'Eliminar conversación',\n it: 'Elimina conversazione',\n pt: 'Excluir conversa',\n nl: 'Gesprek verwijderen',\n pl: 'Usuń rozmowę',\n sv: 'Radera konversation',\n da: 'Slet samtale',\n fi: 'Poista keskustelu',\n no: 'Slett samtale',\n cs: 'Smazat konverzaci',\n hu: 'Beszélgetés törlése',\n ro: 'Șterge conversația',\n sk: 'Vymazať konverzáciu',\n hr: 'Obriši razgovor',\n bg: 'Изтрий разговор',\n sl: 'Izbriši pogovor',\n et: 'Kustuta vestlus',\n lv: 'Dzēst sarunu',\n lt: 'Ištrinti pokalbį',\n el: 'Διαγραφή συνομιλίας',\n ru: 'Удалить беседу',\n uk: 'Видалити розмову',\n // Major Asian and world languages\n ja: '会話を削除',\n zh: '删除对话',\n ko: '대화 삭제',\n ar: 'حذف المحادثة',\n hi: 'बातचीत हटाएं',\n id: 'Hapus Percakapan',\n tr: 'Konuşmayı Sil',\n th: 'ลบการสนทนา',\n vi: 'Xóa cuộc trò chuyện',\n he: 'מחק שיחה',\n fa: 'حذف مکالمه',\n ms: 'Padam Perbualan',\n tl: 'Tanggalin ang Pag-uusap',\n },\n expandView: {\n en: 'Optimize view for better readability',\n de: 'Ansicht für bessere Lesbarkeit optimieren',\n fr: 'Optimiser la vue pour une meilleure lisibilité',\n es: 'Optimizar vista para mejor legibilidad',\n it: 'Ottimizza vista per migliore leggibilità',\n pt: 'Otimizar visualização para melhor legibilidade',\n nl: 'Optimaliseer weergave voor betere leesbaarheid',\n pl: 'Zoptymalizuj widok dla lepszej czytelności',\n sv: 'Optimera vy för bättre läsbarhet',\n da: 'Optimer visning for bedre læsbarhed',\n fi: 'Optimoi näkymä parempaa luettavuutta varten',\n no: 'Optimaliser visning for bedre lesbarhet',\n cs: 'Optimalizovat zobrazení pro lepší čitelnost',\n hu: 'Nézet optimalizálása a jobb olvashatóságért',\n ro: 'Optimizează vizualizarea pentru o mai bună lizibilitate',\n sk: 'Optimalizovať zobrazenie pre lepšiu čitateľnosť',\n hr: 'Optimiziraj prikaz za bolju čitljivost',\n bg: 'Оптимизирай изгледа за по-добра четимост',\n sl: 'Optimiziraj pogled za boljšo berljivost',\n et: 'Optimeeri vaade parema loetavuse jaoks',\n lv: 'Optimizēt skatu labākai lasāmībai',\n lt: 'Optimizuoti vaizdą geresniam skaitomumui',\n el: 'Βελτιστοποίηση προβολής για καλύτερη αναγνωσιμότητα',\n ru: 'Оптимизировать вид для лучшей читаемости',\n uk: 'Оптимізувати вигляд для кращої читабельності',\n // Major Asian and world languages\n ja: '読みやすさのためにビューを最適化',\n zh: '优化视图以提高可读性',\n ko: '가독성을 위한 보기 최적화',\n ar: 'تحسين العرض لقابلية قراءة أفضل',\n hi: 'बेहतर पठनीयता के लिए दृश्य को अनुकूलित करें',\n id: 'Optimalkan tampilan untuk keterbacaan yang lebih baik',\n tr: 'Daha iyi okunabilirlik için görünümü optimize edin',\n th: 'ปรับแต่งมุมมองเพื่อการอ่านที่ดีขึ้น',\n vi: 'Tối ưu hóa chế độ xem để dễ đọc hơn',\n he: 'אופטימיזציה של התצוגה לקריאה טובה יותר',\n fa: 'بهینه سازی نمایش برای خوانایی بهتر',\n ms: 'Optimalkan paparan untuk kebolehbacaan yang lebih baik',\n tl: 'I-optimize ang view para sa mas magandang pagkakabasa',\n },\n minimizeChat: {\n en: 'Minimize',\n de: 'Minimieren',\n fr: 'Réduire',\n es: 'Minimizar',\n it: 'Riduci',\n pt: 'Minimizar',\n nl: 'Minimaliseren',\n pl: 'Minimalizuj',\n sv: 'Minimera',\n da: 'Minimer',\n fi: 'Pienennä',\n no: 'Minimer',\n cs: 'Minimalizovat',\n hu: 'Minimalizálás',\n ro: 'Minimizează',\n sk: 'Minimalizovať',\n hr: 'Minimiziraj',\n bg: 'Минимизирай',\n sl: 'Minimiziraj',\n et: 'Minimeeri',\n lv: 'Minimizēt',\n lt: 'Sumažinti',\n el: 'Ελαχιστοποίηση',\n ru: 'Свернуть',\n uk: 'Згорнути',\n // Major Asian and world languages\n ja: '最小化',\n zh: '最小化',\n ko: '최소화',\n ar: 'تصغير',\n hi: 'न्यूनतम करें',\n id: 'Kecilkan',\n tr: 'Küçült',\n th: 'ย่อขนาด',\n vi: 'Thu nhỏ',\n he: 'מזער',\n fa: 'کمینه کردن',\n ms: 'Kecilkan',\n tl: 'Paliitin',\n },\n dataPolicy: {\n en: 'Data Policy',\n de: 'Datenschutzrichtlinie',\n fr: 'Politique de données',\n es: 'Política de datos',\n it: 'Politica sui dati',\n pt: 'Política de dados',\n nl: 'Gegevensbeleid',\n pl: 'Polityka danych',\n sv: 'Datapolicy',\n da: 'Datapolitik',\n fi: 'Tietopolitiikka',\n no: 'Datapolicy',\n cs: 'Zásady dat',\n hu: 'Adatkezelési irányelvek',\n ro: 'Politica datelor',\n sk: 'Zásady údajov',\n hr: 'Politika podataka',\n bg: 'Политика за данни',\n sl: 'Politika podatkov',\n et: 'Andmepoliitika',\n lv: 'Datu politika',\n lt: 'Duomenų politika',\n el: 'Πολιτική δεδομένων',\n ru: 'Политика данных',\n uk: 'Політика даних',\n // Major Asian and world languages\n ja: 'データポリシー',\n zh: '数据政策',\n ko: '데이터 정책',\n ar: 'سياسة البيانات',\n hi: 'डेटा नीति',\n id: 'Kebijakan Data',\n tr: 'Veri Politikası',\n th: 'นโยบายข้อมูล',\n vi: 'Chính sách dữ liệu',\n he: 'מדיניות נתונים',\n fa: 'سیاست داده',\n ms: 'Dasar Data',\n tl: 'Patakaran sa Data',\n },\n },\n\n // Status messages\n statusMessages: {\n connecting: {\n en: 'Connecting...',\n de: 'Verbindet...',\n fr: 'Connexion...',\n es: 'Conectando...',\n it: 'Connessione...',\n pt: 'Conectando...',\n nl: 'Verbinden...',\n pl: 'Łączenie...',\n sv: 'Ansluter...',\n da: 'Forbinder...',\n fi: 'Yhdistetään...',\n no: 'Kobler til...',\n cs: 'Připojování...',\n hu: 'Csatlakozás...',\n ro: 'Se conectează...',\n sk: 'Pripájanie...',\n hr: 'Povezivanje...',\n bg: 'Свързване...',\n sl: 'Povezovanje...',\n et: 'Ühendamine...',\n lv: 'Savienojas...',\n lt: 'Jungiamasi...',\n el: 'Σύνδεση...',\n ru: 'Подключение...',\n uk: 'Підключення...',\n // Major Asian and world languages\n ja: '接続中...',\n zh: '正在连接...',\n ko: '연결 중...',\n ar: 'جاري الاتصال...',\n hi: 'कनेक्ट हो रहा है...',\n id: 'Menghubungkan...',\n tr: 'Bağlanıyor...',\n th: 'กำลังเชื่อมต่อ...',\n vi: 'Đang kết nối...',\n he: 'מתחבר...',\n fa: 'در حال اتصال...',\n ms: 'Menyambung...',\n tl: 'Kumukonekta...',\n },\n typing: {\n en: 'Typing...',\n de: 'Schreibt...',\n fr: 'Saisie...',\n es: 'Escribiendo...',\n it: 'Scrivendo...',\n pt: 'Digitando...',\n nl: 'Typen...',\n pl: 'Pisze...',\n sv: 'Skriver...',\n da: 'Skriver...',\n fi: 'Kirjoittaa...',\n no: 'Skriver...',\n cs: 'Píše...',\n hu: 'Gépel...',\n ro: 'Scrie...',\n sk: 'Píše...',\n hr: 'Piše...',\n bg: 'Пише...',\n sl: 'Piše...',\n et: 'Kirjutab...',\n lv: 'Raksta...',\n lt: 'Rašo...',\n el: 'Πληκτρολογεί...',\n ru: 'Печатает...',\n uk: 'Друкує...',\n // Major Asian and world languages\n ja: '入力中...',\n zh: '正在输入...',\n ko: '입력 중...',\n ar: 'يكتب...',\n hi: 'टाइप कर रहा है...',\n id: 'Mengetik...',\n tr: 'Yazıyor...',\n th: 'กำลังพิมพ์...',\n vi: 'Đang gõ...',\n he: 'מקליד...',\n fa: 'در حال تایپ...',\n ms: 'Menaip...',\n tl: 'Nagta-type...',\n },\n },\n};\n\n/**\n * Get browser language with enhanced fallback for mobile and PC compatibility\n */\nexport function getBrowserLanguage() {\n let lang = 'en'; // Default fallback\n\n try {\n // Try multiple browser language detection methods for better mobile compatibility\n if (typeof navigator !== 'undefined') {\n // Primary: navigator.language (most reliable)\n lang = navigator.language;\n\n // Fallback 1: navigator.languages array (modern browsers)\n if (!lang && navigator.languages && navigator.languages.length > 0) {\n lang = navigator.languages[0];\n }\n\n // Fallback 2: navigator.userLanguage (IE/older browsers)\n if (!lang && navigator.userLanguage) {\n lang = navigator.userLanguage;\n }\n\n // Fallback 3: navigator.browserLanguage (IE/older browsers)\n if (!lang && navigator.browserLanguage) {\n lang = navigator.browserLanguage;\n }\n\n // Fallback 4: navigator.systemLanguage (IE/older browsers)\n if (!lang && navigator.systemLanguage) {\n lang = navigator.systemLanguage;\n }\n }\n\n // Ensure we have a valid string\n if (!lang || typeof lang !== 'string') {\n lang = 'en';\n }\n\n // Extract main language code (e.g., 'en-US' becomes 'en')\n lang = lang.split('-')[0].toLowerCase();\n\n // Validate the language code (basic check for 2-3 character codes)\n if (!/^[a-z]{2,3}$/.test(lang)) {\n lang = 'en';\n }\n } catch (error) {\n // If any error occurs, fallback to English\n console.warn('Language detection failed:', error);\n lang = 'en';\n }\n\n return lang;\n}\n\n/**\n * Get localized text with fallback to English\n */\nexport function getLocalizedText(category, key, language = null) {\n const lang = language || getBrowserLanguage();\n\n if (translations[category] && translations[category][key]) {\n return (\n translations[category][key][lang] ||\n translations[category][key]['en'] ||\n key\n );\n }\n\n return key;\n}\n\n/**\n * Get all translations for a category in current language\n */\nexport function getLocalizedCategory(category, language = null) {\n const lang = language || getBrowserLanguage();\n\n if (!translations[category]) return {};\n\n const result = {};\n for (const key in translations[category]) {\n result[key] =\n translations[category][key][lang] ||\n translations[category][key]['en'] ||\n key;\n }\n\n return result;\n}\n\n/**\n * Get direct translation for a top-level key\n */\nexport function getTranslation(key, language = null) {\n const lang = language || getBrowserLanguage();\n\n if (translations[key]) {\n return translations[key][lang] || translations[key]['en'] || key;\n }\n\n return key;\n}\n","/*! @license DOMPurify 3.2.6 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.6/LICENSE */\n\nconst {\n entries,\n setPrototypeOf,\n isFrozen,\n getPrototypeOf,\n getOwnPropertyDescriptor\n} = Object;\nlet {\n freeze,\n seal,\n create\n} = Object; // eslint-disable-line import/no-mutable-exports\nlet {\n apply,\n construct\n} = typeof Reflect !== 'undefined' && Reflect;\nif (!freeze) {\n freeze = function freeze(x) {\n return x;\n };\n}\nif (!seal) {\n seal = function seal(x) {\n return x;\n };\n}\nif (!apply) {\n apply = function apply(fun, thisValue, args) {\n return fun.apply(thisValue, args);\n };\n}\nif (!construct) {\n construct = function construct(Func, args) {\n return new Func(...args);\n };\n}\nconst arrayForEach = unapply(Array.prototype.forEach);\nconst arrayLastIndexOf = unapply(Array.prototype.lastIndexOf);\nconst arrayPop = unapply(Array.prototype.pop);\nconst arrayPush = unapply(Array.prototype.push);\nconst arraySplice = unapply(Array.prototype.splice);\nconst stringToLowerCase = unapply(String.prototype.toLowerCase);\nconst stringToString = unapply(String.prototype.toString);\nconst stringMatch = unapply(String.prototype.match);\nconst stringReplace = unapply(String.prototype.replace);\nconst stringIndexOf = unapply(String.prototype.indexOf);\nconst stringTrim = unapply(String.prototype.trim);\nconst objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);\nconst regExpTest = unapply(RegExp.prototype.test);\nconst typeErrorCreate = unconstruct(TypeError);\n/**\n * Creates a new function that calls the given function with a specified thisArg and arguments.\n *\n * @param func - The function to be wrapped and called.\n * @returns A new function that calls the given function with a specified thisArg and arguments.\n */\nfunction unapply(func) {\n return function (thisArg) {\n if (thisArg instanceof RegExp) {\n thisArg.lastIndex = 0;\n }\n for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {\n args[_key - 1] = arguments[_key];\n }\n return apply(func, thisArg, args);\n };\n}\n/**\n * Creates a new function that constructs an instance of the given constructor function with the provided arguments.\n *\n * @param func - The constructor function to be wrapped and called.\n * @returns A new function that constructs an instance of the given constructor function with the provided arguments.\n */\nfunction unconstruct(func) {\n return function () {\n for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {\n args[_key2] = arguments[_key2];\n }\n return construct(func, args);\n };\n}\n/**\n * Add properties to a lookup table\n *\n * @param set - The set to which elements will be added.\n * @param array - The array containing elements to be added to the set.\n * @param transformCaseFunc - An optional function to transform the case of each element before adding to the set.\n * @returns The modified set with added elements.\n */\nfunction addToSet(set, array) {\n let transformCaseFunc = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : stringToLowerCase;\n if (setPrototypeOf) {\n // Make 'in' and truthy checks like Boolean(set.constructor)\n // independent of any properties defined on Object.prototype.\n // Prevent prototype setters from intercepting set as a this value.\n setPrototypeOf(set, null);\n }\n let l = array.length;\n while (l--) {\n let element = array[l];\n if (typeof element === 'string') {\n const lcElement = transformCaseFunc(element);\n if (lcElement !== element) {\n // Config presets (e.g. tags.js, attrs.js) are immutable.\n if (!isFrozen(array)) {\n array[l] = lcElement;\n }\n element = lcElement;\n }\n }\n set[element] = true;\n }\n return set;\n}\n/**\n * Clean up an array to harden against CSPP\n *\n * @param array - The array to be cleaned.\n * @returns The cleaned version of the array\n */\nfunction cleanArray(array) {\n for (let index = 0; index < array.length; index++) {\n const isPropertyExist = objectHasOwnProperty(array, index);\n if (!isPropertyExist) {\n array[index] = null;\n }\n }\n return array;\n}\n/**\n * Shallow clone an object\n *\n * @param object - The object to be cloned.\n * @returns A new object that copies the original.\n */\nfunction clone(object) {\n const newObject = create(null);\n for (const [property, value] of entries(object)) {\n const isPropertyExist = objectHasOwnProperty(object, property);\n if (isPropertyExist) {\n if (Array.isArray(value)) {\n newObject[property] = cleanArray(value);\n } else if (value && typeof value === 'object' && value.constructor === Object) {\n newObject[property] = clone(value);\n } else {\n newObject[property] = value;\n }\n }\n }\n return newObject;\n}\n/**\n * This method automatically checks if the prop is function or getter and behaves accordingly.\n *\n * @param object - The object to look up the getter function in its prototype chain.\n * @param prop - The property name for which to find the getter function.\n * @returns The getter function found in the prototype chain or a fallback function.\n */\nfunction lookupGetter(object, prop) {\n while (object !== null) {\n const desc = getOwnPropertyDescriptor(object, prop);\n if (desc) {\n if (desc.get) {\n return unapply(desc.get);\n }\n if (typeof desc.value === 'function') {\n return unapply(desc.value);\n }\n }\n object = getPrototypeOf(object);\n }\n function fallbackValue() {\n return null;\n }\n return fallbackValue;\n}\n\nconst html$1 = freeze(['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'section', 'select', 'shadow', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr']);\nconst svg$1 = freeze(['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']);\nconst svgFilters = freeze(['feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight', 'feDropShadow', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence']);\n// List of SVG elements that are disallowed by default.\n// We still need to know them so that we can do namespace\n// checks properly in case one wants to add them to\n// allow-list.\nconst svgDisallowed = freeze(['animate', 'color-profile', 'cursor', 'discard', 'font-face', 'font-face-format', 'font-face-name', 'font-face-src', 'font-face-uri', 'foreignobject', 'hatch', 'hatchpath', 'mesh', 'meshgradient', 'meshpatch', 'meshrow', 'missing-glyph', 'script', 'set', 'solidcolor', 'unknown', 'use']);\nconst mathMl$1 = freeze(['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mglyph', 'mi', 'mlabeledtr', 'mmultiscripts', 'mn', 'mo', 'mover', 'mpadded', 'mphantom', 'mroot', 'mrow', 'ms', 'mspace', 'msqrt', 'mstyle', 'msub', 'msup', 'msubsup', 'mtable', 'mtd', 'mtext', 'mtr', 'munder', 'munderover', 'mprescripts']);\n// Similarly to SVG, we want to know all MathML elements,\n// even those that we disallow by default.\nconst mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);\nconst text = freeze(['#text']);\n\nconst html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inputmode', 'integrity', 'ismap', 'kind', 'label', 'lang', 'list', 'loading', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'minlength', 'multiple', 'muted', 'name', 'nonce', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'pattern', 'placeholder', 'playsinline', 'popover', 'popovertarget', 'popovertargetaction', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'wrap', 'xmlns', 'slot']);\nconst svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline', 'amplitude', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', 'clippathunits', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction', 'display', 'divisor', 'dur', 'edgemode', 'elevation', 'end', 'exponent', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'filterunits', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform', 'height', 'href', 'id', 'image-rendering', 'in', 'in2', 'intercept', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints', 'keysplines', 'keytimes', 'lang', 'lengthadjust', 'letter-spacing', 'kernelmatrix', 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid', 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits', 'maskunits', 'max', 'mask', 'media', 'method', 'mode', 'min', 'name', 'numoctaves', 'offset', 'operator', 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order', 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits', 'points', 'preservealpha', 'preserveaspectratio', 'primitiveunits', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'slope', 'specularconstant', 'specularexponent', 'spreadmethod', 'startoffset', 'stddeviation', 'stitchtiles', 'stop-color', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width', 'style', 'surfacescale', 'systemlanguage', 'tabindex', 'tablevalues', 'targetx', 'targety', 'transform', 'transform-origin', 'text-anchor', 'text-decoration', 'text-rendering', 'textlength', 'type', 'u1', 'u2', 'unicode', 'values', 'viewbox', 'visibility', 'version', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'width', 'word-spacing', 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2', 'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan']);\nconst mathMl = freeze(['accent', 'accentunder', 'align', 'bevelled', 'close', 'columnsalign', 'columnlines', 'columnspan', 'denomalign', 'depth', 'dir', 'display', 'displaystyle', 'encoding', 'fence', 'frame', 'height', 'href', 'id', 'largeop', 'length', 'linethickness', 'lspace', 'lquote', 'mathbackground', 'mathcolor', 'mathsize', 'mathvariant', 'maxsize', 'minsize', 'movablelimits', 'notation', 'numalign', 'open', 'rowalign', 'rowlines', 'rowspacing', 'rowspan', 'rspace', 'rquote', 'scriptlevel', 'scriptminsize', 'scriptsizemultiplier', 'selection', 'separator', 'separators', 'stretchy', 'subscriptshift', 'supscriptshift', 'symmetric', 'voffset', 'width', 'xmlns']);\nconst xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);\n\n// eslint-disable-next-line unicorn/better-regex\nconst MUSTACHE_EXPR = seal(/\\{\\{[\\w\\W]*|[\\w\\W]*\\}\\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode\nconst ERB_EXPR = seal(/<%[\\w\\W]*|[\\w\\W]*%>/gm);\nconst TMPLIT_EXPR = seal(/\\$\\{[\\w\\W]*/gm); // eslint-disable-line unicorn/better-regex\nconst DATA_ATTR = seal(/^data-[\\-\\w.\\u00B7-\\uFFFF]+$/); // eslint-disable-line no-useless-escape\nconst ARIA_ATTR = seal(/^aria-[\\-\\w]+$/); // eslint-disable-line no-useless-escape\nconst IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\\-]+(?:[^a-z+.\\-:]|$))/i // eslint-disable-line no-useless-escape\n);\nconst IS_SCRIPT_OR_DATA = seal(/^(?:\\w+script|data):/i);\nconst ATTR_WHITESPACE = seal(/[\\u0000-\\u0020\\u00A0\\u1680\\u180E\\u2000-\\u2029\\u205F\\u3000]/g // eslint-disable-line no-control-regex\n);\nconst DOCTYPE_NAME = seal(/^html$/i);\nconst CUSTOM_ELEMENT = seal(/^[a-z][.\\w]*(-[.\\w]+)+$/i);\n\nvar EXPRESSIONS = /*#__PURE__*/Object.freeze({\n __proto__: null,\n ARIA_ATTR: ARIA_ATTR,\n ATTR_WHITESPACE: ATTR_WHITESPACE,\n CUSTOM_ELEMENT: CUSTOM_ELEMENT,\n DATA_ATTR: DATA_ATTR,\n DOCTYPE_NAME: DOCTYPE_NAME,\n ERB_EXPR: ERB_EXPR,\n IS_ALLOWED_URI: IS_ALLOWED_URI,\n IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA,\n MUSTACHE_EXPR: MUSTACHE_EXPR,\n TMPLIT_EXPR: TMPLIT_EXPR\n});\n\n/* eslint-disable @typescript-eslint/indent */\n// https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType\nconst NODE_TYPE = {\n element: 1,\n attribute: 2,\n text: 3,\n cdataSection: 4,\n entityReference: 5,\n // Deprecated\n entityNode: 6,\n // Deprecated\n progressingInstruction: 7,\n comment: 8,\n document: 9,\n documentType: 10,\n documentFragment: 11,\n notation: 12 // Deprecated\n};\nconst getGlobal = function getGlobal() {\n return typeof window === 'undefined' ? null : window;\n};\n/**\n * Creates a no-op policy for internal use only.\n * Don't export this function outside this module!\n * @param trustedTypes The policy factory.\n * @param purifyHostElement The Script element used to load DOMPurify (to determine policy name suffix).\n * @return The policy created (or null, if Trusted Types\n * are not supported or creating the policy failed).\n */\nconst _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedTypes, purifyHostElement) {\n if (typeof trustedTypes !== 'object' || typeof trustedTypes.createPolicy !== 'function') {\n return null;\n }\n // Allow the callers to control the unique policy name\n // by adding a data-tt-policy-suffix to the script element with the DOMPurify.\n // Policy creation with duplicate names throws in Trusted Types.\n let suffix = null;\n const ATTR_NAME = 'data-tt-policy-suffix';\n if (purifyHostElement && purifyHostElement.hasAttribute(ATTR_NAME)) {\n suffix = purifyHostElement.getAttribute(ATTR_NAME);\n }\n const policyName = 'dompurify' + (suffix ? '#' + suffix : '');\n try {\n return trustedTypes.createPolicy(policyName, {\n createHTML(html) {\n return html;\n },\n createScriptURL(scriptUrl) {\n return scriptUrl;\n }\n });\n } catch (_) {\n // Policy creation failed (most likely another DOMPurify script has\n // already run). Skip creating the policy, as this will only cause errors\n // if TT are enforced.\n console.warn('TrustedTypes policy ' + policyName + ' could not be created.');\n return null;\n }\n};\nconst _createHooksMap = function _createHooksMap() {\n return {\n afterSanitizeAttributes: [],\n afterSanitizeElements: [],\n afterSanitizeShadowDOM: [],\n beforeSanitizeAttributes: [],\n beforeSanitizeElements: [],\n beforeSanitizeShadowDOM: [],\n uponSanitizeAttribute: [],\n uponSanitizeElement: [],\n uponSanitizeShadowNode: []\n };\n};\nfunction createDOMPurify() {\n let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();\n const DOMPurify = root => createDOMPurify(root);\n DOMPurify.version = '3.2.6';\n DOMPurify.removed = [];\n if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {\n // Not running in a browser, provide a factory function\n // so that you can pass your own Window\n DOMPurify.isSupported = false;\n return DOMPurify;\n }\n let {\n document\n } = window;\n const originalDocument = document;\n const currentScript = originalDocument.currentScript;\n const {\n DocumentFragment,\n HTMLTemplateElement,\n Node,\n Element,\n NodeFilter,\n NamedNodeMap = window.NamedNodeMap || window.MozNamedAttrMap,\n HTMLFormElement,\n DOMParser,\n trustedTypes\n } = window;\n const ElementPrototype = Element.prototype;\n const cloneNode = lookupGetter(ElementPrototype, 'cloneNode');\n const remove = lookupGetter(ElementPrototype, 'remove');\n const getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');\n const getChildNodes = lookupGetter(ElementPrototype, 'childNodes');\n const getParentNode = lookupGetter(ElementPrototype, 'parentNode');\n // As per issue #47, the web-components registry is inherited by a\n // new document created via createHTMLDocument. As per the spec\n // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)\n // a new empty registry is used when creating a template contents owner\n // document, so we use that as our parent document to ensure nothing\n // is inherited.\n if (typeof HTMLTemplateElement === 'function') {\n const template = document.createElement('template');\n if (template.content && template.content.ownerDocument) {\n document = template.content.ownerDocument;\n }\n }\n let trustedTypesPolicy;\n let emptyHTML = '';\n const {\n implementation,\n createNodeIterator,\n createDocumentFragment,\n getElementsByTagName\n } = document;\n const {\n importNode\n } = originalDocument;\n let hooks = _createHooksMap();\n /**\n * Expose whether this browser supports running the full DOMPurify.\n */\n DOMPurify.isSupported = typeof entries === 'function' && typeof getParentNode === 'function' && implementation && implementation.createHTMLDocument !== undefined;\n const {\n MUSTACHE_EXPR,\n ERB_EXPR,\n TMPLIT_EXPR,\n DATA_ATTR,\n ARIA_ATTR,\n IS_SCRIPT_OR_DATA,\n ATTR_WHITESPACE,\n CUSTOM_ELEMENT\n } = EXPRESSIONS;\n let {\n IS_ALLOWED_URI: IS_ALLOWED_URI$1\n } = EXPRESSIONS;\n /**\n * We consider the elements and attributes below to be safe. Ideally\n * don't add any new ones but feel free to remove unwanted ones.\n */\n /* allowed element names */\n let ALLOWED_TAGS = null;\n const DEFAULT_ALLOWED_TAGS = addToSet({}, [...html$1, ...svg$1, ...svgFilters, ...mathMl$1, ...text]);\n /* Allowed attribute names */\n let ALLOWED_ATTR = null;\n const DEFAULT_ALLOWED_ATTR = addToSet({}, [...html, ...svg, ...mathMl, ...xml]);\n /*\n * Configure how DOMPurify should handle custom elements and their attributes as well as customized built-in elements.\n * @property {RegExp|Function|null} tagNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any custom elements)\n * @property {RegExp|Function|null} attributeNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any attributes not on the allow list)\n * @property {boolean} allowCustomizedBuiltInElements allow custom elements derived from built-ins if they pass CUSTOM_ELEMENT_HANDLING.tagNameCheck. Default: `false`.\n */\n let CUSTOM_ELEMENT_HANDLING = Object.seal(create(null, {\n tagNameCheck: {\n writable: true,\n configurable: false,\n enumerable: true,\n value: null\n },\n attributeNameCheck: {\n writable: true,\n configurable: false,\n enumerable: true,\n value: null\n },\n allowCustomizedBuiltInElements: {\n writable: true,\n configurable: false,\n enumerable: true,\n value: false\n }\n }));\n /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */\n let FORBID_TAGS = null;\n /* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */\n let FORBID_ATTR = null;\n /* Decide if ARIA attributes are okay */\n let ALLOW_ARIA_ATTR = true;\n /* Decide if custom data attributes are okay */\n let ALLOW_DATA_ATTR = true;\n /* Decide if unknown protocols are okay */\n let ALLOW_UNKNOWN_PROTOCOLS = false;\n /* Decide if self-closing tags in attributes are allowed.\n * Usually removed due to a mXSS issue in jQuery 3.0 */\n let ALLOW_SELF_CLOSE_IN_ATTR = true;\n /* Output should be safe for common template engines.\n * This means, DOMPurify removes data attributes, mustaches and ERB\n */\n let SAFE_FOR_TEMPLATES = false;\n /* Output should be safe even for XML used within HTML and alike.\n * This means, DOMPurify removes comments when containing risky content.\n */\n let SAFE_FOR_XML = true;\n /* Decide if document with <html>... should be returned */\n let WHOLE_DOCUMENT = false;\n /* Track whether config is already set on this instance of DOMPurify. */\n let SET_CONFIG = false;\n /* Decide if all elements (e.g. style, script) must be children of\n * document.body. By default, browsers might move them to document.head */\n let FORCE_BODY = false;\n /* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html\n * string (or a TrustedHTML object if Trusted Types are supported).\n * If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead\n */\n let RETURN_DOM = false;\n /* Decide if a DOM `DocumentFragment` should be returned, instead of a html\n * string (or a TrustedHTML object if Trusted Types are supported) */\n let RETURN_DOM_FRAGMENT = false;\n /* Try to return a Trusted Type object instead of a string, return a string in\n * case Trusted Types are not supported */\n let RETURN_TRUSTED_TYPE = false;\n /* Output should be free from DOM clobbering attacks?\n * This sanitizes markups named with colliding, clobberable built-in DOM APIs.\n */\n let SANITIZE_DOM = true;\n /* Achieve full DOM Clobbering protection by isolating the namespace of named\n * properties and JS variables, mitigating attacks that abuse the HTML/DOM spec rules.\n *\n * HTML/DOM spec rules that enable DOM Clobbering:\n * - Named Access on Window (§7.3.3)\n * - DOM Tree Accessors (§3.1.5)\n * - Form Element Parent-Child Relations (§4.10.3)\n * - Iframe srcdoc / Nested WindowProxies (§4.8.5)\n * - HTMLCollection (§4.2.10.2)\n *\n * Namespace isolation is implemented by prefixing `id` and `name` attributes\n * with a constant string, i.e., `user-content-`\n */\n let SANITIZE_NAMED_PROPS = false;\n const SANITIZE_NAMED_PROPS_PREFIX = 'user-content-';\n /* Keep element content when removing element? */\n let KEEP_CONTENT = true;\n /* If a `Node` is passed to sanitize(), then performs sanitization in-place instead\n * of importing it into a new Document and returning a sanitized copy */\n let IN_PLACE = false;\n /* Allow usage of profiles like html, svg and mathMl */\n let USE_PROFILES = {};\n /* Tags to ignore content of when KEEP_CONTENT is true */\n let FORBID_CONTENTS = null;\n const DEFAULT_FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'noscript', 'plaintext', 'script', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']);\n /* Tags that are safe for data: URIs */\n let DATA_URI_TAGS = null;\n const DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']);\n /* Attributes safe for values like \"javascript:\" */\n let URI_SAFE_ATTRIBUTES = null;\n const DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'role', 'summary', 'title', 'value', 'style', 'xmlns']);\n const MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';\n const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';\n const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';\n /* Document namespace */\n let NAMESPACE = HTML_NAMESPACE;\n let IS_EMPTY_INPUT = false;\n /* Allowed XHTML+XML namespaces */\n let ALLOWED_NAMESPACES = null;\n const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], stringToString);\n let MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']);\n let HTML_INTEGRATION_POINTS = addToSet({}, ['annotation-xml']);\n // Certain elements are allowed in both SVG and HTML\n // namespace. We need to sp