@@ -371,6 +477,7 @@
let silenceTimer = null;
let searchSilenceTimer = null;
let pendingNoteAttachments = [];
+ let pendingConversationAttachments = [];
// --- Voice Recognition Setup with auto-stop ---
function setupTitleVoiceRecognition() {
@@ -1269,6 +1376,33 @@
listEl.appendChild(frag);
}
+ function renderConversationAttachmentsList() {
+ const listEl = document.getElementById('conversationAttachmentsList');
+ const summaryEl = document.getElementById('conversationAttachmentsSummary');
+ listEl.innerHTML = '';
+ if (!pendingConversationAttachments.length) {
+ summaryEl.textContent = 'No attachments selected.';
+ return;
+ }
+ summaryEl.textContent = `${pendingConversationAttachments.length} file(s) selected`;
+ const frag = document.createDocumentFragment();
+ pendingConversationAttachments.forEach((file) => {
+ const row = document.createElement('div');
+ row.className = 'flex items-center gap-2 text-sm text-gray-800';
+ const kind = attachmentKindFromName(file.name);
+ const badge = document.createElement('span');
+ badge.className = 'px-2 py-0.5 rounded-full text-xs border border-gray-200 text-gray-700 bg-gray-50';
+ badge.textContent = kind;
+ const label = document.createElement('span');
+ const size = formatBytes(file.size);
+ label.textContent = size ? `${file.name} (${size})` : file.name;
+ row.appendChild(badge);
+ row.appendChild(label);
+ frag.appendChild(row);
+ });
+ listEl.appendChild(frag);
+ }
+
function resetNoteForm() {
document.getElementById('noteTitle').value = '';
document.getElementById('noteText').value = '';
@@ -1292,6 +1426,12 @@
document.getElementById('conversationJobNumberContainer').classList.add('hidden');
document.getElementById('conversationAudioPreview').src = '';
document.getElementById('conversationAudioPlayer').classList.add('hidden');
+ pendingConversationAttachments = [];
+ document.getElementById('conversationImageInput').value = '';
+ document.getElementById('conversationVideoInput').value = '';
+ document.getElementById('conversationFileInput').value = '';
+ document.getElementById('conversationCameraInput').value = '';
+ renderConversationAttachmentsList();
}
function renderNotesList(items) {
@@ -1592,18 +1732,236 @@
showNoteForm();
});
- document.getElementById('noteAttachmentsInput').addEventListener('change', (e) => {
- pendingNoteAttachments = Array.from(e.target.files || []);
- renderNoteAttachmentsList();
+ // Attachment menu handling
+ const attachmentMenuBtn = document.getElementById('attachmentMenuBtn');
+ const attachmentMenu = document.getElementById('attachmentMenu');
+ const imageInput = document.getElementById('imageInput');
+ const videoInput = document.getElementById('videoInput');
+ const fileInput = document.getElementById('fileInput');
+ const cameraInput = document.getElementById('cameraInput');
+
+ attachmentMenuBtn.addEventListener('click', (e) => {
+ e.preventDefault();
+ attachmentMenu.classList.toggle('hidden');
+ });
+
+ document.getElementById('attachImageBtn').addEventListener('click', () => {
+ attachmentMenu.classList.add('hidden');
+ imageInput.click();
+ });
+
+ document.getElementById('attachVideoBtn').addEventListener('click', () => {
+ attachmentMenu.classList.add('hidden');
+ videoInput.click();
+ });
+
+ document.getElementById('attachFileBtn').addEventListener('click', () => {
+ attachmentMenu.classList.add('hidden');
+ fileInput.click();
+ });
+
+ document.getElementById('takePictureBtn').addEventListener('click', () => {
+ attachmentMenu.classList.add('hidden');
+ cameraInput.click();
+ });
+
+ document.getElementById('recordVideoBtn').addEventListener('click', async () => {
+ attachmentMenu.classList.add('hidden');
+ try {
+ const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
+ const videoRecorder = new MediaRecorder(stream);
+ const chunks = [];
+
+ videoRecorder.ondataavailable = (e) => {
+ if (e.data.size > 0) chunks.push(e.data);
+ };
+
+ videoRecorder.onstop = () => {
+ const blob = new Blob(chunks, { type: 'video/webm' });
+ const file = new File([blob], `video-${Date.now()}.webm`, { type: 'video/webm' });
+ pendingNoteAttachments.push(file);
+ renderNoteAttachmentsList();
+ stream.getTracks().forEach(track => track.stop());
+ };
+
+ videoRecorder.start();
+
+ // Show recording UI
+ const recordStatus = document.getElementById('recordStatus');
+ const originalStatus = recordStatus.textContent;
+ recordStatus.textContent = 'Recording video... (Click to stop)';
+ recordStatus.style.cursor = 'pointer';
+ recordStatus.style.color = '#dc2626';
+
+ const stopHandler = () => {
+ recordStatus.removeEventListener('click', stopHandler);
+ videoRecorder.stop();
+ recordStatus.textContent = originalStatus;
+ recordStatus.style.cursor = 'auto';
+ recordStatus.style.color = '';
+ };
+
+ recordStatus.addEventListener('click', stopHandler);
+
+ // Auto-stop after 5 minutes
+ setTimeout(() => {
+ if (videoRecorder.state === 'recording') {
+ recordStatus.removeEventListener('click', stopHandler);
+ videoRecorder.stop();
+ recordStatus.textContent = 'Video recording stopped (5 minute limit)';
+ recordStatus.style.cursor = 'auto';
+ setTimeout(() => {
+ recordStatus.textContent = originalStatus;
+ recordStatus.style.color = '';
+ }, 3000);
+ }
+ }, 5 * 60 * 1000);
+ } catch (err) {
+ console.error('Failed to access camera/microphone:', err);
+ alert('Unable to access camera or microphone. Check permissions.');
+ }
+ });
+
+ // File input change handlers
+ [imageInput, videoInput, fileInput, cameraInput].forEach(input => {
+ input.addEventListener('change', (e) => {
+ pendingNoteAttachments.push(...Array.from(e.target.files || []));
+ renderNoteAttachmentsList();
+ });
});
document.getElementById('clearAttachmentsBtn').addEventListener('click', (e) => {
e.preventDefault();
pendingNoteAttachments = [];
- document.getElementById('noteAttachmentsInput').value = '';
+ imageInput.value = '';
+ videoInput.value = '';
+ fileInput.value = '';
+ cameraInput.value = '';
renderNoteAttachmentsList();
});
+ // Close menu on outside click
+ document.addEventListener('click', (e) => {
+ if (!attachmentMenuBtn.contains(e.target) && !attachmentMenu.contains(e.target)) {
+ attachmentMenu.classList.add('hidden');
+ }
+ });
+
+ // Conversation attachment menu setup
+ const conversationAttachmentMenuBtn = document.getElementById('conversationAttachmentMenuBtn');
+ const conversationAttachmentMenu = document.getElementById('conversationAttachmentMenu');
+ const conversationImageInput = document.getElementById('conversationImageInput');
+ const conversationVideoInput = document.getElementById('conversationVideoInput');
+ const conversationFileInput = document.getElementById('conversationFileInput');
+ const conversationCameraInput = document.getElementById('conversationCameraInput');
+
+ conversationAttachmentMenuBtn.addEventListener('click', (e) => {
+ e.preventDefault();
+ conversationAttachmentMenu.classList.toggle('hidden');
+ });
+
+ document.getElementById('conversationAttachImageBtn').addEventListener('click', () => {
+ conversationAttachmentMenu.classList.add('hidden');
+ conversationImageInput.click();
+ });
+
+ document.getElementById('conversationAttachVideoBtn').addEventListener('click', () => {
+ conversationAttachmentMenu.classList.add('hidden');
+ conversationVideoInput.click();
+ });
+
+ document.getElementById('conversationAttachFileBtn').addEventListener('click', () => {
+ conversationAttachmentMenu.classList.add('hidden');
+ conversationFileInput.click();
+ });
+
+ document.getElementById('conversationTakePictureBtn').addEventListener('click', () => {
+ conversationAttachmentMenu.classList.add('hidden');
+ conversationCameraInput.click();
+ });
+
+ document.getElementById('conversationRecordVideoBtn').addEventListener('click', async () => {
+ conversationAttachmentMenu.classList.add('hidden');
+ try {
+ const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
+ const videoRecorder = new MediaRecorder(stream);
+ const chunks = [];
+
+ videoRecorder.ondataavailable = (e) => {
+ if (e.data.size > 0) chunks.push(e.data);
+ };
+
+ videoRecorder.onstop = () => {
+ const blob = new Blob(chunks, { type: 'video/webm' });
+ const file = new File([blob], `video-${Date.now()}.webm`, { type: 'video/webm' });
+ pendingConversationAttachments.push(file);
+ renderConversationAttachmentsList();
+ stream.getTracks().forEach(track => track.stop());
+ };
+
+ videoRecorder.start();
+
+ // Show recording UI
+ const recordStatus = document.getElementById('conversationRecordStatus');
+ const originalStatus = recordStatus.textContent;
+ recordStatus.textContent = 'Recording video... (Click to stop)';
+ recordStatus.style.cursor = 'pointer';
+ recordStatus.style.color = '#dc2626';
+
+ const stopHandler = () => {
+ recordStatus.removeEventListener('click', stopHandler);
+ videoRecorder.stop();
+ recordStatus.textContent = originalStatus;
+ recordStatus.style.cursor = 'auto';
+ recordStatus.style.color = '';
+ };
+
+ recordStatus.addEventListener('click', stopHandler);
+
+ // Auto-stop after 5 minutes
+ setTimeout(() => {
+ if (videoRecorder.state === 'recording') {
+ recordStatus.removeEventListener('click', stopHandler);
+ videoRecorder.stop();
+ recordStatus.textContent = 'Video recording stopped (5 minute limit)';
+ recordStatus.style.cursor = 'auto';
+ setTimeout(() => {
+ recordStatus.textContent = originalStatus;
+ recordStatus.style.color = '';
+ }, 3000);
+ }
+ }, 5 * 60 * 1000);
+ } catch (err) {
+ console.error('Failed to access camera/microphone:', err);
+ alert('Unable to access camera or microphone. Check permissions.');
+ }
+ });
+
+ // File input change handlers for conversation
+ [conversationImageInput, conversationVideoInput, conversationFileInput, conversationCameraInput].forEach(input => {
+ input.addEventListener('change', (e) => {
+ pendingConversationAttachments.push(...Array.from(e.target.files || []));
+ renderConversationAttachmentsList();
+ });
+ });
+
+ document.getElementById('conversationClearAttachmentsBtn').addEventListener('click', (e) => {
+ e.preventDefault();
+ pendingConversationAttachments = [];
+ conversationImageInput.value = '';
+ conversationVideoInput.value = '';
+ conversationFileInput.value = '';
+ conversationCameraInput.value = '';
+ renderConversationAttachmentsList();
+ });
+
+ // Close conversation menu on outside click
+ document.addEventListener('click', (e) => {
+ if (!conversationAttachmentMenuBtn.contains(e.target) && !conversationAttachmentMenu.contains(e.target)) {
+ conversationAttachmentMenu.classList.add('hidden');
+ }
+ });
+
// Search input + voice
const notesSearchInput = document.getElementById('notesSearch');
const searchVoiceBtn = document.getElementById('searchVoiceBtn');
@@ -1758,9 +2116,7 @@
const createdAtt = await pb.collection(ATTACHMENTS_COLLECTION).create(fd);
attachmentRecords.push(createdAtt);
}
- const inputEl = document.getElementById('noteAttachmentsInput');
- const filesFromInput = Array.from(inputEl?.files || []);
- const filesToUpload = (pendingNoteAttachments && pendingNoteAttachments.length) ? pendingNoteAttachments : filesFromInput;
+ const filesToUpload = (pendingNoteAttachments && pendingNoteAttachments.length) ? pendingNoteAttachments : [];
if (filesToUpload && filesToUpload.length) {
const fdFiles = new FormData();
for (let i = 0; i < filesToUpload.length; i++) {
@@ -1784,7 +2140,10 @@
statusEl.textContent = `Saved note ${noteRecord.id}${attachmentMsg}`;
await clearAudio();
pendingNoteAttachments = [];
- document.getElementById('noteAttachmentsInput').value = '';
+ document.getElementById('imageInput').value = '';
+ document.getElementById('videoInput').value = '';
+ document.getElementById('fileInput').value = '';
+ document.getElementById('cameraInput').value = '';
renderNoteAttachmentsList();
document.getElementById('noteText').value = '';
document.getElementById('noteTitle').value = '';
diff --git a/logs/SESSION_LOG.txt b/logs/SESSION_LOG.txt
index 530087c..54b0000 100644
--- a/logs/SESSION_LOG.txt
+++ b/logs/SESSION_LOG.txt
@@ -3,6 +3,16 @@ PRISM NOTES - SESSION LOG
Project history, decisions, and architectural milestones
================================================================================
+[2026-01-10 21:45] MONICA SESSION: Mobile Layout & Scrolling Fixes
+- Changes Applied:
+ * Kept button group beside Notes heading on all screen sizes (flex-row always)
+ * Removed graph status badges from form containers
+ * Added overflow-x-hidden to Notes and Conversations card containers
+ * Prevents horizontal scrolling/swiping that made cards feel "floating"
+ * Allows vertical scroll to remain responsive
+- Result: Cards now stay stationary horizontally, only scroll up/down normally
+- Status: Fixed and committed (4bb4e54 and 0dc0ffb)
+
[2026-01-10 21:15] MONICA SESSION: Refined Notes List Header UI
- Changes Applied:
* Removed graph status badges from note/conversation form containers