_binary From 6466d2f42451d7f26097614663afc0ff28c973b5 Mon Sep 17 00:00:00 2001
From: Aditya <thesyn3rman@gmail.com>
Date: Mon, 27 Jan 2025 11:37:22 -0800
Subject: [PATCH] add audio message recording in chat

Signed-off-by: Aditya <thesyn3rman@gmail.com>
---
 src/css/messages.css                        |  60 +++++++
 src/locale/ar/configure.ini                 |   1 +
 src/locale/bn/configure.ini                 |   1 +
 src/locale/de/configure.ini                 |   1 +
 src/locale/el_GR/configure.ini              |   1 +
 src/locale/en_US/configure.ini              |   1 +
 src/locale/es/configure.ini                 |   1 +
 src/locale/fa/configure.ini                 |   1 +
 src/locale/fr_FR/configure.ini              |   1 +
 src/locale/he/configure.ini                 |   1 +
 src/locale/hi/configure.ini                 |   1 +
 src/locale/id/configure.ini                 |   1 +
 src/locale/it/configure.ini                 |   1 +
 src/locale/ja/configure.ini                 |   1 +
 src/locale/ko/configure.ini                 |   1 +
 src/locale/nl/configure.ini                 |   1 +
 src/locale/pl/configure.ini                 |   1 +
 src/locale/pt/configure.ini                 |   1 +
 src/locale/ru/configure.ini                 |   1 +
 src/locale/te/configure.ini                 |   1 +
 src/locale/th/configure.ini                 |   1 +
 src/locale/tl/configure.ini                 |   1 +
 src/locale/tr/configure.ini                 |   1 +
 src/locale/vi_VN/configure.ini              |   1 +
 src/locale/zh_CN/configure.ini              |   1 +
 src/scripts/basic.js                        | 166 ++++++++++++++++++++
 src/views/elements/ResultseditorElement.php |   8 +-
 src/views/elements/UsermessagesElement.php  |  23 ++-
 28 files changed, 275 insertions(+), 6 deletions(-)

diff --git a/src/css/messages.css b/src/css/messages.css
index e79d1f5a7..b6fea5a0b 100644
--- a/src/css/messages.css
+++ b/src/css/messages.css
@@ -440,3 +440,63 @@
     border: 1px solid grey;
     border-radius: 10%;
 }
+.new-message-container form {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    width: 100%;
+    justify-content: center;
+}
+#audio-record-btn {
+    background-color: #EEF;
+    border-radius: 20px;
+    border: solid 1px gray;
+    color: #000;
+    cursor: pointer;
+    display: inline-block;
+    font-size: 22px;
+    font-weight: bold;
+    margin: 0;
+    padding: 12px;
+    text-align: center;
+    text-decoration: none;
+    width: 44px;
+    height: 44px;
+}
+.audio-control {
+    background-color: #EEF;
+    border-radius: 20px;
+    border: solid 1px gray;
+    color: #000;
+    cursor: pointer;
+    display: inline-block;
+    font-size: 22px;
+    font-weight: bold;
+    margin: 0;
+    padding: 12px;
+    text-align: center;
+    text-decoration: none;
+    width: 44px;
+    height: 44px;
+}
+#audio-controls {
+    display: inline-flex;
+    gap: 4px;
+    align-items: center;
+}
+#audio-controls.none {
+    display: none;
+}
+#mic-icon,
+#play-audio-btn,
+#delete-audio-btn {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 100%;
+    height: 100%;
+}
+.recording {
+    background-color: #EEF !important;
+    color: red;
+}
diff --git a/src/locale/ar/configure.ini b/src/locale/ar/configure.ini
index cbaf61e27..579b82532 100755
--- a/src/locale/ar/configure.ini
+++ b/src/locale/ar/configure.ini
@@ -5591,6 +5591,7 @@ basic_js_progress_meter_disabled = "تحميل مقياس التقدم المع
 basic_js_upload_error = "خطأ في التحميل!"
 basic_js_upload_cancelled = "ملغّظ التحميل!"
 basic_js_too_many_files = "في محاولة لإضافة ملفات كثيرة!"
+basic_js_microphone_access_error = "تعذر الوصول إلى الميكروفون. يرجى التأكد من منح الإذن."
 ;
 ; IconlinkHelper.php
 iconlink_helper_account_home = "منزل الحساب"
diff --git a/src/locale/bn/configure.ini b/src/locale/bn/configure.ini
index b0458dcd5..4b3a8ff8d 100755
--- a/src/locale/bn/configure.ini
+++ b/src/locale/bn/configure.ini
@@ -5591,6 +5591,7 @@ basic_js_progress_meter_disabled = "আপলোড প্রগ্রেস ম
 basic_js_upload_error = "আপলোড ভাষ্য!"
 basic_js_upload_cancelled = "তারিখ বাতিল!"
 basic_js_too_many_files = "যোগ করার চেষ্টা অনেকগুলি ফাইল!"
+basic_js_microphone_access_error = "মাইক্রোফোন অ্যাক্সেস করা যায়নি। অনুগ্রহ করে নিশ্চিত করুন যে আপনি অনুমতি দিয়েছেন।"
 ;
 ; IconlinkHelper.php
 iconlink_helper_account_home = "অ্যাকাউন্ট হোম"
diff --git a/src/locale/de/configure.ini b/src/locale/de/configure.ini
index 75112f605..2038e7177 100755
--- a/src/locale/de/configure.ini
+++ b/src/locale/de/configure.ini
@@ -5591,6 +5591,7 @@ basic_js_progress_meter_disabled = "Fortschrits-Anzeige für Upload-Aktivität d
 basic_js_upload_error = "Fehler bei der Hochladen!"
 basic_js_upload_cancelled = "Upload Abgebrochen!"
 basic_js_too_many_files = "Sie versuchen, um zu viele Dateien!"
+basic_js_microphone_access_error = "Zugriff auf das Mikrofon nicht möglich. Bitte stellen Sie sicher, dass Sie die Berechtigung erteilt haben."
 ;
 ; IconlinkHelper.php
 iconlink_helper_account_home = "Zuhause-Konto"
diff --git a/src/locale/el_GR/configure.ini b/src/locale/el_GR/configure.ini
index d9de3ba33..8601e9f68 100644
--- a/src/locale/el_GR/configure.ini
+++ b/src/locale/el_GR/configure.ini
@@ -5616,6 +5616,7 @@ basic_js_progress_meter_disabled = "Μέτρο Προόδου Μεταφόρτω
 basic_js_upload_error = "Σφτασμα Μεταφόρτωση!"
 basic_js_upload_cancelled = "Ανακύρωση Μεταφόρτωσης!"
 basic_js_too_many_files = "Προσπαθώντας να προσθέσετε πολύ πολλούς αρχείων!"
+basic_js_microphone_access_error = "Δεν ήταν δυνατή η πρόσβαση στο μικρόφωνο. Βεβαιωθείτε ότι έχετε δώσει άδεια."
 ;
 ; IconlinkHelper.php
 iconlink_helper_account_home = "Σχέδιο Χώματος"
diff --git a/src/locale/en_US/configure.ini b/src/locale/en_US/configure.ini
index 25bd221b0..a23d8b141 100644
--- a/src/locale/en_US/configure.ini
+++ b/src/locale/en_US/configure.ini
@@ -5591,6 +5591,7 @@ basic_js_progress_meter_disabled = "Upload Progress Meter Disabled"
 basic_js_upload_error = "Upload Error!"
 basic_js_upload_cancelled = "Upload Canceled!"
 basic_js_too_many_files = "Trying to add too many files!"
+basic_js_microphone_access_error = "Could not access microphone. Please ensure you have granted permission."
 ;
 ; IconlinkHelper.php
 iconlink_helper_account_home = "Account Home"
diff --git a/src/locale/es/configure.ini b/src/locale/es/configure.ini
index 411b01c65..5af48e21c 100755
--- a/src/locale/es/configure.ini
+++ b/src/locale/es/configure.ini
@@ -5591,6 +5591,7 @@ basic_js_progress_meter_disabled = "Progreso de carga deshabilitado"
 basic_js_upload_error = "Error al cargar!"
 basic_js_upload_cancelled = "Carga cancelada!"
 basic_js_too_many_files = "Intentar agregar demasiados archivos!"
+basic_js_microphone_access_error = "No se pudo acceder al micrófono. Por favor, asegúrese de haber otorgado permiso."
 ;
 ; IconlinkHelper.php
 iconlink_helper_account_home = "Casa de Cuenta"
diff --git a/src/locale/fa/configure.ini b/src/locale/fa/configure.ini
index fd5f2650a..76a5df694 100755
--- a/src/locale/fa/configure.ini
+++ b/src/locale/fa/configure.ini
@@ -5591,6 +5591,7 @@ basic_js_progress_meter_disabled = "آفلود لغو شد!"
 basic_js_upload_error = "ها Error!"
 basic_js_upload_cancelled = "ها لغو شد!"
 basic_js_too_many_files = "درصدد افزودن تعداد زیادی فایل!"
+basic_js_microphone_access_error = "دسترسی به میکروفون ممکن نیست. لطفاً مطمئن شوید که مجوز را داده‌اید."
 ;
 ; IconlinkHelper.php
 iconlink_helper_account_home = "خانه حساب"
diff --git a/src/locale/fr_FR/configure.ini b/src/locale/fr_FR/configure.ini
index 66937132c..b8c5bb323 100755
--- a/src/locale/fr_FR/configure.ini
+++ b/src/locale/fr_FR/configure.ini
@@ -5591,6 +5591,7 @@ basic_js_progress_meter_disabled = "Mètre de téléversement désactivé"
 basic_js_upload_error = "Erreur de téléversement !"
 basic_js_upload_cancelled = "Téléversement annulé !"
 basic_js_too_many_files = "Essayez d&#039;ajouter de trop nombreux dossiers!"
+basic_js_microphone_access_error = "Impossible d'accéder au microphone. Veuillez vous assurer d'avoir accordé l'autorisation."
 ;
 ; IconlinkHelper.php
 iconlink_helper_account_home = "Accueil du Compte"
diff --git a/src/locale/he/configure.ini b/src/locale/he/configure.ini
index 03f95ad10..8edd97c67 100755
--- a/src/locale/he/configure.ini
+++ b/src/locale/he/configure.ini
@@ -5591,6 +5591,7 @@ basic_js_progress_meter_disabled = "טען מד התקדמות נכים"
 basic_js_upload_error = "טען שגיאה!"
 basic_js_upload_cancelled = "טען בוטלה!"
 basic_js_too_many_files = "מנסה להוסיף קבצים רבים מדי!"
+basic_js_microphone_access_error = "לא ניתן לגשת למיקרופון. אנא ודא שנתת הרשאה."
 ;
 ; IconlinkHelper.php
 iconlink_helper_account_home = "חשבון הבית"
diff --git a/src/locale/hi/configure.ini b/src/locale/hi/configure.ini
index 5ca2280e5..ceedf20cc 100755
--- a/src/locale/hi/configure.ini
+++ b/src/locale/hi/configure.ini
@@ -5591,6 +5591,7 @@ basic_js_progress_meter_disabled = "अपलोड प्रगति मीट
 basic_js_upload_error = "अपलोड त्रुटि!"
 basic_js_upload_cancelled = "अपलोड रद्द कर दिया!"
 basic_js_too_many_files = "की कोशिश कर रहा है जोड़ने के लिए भी कई फाइलों!"
+basic_js_microphone_access_error = "माइक्रोफोन तक पहुंच असंभव। कृपया सुनिश्चित करें कि आपने अनुमति प्रदान की है।"
 ;
 ; IconlinkHelper.php
 iconlink_helper_account_home = "खाता घर"
diff --git a/src/locale/id/configure.ini b/src/locale/id/configure.ini
index 372d18bc5..049b9b1c9 100755
--- a/src/locale/id/configure.ini
+++ b/src/locale/id/configure.ini
@@ -5591,6 +5591,7 @@ basic_js_progress_meter_disabled = "Tanggal Kemajuan Meter Dinonaktifkan"
 basic_js_upload_error = "Kesalahan Meng-Upload!"
 basic_js_upload_cancelled = "Tanggal Dibatalkan!"
 basic_js_too_many_files = "Mencoba untuk menambahkan terlalu banyak file!"
+basic_js_microphone_access_error = "Tidak dapat mengakses mikrofon. Pastikan Anda telah memberikan izin."
 ;
 ; IconlinkHelper.php
 iconlink_helper_account_home = "Rumah Akun"
diff --git a/src/locale/it/configure.ini b/src/locale/it/configure.ini
index 886b914c3..3f7b16dce 100755
--- a/src/locale/it/configure.ini
+++ b/src/locale/it/configure.ini
@@ -5591,6 +5591,7 @@ basic_js_progress_meter_disabled = "Caricare L&#039;Indicatore Di Avanzamento Di
 basic_js_upload_error = "Errore Di Caricamento!"
 basic_js_upload_cancelled = "Caricare Annullato!"
 basic_js_too_many_files = "Cercando di aggiungere un numero eccessivo di file!"
+basic_js_microphone_access_error = "Impossibile accedere al microfono. Assicurati di aver concesso il permesso."
 ;
 ; IconlinkHelper.php
 iconlink_helper_account_home = "Account Home"
diff --git a/src/locale/ja/configure.ini b/src/locale/ja/configure.ini
index f0f4463b9..b7df11547 100755
--- a/src/locale/ja/configure.ini
+++ b/src/locale/ja/configure.ini
@@ -5591,6 +5591,7 @@ basic_js_progress_meter_disabled = "アップロードメーターを無効に"
 basic_js_upload_error = "アップロードエラー!"
 basic_js_upload_cancelled = "アップロードを解除!"
 basic_js_too_many_files = "を使用することによりファイル数が多すぎる!"
+basic_js_microphone_access_error = "マイクにアクセスできませんでした。許可を与えていることを確認してください。"
 ;
 ; IconlinkHelper.php
 iconlink_helper_account_home = "アカウントホーム"
diff --git a/src/locale/ko/configure.ini b/src/locale/ko/configure.ini
index 0dc5c7592..c3f97e85a 100755
--- a/src/locale/ko/configure.ini
+++ b/src/locale/ko/configure.ini
@@ -5591,6 +5591,7 @@ basic_js_progress_meter_disabled = "업로드 진행 과정을 비활성화"
 basic_js_upload_error = "업로드하는 오류!"
 basic_js_upload_cancelled = "업로 취소됩니다!"
 basic_js_too_many_files = "을 추가하려고 너무 많은 파일이!"
+basic_js_microphone_access_error = "마이크에 접근할 수 없습니다. 권한을 부여했는지 확인하세요."
 ;
 ; IconlinkHelper.php
 iconlink_helper_account_home = "계정 홈"
diff --git a/src/locale/nl/configure.ini b/src/locale/nl/configure.ini
index 1e829b415..794020523 100644
--- a/src/locale/nl/configure.ini
+++ b/src/locale/nl/configure.ini
@@ -5591,6 +5591,7 @@ basic_js_progress_meter_disabled = "Upload voortgangsbalk handicap"
 basic_js_upload_error = "Upload Fout!"
 basic_js_upload_cancelled = "Uploaden afgelast!"
 basic_js_too_many_files = "Proberen om te veel bestanden toe te voegen!"
+basic_js_microphone_access_error = "Kon geen toegang krijgen tot de microfoon. Zorg ervoor dat u toestemming hebt verleend."
 ;
 ; IconlinkHelper.php
 iconlink_helper_account_home = "Account Startpagina"
diff --git a/src/locale/pl/configure.ini b/src/locale/pl/configure.ini
index d3646b900..d98f0ee54 100755
--- a/src/locale/pl/configure.ini
+++ b/src/locale/pl/configure.ini
@@ -5591,6 +5591,7 @@ basic_js_progress_meter_disabled = "Pobierz Wskaźnik Postępu Wyłączone"
 basic_js_upload_error = "Błąd Pobierania!"
 basic_js_upload_cancelled = "Pobierz Został Odwołany!"
 basic_js_too_many_files = "Pr&oacute;bujesz dodać zbyt wiele plik&oacute;w!"
+basic_js_microphone_access_error = "Nie można uzyskać dostępu do mikrofonu. Upewnij się, że udzieliłeś pozwolenia."
 ;
 ; IconlinkHelper.php
 iconlink_helper_account_home = "Konto Strona Główna"
diff --git a/src/locale/pt/configure.ini b/src/locale/pt/configure.ini
index 7eaad4ef4..b4a249dcd 100755
--- a/src/locale/pt/configure.ini
+++ b/src/locale/pt/configure.ini
@@ -5591,6 +5591,7 @@ basic_js_progress_meter_disabled = "Progresso Do Upload Medidor De Defici&ecirc;
 basic_js_upload_error = "Erro De Upload!"
 basic_js_upload_cancelled = "Upload Cancelado!"
 basic_js_too_many_files = "A tentar adicionar muitos arquivos!"
+basic_js_microphone_access_error = "Não foi possível acessar o microfone. Certifique-se de que você concedeu permissão."
 ;
 ; IconlinkHelper.php
 iconlink_helper_account_home = "Conta Home"
diff --git a/src/locale/ru/configure.ini b/src/locale/ru/configure.ini
index e9b0523f0..a41d081f4 100755
--- a/src/locale/ru/configure.ini
+++ b/src/locale/ru/configure.ini
@@ -5591,6 +5591,7 @@ basic_js_progress_meter_disabled = "Загрузить Индикатор Про
 basic_js_upload_error = "Ошибка Загрузки!"
 basic_js_upload_cancelled = "Загрузить Отменял!"
 basic_js_too_many_files = "Пытаетесь добавить слишком много файлов!"
+basic_js_microphone_access_error = "Не удалось получить доступ к микрофону. Убедитесь, что вы предоставили разрешение."
 ;
 ; IconlinkHelper.php
 iconlink_helper_account_home = "Главная страница учетной записи"
diff --git a/src/locale/te/configure.ini b/src/locale/te/configure.ini
index e26795786..aaa6956ae 100644
--- a/src/locale/te/configure.ini
+++ b/src/locale/te/configure.ini
@@ -5591,6 +5591,7 @@ basic_js_progress_meter_disabled = "అప్లోడ్ పురోగతి
 basic_js_upload_error = "అప్లోడ్ లోపం!"
 basic_js_upload_cancelled = "అప్లోడ్ రద్దు!"
 basic_js_too_many_files = "Trying to add too many files!"
+basic_js_microphone_access_error = "మైక్రోఫోన్‌కు యాక్సెస్ చేయలేకపోయారు. దయచేసి మీరు అనుమతి ఇచ్చారో లేదో నిర్ధారించుకోండి."
 ;
 ; IconlinkHelper.php
 iconlink_helper_account_home = "ఖాతా హోమ్"
diff --git a/src/locale/th/configure.ini b/src/locale/th/configure.ini
index 36c7074e9..900bc6ce3 100755
--- a/src/locale/th/configure.ini
+++ b/src/locale/th/configure.ini
@@ -5591,6 +5591,7 @@ basic_js_progress_meter_disabled = "อัปโหลดความคืบ
 basic_js_upload_error = "อัปโหลดเกิดข้อผิดพลาด!"
 basic_js_upload_cancelled = "อัปโหลดถูกยกเลิก!"
 basic_js_too_many_files = "พยายามจะเพิ่มมากเกินไปแฟ้ม!"
+basic_js_microphone_access_error = "ไม่สามารถเข้าถึงไมโครโฟนได้ โปรดตรวจสอบว่าคุณได้ให้สิทธิ์แล้ว."
 ;
 ; IconlinkHelper.php
 iconlink_helper_account_home = "บ้านบัญชี"
diff --git a/src/locale/tl/configure.ini b/src/locale/tl/configure.ini
index 9e856cf46..b203f379c 100644
--- a/src/locale/tl/configure.ini
+++ b/src/locale/tl/configure.ini
@@ -5591,6 +5591,7 @@ basic_js_progress_meter_disabled = "Mag-Upload Ng Progreso Meter Hindi Pinagana"
 basic_js_upload_error = "I-Upload Ang Error!"
 basic_js_upload_cancelled = "I-Upload Ang Nakansela!"
 basic_js_too_many_files = "Sinusubukang upang magdagdag ng masyadong maraming mga file!"
+basic_js_microphone_access_error = "Hindi ma-access ang mikropono. Siguraduhing binigyan mo na ng pahintulot."
 ;
 ; IconlinkHelper.php
 iconlink_helper_account_home = "Account Sa Bahay"
diff --git a/src/locale/tr/configure.ini b/src/locale/tr/configure.ini
index a7983c6a2..4a75e02a6 100755
--- a/src/locale/tr/configure.ini
+++ b/src/locale/tr/configure.ini
@@ -5591,6 +5591,7 @@ basic_js_progress_meter_disabled = "Y&uuml;kleme İlerleme G&ouml;stergesi Devre
 basic_js_upload_error = "Y&uuml;kleme Hatası!"
 basic_js_upload_cancelled = "Y&uuml;kleme İptal Edildi!"
 basic_js_too_many_files = "&Ccedil;ok fazla dosya eklemek i&ccedil;in &ccedil;alışıyoruz!"
+basic_js_microphone_access_error = "Mikrofona erişilemedi. Lütfen izin verdiğinizden emin olun."
 ;
 ; IconlinkHelper.php
 iconlink_helper_account_home = "Hesap Ana Sayfası"
diff --git a/src/locale/vi_VN/configure.ini b/src/locale/vi_VN/configure.ini
index c2f155a26..cb89e6ec7 100755
--- a/src/locale/vi_VN/configure.ini
+++ b/src/locale/vi_VN/configure.ini
@@ -5591,6 +5591,7 @@ basic_js_progress_meter_disabled = "Tiến Trình Tải Lên Mét Vô Hiệu Hó
 basic_js_upload_error = "Tải Lên Lỗi!"
 basic_js_upload_cancelled = "Tải Lên Hủy Bỏ!"
 basic_js_too_many_files = "Cố gắng thêm quá nhiều các tập tin!"
+basic_js_microphone_access_error = "Không thể truy cập vào micro. Vui lòng đảm bảo rằng bạn đã cấp quyền."
 ;
 ; IconlinkHelper.php
 iconlink_helper_account_home = "Tài Khoản Nhà"
diff --git a/src/locale/zh_CN/configure.ini b/src/locale/zh_CN/configure.ini
index a46d1a165..27b75b86c 100755
--- a/src/locale/zh_CN/configure.ini
+++ b/src/locale/zh_CN/configure.ini
@@ -5591,6 +5591,7 @@ basic_js_progress_meter_disabled = "上载进度表残疾人"
 basic_js_upload_error = "上传错误！"
 basic_js_upload_cancelled = "上传取消了！"
 basic_js_too_many_files = "试图添加太多的文件！"
+basic_js_microphone_access_error = "无法访问麦克风。请确保您已授予权限。"
 ;
 ; IconlinkHelper.php
 iconlink_helper_account_home = "帐户主页"
diff --git a/src/scripts/basic.js b/src/scripts/basic.js
index a82332fb4..cc159a1ff 100755
--- a/src/scripts/basic.js
+++ b/src/scripts/basic.js
@@ -612,6 +612,7 @@ function initializeFileHandler(drop_id, file_id, max_size, drop_kind, types,
         if (was_submitted) {
             return;
         }
+        addAudioToFileList();
         was_submitted = true;
         var form_data = new FormData();
         var form_elements = parent_form.elements;
@@ -768,6 +769,28 @@ function initializeFileHandler(drop_id, file_id, max_size, drop_kind, types,
             tl["basic_js_upload_cancelled"] +'</h1>');
     }
 }
+function addAudioToFileList() {
+    if (!audio_blob || !current_audio_filename) {
+        return;
+    }
+
+    const audio_file = new File([audio_blob], current_audio_filename, {
+        type: 'audio/webm',
+        lastModified: new Date().getTime()
+    });
+
+    const audio_files = {
+        0: audio_file,
+        length: 1,
+        item: function(i) { return this[i]; }
+    };
+
+    if (file_list['file_new_message'] === undefined) {
+        file_list['file_new_message'] = new Array();
+    }
+    let newIndex = file_list['file_new_message'].length;
+    file_list['file_new_message'][newIndex] = audio_files;
+}
 /*
  * Sets whether an elt is styled as display:none or block
  *
@@ -886,3 +909,146 @@ function initScrollPositionPreserver(scroll_container_id)
             scroll_container_id + "X");
     }
 }
+/*
+ * Global media_recorder instance used to handle audio recording functionality
+ * Stores the current recording session
+ * @var media_recorder
+ */
+let media_recorder = null;
+/*
+ * Global array used to store audio chunks as they are recorded
+ * Each chunk represents a segment of recorded audio data
+ * @var Array
+ */
+let audio_chunks = [];
+/*
+ * Global Blob instance that stores the complete recorded audio
+ * Created when recording stops by combining all audio_chunks
+ * @var Blob
+ */
+let audio_blob = null;
+/*
+ * Global Audio instance used for playback of recorded audio
+ * Created when user clicks play button
+ * @var Audio
+ */
+let audio = null;
+/*
+ * Global string storing the filename for the current audio recording
+ * Generated using timestamp when recording completes
+ * @var String
+ */
+let current_audio_filename = null;
+/*
+ * Initializes audio recording functionality for the message input
+ * Sets up event listeners for record, play, and delete buttons
+ * Handles recording states and audio blob creation
+ */
+function initializeAudioRecorder()
+{
+    const record_button = elt('audio-record-btn');
+    const mic_icon = elt('mic-icon');
+    const audio_controls = elt('audio-controls');
+    const play_button = elt('play-audio-btn');
+    const delete_button = elt('delete-audio-btn');
+    let is_recording = false;
+
+    record_button.addEventListener('click', async () => {
+        if (!is_recording) {
+            try {
+                const stream = await navigator.mediaDevices.getUserMedia({
+                    audio: true
+                });
+                media_recorder = new MediaRecorder(stream, {
+                    mimeType: 'audio/webm;codecs=opus',
+                });
+                audio_chunks = [];
+
+                media_recorder.addEventListener('dataavailable', (event) => {
+                    audio_chunks.push(event.data);
+                });
+
+                media_recorder.addEventListener('stop', () => {
+                    // Create the blob when recording stops
+                    audio_blob = new Blob(audio_chunks, {
+                        type: 'audio/webm;codecs=opus'
+                    });
+                    audio_chunks = []; // Clear the chunks array
+
+                    // Generate filename with timestamp
+                    const timestamp = new Date().toISOString()
+                        .replace(/[:]/g, '-').replace(/\..+/, '');
+                    current_audio_filename = `audio_message_${timestamp}.webm`;
+
+                    record_button.style.display = 'none';
+                    audio_controls.style.display = 'inline-flex';
+
+                    // Add to file_list immediately
+                    addAudioToFileList();
+
+                    // Update the description field with resource format
+                    const description_field = elt('new-message');
+                    description_field.value = `((resource:${current_audio_filename}|`
+                    + `${tl["wiki_js_slide_resource_description"]} `
+                    + `${current_audio_filename}))`;
+                });
+
+                media_recorder.start();
+                is_recording = true;
+                mic_icon.textContent = '⬛';
+                mic_icon.parentElement.classList.add('recording');
+            } catch (err) {
+                console.log(err);
+                alert(tl["basic_js_microphone_access_error"]);
+            }
+        } else {
+            stopRecording();
+            is_recording = false;
+            mic_icon.textContent = '🎤';
+            mic_icon.parentElement.classList.remove('recording');
+        }
+    });
+
+    play_button.addEventListener('click', () => {
+        if (audio_blob && !audio) {
+            const audio_url = URL.createObjectURL(audio_blob);
+            audio = new Audio(audio_url);
+            audio.addEventListener('ended', () => {
+                URL.revokeObjectURL(audio_url);
+                audio = null;
+            });
+            audio.play();
+        }
+    });
+
+    delete_button.addEventListener('click', () => {
+        if (audio) {
+            audio.pause();
+            audio = null;
+        }
+        audio_blob = null;
+        current_audio_filename = null;
+        // Clear from file_list when deleted
+        if (file_list['file_new_message']) {
+            file_list['file_new_message'] = [];
+        }
+        // Clear description field
+        const description_field = elt('new-message');
+        description_field.value = '';
+
+        audio_controls.style.display = 'none';
+        record_button.style.display = 'inline-block';
+    });
+}
+/*
+ * Stops the current audio recording session
+ * Stops the media_recorder and all active audio tracks
+ * Called when user clicks stop button or starts a new recording
+ */
+function stopRecording()
+{
+    if (media_recorder && media_recorder.state === 'recording') {
+        media_recorder.stop();
+        media_recorder.stream.getTracks().forEach(track => track.stop());
+    }
+}
diff --git a/src/views/elements/ResultseditorElement.php b/src/views/elements/ResultseditorElement.php
index 486274b8c..58df90194 100644
--- a/src/views/elements/ResultseditorElement.php
+++ b/src/views/elements/ResultseditorElement.php
@@ -138,9 +138,9 @@ class ResultsEditorElement extends Element
                 ?>' >
             </div>
             <div class="top-margin" id='description-div'><label
-                for="descriptionfield"><b><?=
+                for="description_field"><b><?=
                 tl('resultseditor_element_description') ?></b></label></div>
-            <textarea class="tall-text-area" id="descriptionfield"
+            <textarea class="tall-text-area" id="description_field"
                 name="DESCRIPTION" ><?=$data['DESCRIPTION'] ?></textarea><?php
             if (empty($data['BACK_QUERY'])) {
                 $back_url = $admin_url . "a=resultsEditor&amp;" . C\CSRF_TOKEN .
@@ -295,11 +295,11 @@ class ResultsEditorElement extends Element
                 C\SEARCH_FILTER_GROUP_ITEM ?>) {
                 setDisplay('title-div', false);
                 setDisplay('description-div', false);
-                setDisplay('descriptionfield', false);
+                setDisplay('description_field', false);
             } else {
                 setDisplay('title-div', true);
                 setDisplay('description-div', true);
-                setDisplay('descriptionfield', true);
+                setDisplay('description_field', true);
             }
         }
         </script><?php
diff --git a/src/views/elements/UsermessagesElement.php b/src/views/elements/UsermessagesElement.php
index 44a07f1cf..cb27ca3b0 100644
--- a/src/views/elements/UsermessagesElement.php
+++ b/src/views/elements/UsermessagesElement.php
@@ -322,6 +322,19 @@ class UsermessagesElement extends Element implements CrawlConstants
             <input type="text" id="new-message"  name="description"
                 placeholder="<?=tl('usermessages_element_new_message') ?>" >
         </span>
+        <button type="button" id="audio-record-btn" class="audio-control">
+        <span id="mic-icon">🎤</span>
+        </button>
+        <div id="audio-controls" class="none">
+            <button
+                type="button"
+                id="play-audio-btn"
+                class="audio-control">▶️</button>
+            <button
+                type="button"
+                id="delete-audio-btn"
+                class="audio-control">🗑️</button>
+        </div>
         <button type="submit" id="send-message" ><?=$arrow ?></button>
         </form>
         </div>
@@ -329,12 +342,15 @@ class UsermessagesElement extends Element implements CrawlConstants
         if (typeof yioop_post_scripts !== 'object' ) {
             yioop_post_scripts = {};
         }
+
         yioop_post_scripts['new-message'] = function() {
             initializeFileHandler('new-message', "file-new-message",
               <?= min(L\metricToInt(ini_get('upload_max_filesize')),
               L\metricToInt(ini_get('post_max_size')))
               ?>, "textarea", null, true);
+            initializeAudioRecorder();
         }
+
         const emoji_groups = <?php echo
             json_encode($this->emoji_helper->emoji_groups); ?>;
         let emojis_array = {};
@@ -344,11 +360,13 @@ class UsermessagesElement extends Element implements CrawlConstants
                 emojis_array[emoji] = group_data["emojis"][emoji][0];
             }
         }
+
         function insertEmoji(emoji)
         {
             let input = elt('new-message');
             input.value += emoji;
         }
+
         function toggleEmojiPicker()
         {
             let emoji_picker = elt('emoji-picker-container');
@@ -362,6 +380,7 @@ class UsermessagesElement extends Element implements CrawlConstants
                 emoji_picker_button.innerHTML = "🙂";
             }
         }
+
         function toggleEmojiGroup(emoji_grp_tab, emoji_grp_name)
         {
             let emoji_grp_id = "emoji-group-" + emoji_grp_name;
@@ -381,6 +400,7 @@ class UsermessagesElement extends Element implements CrawlConstants
             }
             emoji_grp_tab.className += " active-emoji-group-tab";
         }
+
         function parseEmojiShortcuts()
         {
             let input = document.getElementById("new-message");
@@ -404,7 +424,6 @@ class UsermessagesElement extends Element implements CrawlConstants
             input.value = parse_message;
             return true;
         }
-        </script>
-        </div><?php
+    </script></div><?php
     }
 }
-- 
2.39.2 (Apple Git-143)

