Add hidden notes feature, note filters, detail view edit/delete, and dynamic styling
- Hidden notes feature: hide/unhide notes with dedicated view, matching main list styling - Filter buttons: All Notes, My Shared, Shared With Me, Job Notes, Manager Notes, Personal Notes - Note detail view: dynamic background colors matching note card styling - Edit functionality: double-click title/body to edit, inline save/cancel - Soft delete: delete button with confirmation, sets delete=true in database - Icon updates: magnifying glass for hide/unhide actions (14px) - Session log: documented all feature changes
This commit is contained in:
+454
-11
@@ -412,10 +412,19 @@
|
||||
</svg>
|
||||
</button>
|
||||
<button id="newNoteBtn" class="h-6 px-2 sm:px-3 py-0 text-xs leading-none rounded bg-secondary text-white font-semibold flex items-center justify-center">New Note</button>
|
||||
<button id="hiddenNotesBtn" class="h-6 px-2 sm:px-3 py-0 text-xs leading-none rounded bg-gray-600 text-white font-semibold flex items-center justify-center">Hidden Notes</button>
|
||||
<button id="conversationsBtn" class="h-6 px-2 sm:px-3 py-0 text-xs leading-none rounded bg-secondary text-white font-semibold flex items-center justify-center">Conversations</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full mt-3">
|
||||
<div class="flex items-center gap-2 mb-3 flex-wrap">
|
||||
<button id="filterAllBtn" class="px-3 py-1 text-xs rounded border border-gray-300 bg-white text-gray-700 hover:bg-gray-100 transition font-medium">All Notes</button>
|
||||
<button id="filterMySharedBtn" class="px-3 py-1 text-xs rounded border border-gray-300 bg-white text-gray-700 hover:bg-gray-100 transition font-medium">My Shared</button>
|
||||
<button id="filterSharedWithMeBtn" class="px-3 py-1 text-xs rounded border border-gray-300 bg-white text-gray-700 hover:bg-gray-100 transition font-medium">Shared With Me</button>
|
||||
<button id="filterJobBtn" class="px-3 py-1 text-xs rounded border border-gray-300 bg-white text-gray-700 hover:bg-gray-100 transition font-medium">Job Notes</button>
|
||||
<button id="filterManagerBtn" class="px-3 py-1 text-xs rounded border border-gray-300 bg-white text-gray-700 hover:bg-gray-100 transition font-medium">Manager Notes</button>
|
||||
<button id="filterPersonalBtn" class="px-3 py-1 text-xs rounded border border-gray-300 bg-white text-gray-700 hover:bg-gray-100 transition font-medium">Personal Notes</button>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 border border-gray-200 rounded-lg px-3 py-2 bg-gray-50">
|
||||
<input id="notesSearch" type="text" class="flex-1 bg-transparent outline-none text-sm text-gray-800" placeholder="Search notes..." />
|
||||
<button id="searchVoiceBtn" class="text-purple-600 hover:text-purple-700" title="Voice search">
|
||||
@@ -442,11 +451,18 @@
|
||||
</div>
|
||||
|
||||
<!-- Note Detail Container -->
|
||||
<div id="noteDetailContainer" class="hidden flex flex-col bg-white rounded-2xl shadow-2xl max-w-4xl w-full p-6 gap-3 overflow-hidden mt-2.5 max-h-[calc(100vh-140px)]">
|
||||
<div id="noteDetailContainer" class="hidden flex flex-col bg-white rounded-2xl shadow-2xl border border-gray-300 max-w-4xl w-full p-6 gap-3 overflow-hidden mt-2.5 max-h-[calc(100vh-140px)]">
|
||||
<div class="flex-shrink-0 flex items-center justify-between">
|
||||
<div>
|
||||
<div class="flex-1">
|
||||
<div class="text-xs text-gray-500" id="noteDetailMeta"></div>
|
||||
<h2 id="noteDetailTitle" class="text-2xl font-bold text-gray-900">Note</h2>
|
||||
<h2 id="noteDetailTitle" class="text-2xl font-bold text-gray-900 cursor-pointer hover:text-gray-600 transition" title="Double-click to edit">Note</h2>
|
||||
<div id="noteDetailTitleEdit" class="hidden">
|
||||
<input type="text" id="noteDetailTitleInput" class="w-full text-2xl font-bold border border-gray-300 rounded px-2 py-1 focus:outline-none focus:ring-2 focus:ring-primary" />
|
||||
<div class="flex gap-2 mt-2">
|
||||
<button id="noteDetailTitleSave" class="px-3 py-1 rounded bg-green-600 text-white text-sm font-medium hover:bg-green-700">Save</button>
|
||||
<button id="noteDetailTitleCancel" class="px-3 py-1 rounded bg-gray-400 text-white text-sm font-medium hover:bg-gray-500">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button id="detailBackBtn" class="px-3 py-2 rounded border border-gray-200 text-gray-700 hover:bg-gray-100">Back to Notes</button>
|
||||
@@ -454,7 +470,14 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="min-h-0 overflow-y-auto">
|
||||
<div class="text-gray-800 leading-relaxed whitespace-pre-wrap border border-gray-100 rounded-lg p-3 bg-gray-50 mb-3" id="noteDetailBody"></div>
|
||||
<div id="noteDetailBodyDisplay" class="text-gray-800 leading-relaxed whitespace-pre-wrap border border-gray-100 rounded-lg p-3 bg-gray-50 mb-3 cursor-pointer hover:bg-gray-100 transition" title="Double-click to edit"></div>
|
||||
<div id="noteDetailBodyEdit" class="hidden">
|
||||
<textarea id="noteDetailBodyInput" class="w-full h-32 border border-gray-300 rounded-lg p-3 focus:outline-none focus:ring-2 focus:ring-primary text-gray-800 leading-relaxed"></textarea>
|
||||
<div class="flex gap-2 mt-2">
|
||||
<button id="noteDetailBodySave" class="px-3 py-1 rounded bg-green-600 text-white text-sm font-medium hover:bg-green-700">Save</button>
|
||||
<button id="noteDetailBodyCancel" class="px-3 py-1 rounded bg-gray-400 text-white text-sm font-medium hover:bg-gray-500">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<div class="text-sm text-gray-600">Audio</div>
|
||||
<audio id="noteDetailAudio" controls class="hidden w-full"></audio>
|
||||
@@ -466,6 +489,9 @@
|
||||
<div id="noteDetailAttachmentsStatus" class="text-xs text-gray-500"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-shrink-0 flex justify-end pt-2 border-t border-gray-200">
|
||||
<button id="noteDetailDeleteBtn" class="px-4 py-2 rounded bg-red-600 text-white text-sm font-medium hover:bg-red-700 transition">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Note + Audio Container -->
|
||||
@@ -664,6 +690,25 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hidden Notes Container -->
|
||||
<div id="hiddenNotesContainer" class="hidden flex flex-col bg-gray-200 rounded-2xl shadow-2xl border border-gray-400 max-w-5xl w-full p-6 gap-4 max-h-[calc(100vh-136px)] overflow-hidden mb-3" style="transform: translateY(-24px);">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="flex flex-row items-center justify-between gap-3">
|
||||
<div>
|
||||
<h2 class="text-lg sm:text-2xl font-bold text-gray-800">Hidden Notes</h2>
|
||||
</div>
|
||||
<div class="flex gap-2 flex-wrap">
|
||||
<button id="backToNotesFromHiddenBtn" class="px-2 sm:px-3 py-1 sm:py-2 text-xs sm:text-sm rounded border border-gray-200 text-gray-700 hover:bg-gray-100">Back to Notes</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-1 overflow-x-hidden overflow-y-auto">
|
||||
<div id="hiddenNotesStatus" class="text-sm text-gray-600"></div>
|
||||
<div id="hiddenNotesEmpty" class="hidden text-sm text-gray-500">No hidden notes.</div>
|
||||
<div id="hiddenNotesGrid" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2 w-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Conversations List Container -->
|
||||
<div id="conversationsContainer" class="hidden flex flex-col bg-purple-100 rounded-2xl shadow-2xl border border-purple-200 max-w-5xl w-full p-6 gap-4 flex-1 overflow-hidden mt-2.5 mb-3">
|
||||
<div class="flex-shrink-0">
|
||||
@@ -1797,6 +1842,7 @@
|
||||
let notesCache = {};
|
||||
let notesList = [];
|
||||
let searchQuery = '';
|
||||
let currentFilter = 'all'; // 'all', 'myshared', 'sharedwithme', 'job', 'manager', 'personal'
|
||||
let searchRecognition = null;
|
||||
let isSearchRecognizing = false;
|
||||
|
||||
@@ -1822,6 +1868,169 @@
|
||||
document.getElementById('noteContainer').classList.add('hidden');
|
||||
document.getElementById('conversationsContainer').classList.add('hidden');
|
||||
document.getElementById('conversationContainer').classList.add('hidden');
|
||||
document.getElementById('hiddenNotesContainer').classList.add('hidden');
|
||||
}
|
||||
|
||||
function showHiddenNotes() {
|
||||
document.getElementById('notesListContainer').classList.add('hidden');
|
||||
document.getElementById('noteDetailContainer').classList.add('hidden');
|
||||
document.getElementById('noteContainer').classList.add('hidden');
|
||||
document.getElementById('conversationsContainer').classList.add('hidden');
|
||||
document.getElementById('conversationContainer').classList.add('hidden');
|
||||
document.getElementById('hiddenNotesContainer').classList.remove('hidden');
|
||||
renderHiddenNotes();
|
||||
}
|
||||
|
||||
function renderHiddenNotes() {
|
||||
// Color palette for shared with person names (different from green)
|
||||
const personColorPalette = [
|
||||
{ bg: 'bg-blue-200', border: 'border-blue-400', text: 'text-blue-900' },
|
||||
{ bg: 'bg-purple-200', border: 'border-purple-400', text: 'text-purple-900' },
|
||||
{ bg: 'bg-indigo-200', border: 'border-indigo-400', text: 'text-indigo-900' },
|
||||
{ bg: 'bg-cyan-200', border: 'border-cyan-400', text: 'text-cyan-900' },
|
||||
{ bg: 'bg-teal-200', border: 'border-teal-400', text: 'text-teal-900' },
|
||||
{ bg: 'bg-sky-200', border: 'border-sky-400', text: 'text-sky-900' },
|
||||
{ bg: 'bg-lime-200', border: 'border-lime-400', text: 'text-lime-900' },
|
||||
{ bg: 'bg-rose-200', border: 'border-rose-400', text: 'text-rose-900' },
|
||||
];
|
||||
|
||||
// Map to store consistent colors for each person
|
||||
const personColorMap = {};
|
||||
|
||||
function getPersonColor(name) {
|
||||
if (!personColorMap[name]) {
|
||||
// Generate consistent hash-based color index for this person
|
||||
let hash = 0;
|
||||
for (let i = 0; i < name.length; i++) {
|
||||
hash = ((hash << 5) - hash) + name.charCodeAt(i);
|
||||
hash = hash & hash; // Convert to 32-bit integer
|
||||
}
|
||||
const colorIndex = Math.abs(hash) % personColorPalette.length;
|
||||
personColorMap[name] = personColorPalette[colorIndex];
|
||||
}
|
||||
return personColorMap[name];
|
||||
}
|
||||
|
||||
const hiddenNotes = notesList.filter(n => n.hidden);
|
||||
const grid = document.getElementById('hiddenNotesGrid');
|
||||
const empty = document.getElementById('hiddenNotesEmpty');
|
||||
const status = document.getElementById('hiddenNotesStatus');
|
||||
|
||||
grid.innerHTML = '';
|
||||
status.textContent = '';
|
||||
|
||||
if (!hiddenNotes.length) {
|
||||
empty.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
empty.classList.add('hidden');
|
||||
|
||||
const frag = document.createDocumentFragment();
|
||||
hiddenNotes.forEach((note) => {
|
||||
const card = document.createElement('button');
|
||||
card.type = 'button';
|
||||
card.dataset.noteId = note.id;
|
||||
const type = (note.type || 'personal').toLowerCase();
|
||||
|
||||
// Determine styling based on note sharing status
|
||||
let typeClasses;
|
||||
if (note._sharedWithMe) {
|
||||
// Light blue for notes shared WITH the current user
|
||||
typeClasses = 'bg-blue-100 border border-blue-300 hover:border-blue-400';
|
||||
} else if (note.shared) {
|
||||
// Green for notes created by current user and shared with others
|
||||
typeClasses = 'bg-green-100 border border-green-300 hover:border-green-400';
|
||||
} else {
|
||||
// Type-based colors for non-shared notes
|
||||
typeClasses = {
|
||||
personal: 'bg-amber-100 border border-amber-300 hover:border-amber-400',
|
||||
manager: 'bg-orange-200 border border-orange-400 hover:border-orange-500',
|
||||
job: 'bg-pink-200 border border-pink-400 hover:border-pink-500',
|
||||
}[type] || 'bg-gray-100 border border-gray-300 hover:border-primary/60';
|
||||
}
|
||||
|
||||
card.className = `text-left ${typeClasses} hover:shadow-md transition rounded-xl px-2 py-0.5 space-y-0 h-full flex flex-col`;
|
||||
const title = note.title || 'Untitled';
|
||||
const plain = note[NOTE_BODY_PLAIN_FIELD] || (note[NOTE_BODY_HTML_FIELD] ? htmlToPlainText(note[NOTE_BODY_HTML_FIELD]) : '');
|
||||
const snippet = (plain || '').slice(0, 160) + ((plain && plain.length > 160) ? '…' : '');
|
||||
const meta = `${note.type || 'personal'} • ${note.job_note ? 'Job' : 'General'}${note.Job_Number ? ' • #' + note.Job_Number : ''}`;
|
||||
const atts = note._attachments || [];
|
||||
const typeSet = buildKindSetFromAttachments(atts);
|
||||
function iconBtnHtml(kind) {
|
||||
const map = {
|
||||
image: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>',
|
||||
video: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="23 7 16 12 23 17 23 7"/><rect x="1" y="5" width="15" height="14" rx="2" ry="2"/></svg>',
|
||||
audio: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/><path d="M15.54 3.89a9 9 0 0 1 0 12.22M18.5 8.5a5 5 0 0 1 0 7"/></svg>',
|
||||
file: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"/><polyline points="13 2 13 9 20 9"/></svg>',
|
||||
};
|
||||
return `<button type="button" class="p-0.5 sm:p-1 rounded hover:bg-gray-200 text-gray-700 transition flex-shrink-0" data-attachment-button data-attachment-kind="${kind}" title="${kind}" style="width:24px;height:24px;min-width:24px;display:flex;align-items:center;justify-content:center;">${map[kind] || ''}</button>`;
|
||||
}
|
||||
const attachmentIcons = ['image', 'video', 'file', 'audio']
|
||||
.filter((k) => typeSet.has(k))
|
||||
.map(iconBtnHtml)
|
||||
.join('');
|
||||
|
||||
// Create type bubble and shared with capsules for shared notes only
|
||||
let typeBubble = '';
|
||||
let sharedWithDisplay = '';
|
||||
if (note.shared) {
|
||||
const typeColorMap = {
|
||||
personal: { bg: 'bg-amber-300', border: 'border-amber-400' },
|
||||
manager: { bg: 'bg-orange-400', border: 'border-orange-500' },
|
||||
job: { bg: 'bg-pink-400', border: 'border-pink-500' },
|
||||
};
|
||||
const typeColor = typeColorMap[type] || { bg: 'bg-gray-300', border: 'border-gray-400' };
|
||||
typeBubble = `<span class="inline-block w-3 h-3 rounded-full ${typeColor.bg} ${typeColor.border} border flex-shrink-0" title="${escapeHtml(type)}"></span>`;
|
||||
|
||||
if (note.shared_with && note.shared_with.length > 0) {
|
||||
const sharedNames = Array.isArray(note.shared_with) ? note.shared_with : (typeof note.shared_with === 'string' ? note.shared_with.split(',').map(n => n.trim()) : []);
|
||||
const capsules = sharedNames.map(name => {
|
||||
const firstName = name.split(' ')[0];
|
||||
const colors = getPersonColor(name);
|
||||
return `<span class="inline-block px-2 py-0.5 rounded-full text-xs font-semibold ${colors.bg} ${colors.border} border ${colors.text}">${escapeHtml(firstName)}</span>`;
|
||||
}).join('');
|
||||
sharedWithDisplay = `<div class="flex items-center flex-wrap gap-1 justify-end">${capsules}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<div class="font-semibold text-gray-900 truncate">${escapeHtml(title)}</div>
|
||||
<div class="flex items-center gap-1 flex-shrink-0">
|
||||
<div class="text-xs text-gray-500">${formatDateShort(note.created)}</div>
|
||||
<button type="button" class="unhide-note-btn text-gray-500 hover:text-green-600 transition flex-shrink-0" title="Unhide note" style="font-size: 14px; padding: 0; width: 20px; height: 20px;">🔍</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-start gap-2 flex-1">
|
||||
<div class="text-sm text-gray-700 line-clamp-1 flex-1">${escapeHtml(snippet || '(empty)')}</div>
|
||||
${note.shared ? `<div class="flex items-center gap-1 flex-shrink-0">${typeBubble}</div>` : ''}
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-xs text-gray-600 w-full">
|
||||
<div class="flex items-center gap-1">
|
||||
${attachmentIcons}
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
${note.shared ? sharedWithDisplay : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Add unhide button event listener
|
||||
card.querySelector('.unhide-note-btn')?.addEventListener('click', async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
try {
|
||||
await pb.collection(NOTES_COLLECTION).update(note.id, { hidden: false });
|
||||
loadNotesList();
|
||||
showHiddenNotes();
|
||||
} catch (err) {
|
||||
console.error('Error unhiding note:', err);
|
||||
}
|
||||
});
|
||||
|
||||
frag.appendChild(card);
|
||||
});
|
||||
grid.appendChild(frag);
|
||||
}
|
||||
|
||||
function showNoteForm() {
|
||||
@@ -1830,6 +2039,7 @@
|
||||
document.getElementById('noteContainer').classList.remove('hidden');
|
||||
document.getElementById('conversationsContainer').classList.add('hidden');
|
||||
document.getElementById('conversationContainer').classList.add('hidden');
|
||||
document.getElementById('hiddenNotesContainer').classList.add('hidden');
|
||||
// Initialize email lookup for sharing
|
||||
if (window.setupUserEmailLookup) {
|
||||
window.setupUserEmailLookup(pb, '#selectedUsersContainer');
|
||||
@@ -1988,11 +2198,16 @@
|
||||
card.dataset.noteId = note.id;
|
||||
const type = (note.type || 'personal').toLowerCase();
|
||||
|
||||
// Use green background for shared notes, otherwise use type-based colors
|
||||
// Determine styling based on note sharing status
|
||||
let typeClasses;
|
||||
if (note.shared) {
|
||||
if (note._sharedWithMe) {
|
||||
// Light blue for notes shared WITH the current user
|
||||
typeClasses = 'bg-blue-100 border border-blue-300 hover:border-blue-400';
|
||||
} else if (note.shared) {
|
||||
// Green for notes created by current user and shared with others
|
||||
typeClasses = 'bg-green-100 border border-green-300 hover:border-green-400';
|
||||
} else {
|
||||
// Type-based colors for non-shared notes
|
||||
typeClasses = {
|
||||
personal: 'bg-amber-100 border border-amber-300 hover:border-amber-400',
|
||||
manager: 'bg-orange-200 border border-orange-400 hover:border-orange-500',
|
||||
@@ -2047,7 +2262,10 @@
|
||||
card.innerHTML = `
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<div class="font-semibold text-gray-900 truncate">${escapeHtml(title)}</div>
|
||||
<div class="text-xs text-gray-500 flex-shrink-0">${formatDateShort(note.created)}</div>
|
||||
<div class="flex items-center gap-1 flex-shrink-0">
|
||||
<div class="text-xs text-gray-500">${formatDateShort(note.created)}</div>
|
||||
<button type="button" class="hide-note-btn text-gray-500 hover:text-orange-600 transition flex-shrink-0" title="Hide note" style="font-size: 14px; padding: 0; width: 20px; height: 20px;">🔍</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-start gap-2 flex-1">
|
||||
<div class="text-sm text-gray-700 line-clamp-1 flex-1">${escapeHtml(snippet || '(empty)')}</div>
|
||||
@@ -2062,6 +2280,19 @@
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Add hide button event listener
|
||||
card.querySelector('.hide-note-btn')?.addEventListener('click', async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
try {
|
||||
await pb.collection(NOTES_COLLECTION).update(note.id, { hidden: true });
|
||||
loadNotesList();
|
||||
} catch (err) {
|
||||
console.error('Error hiding note:', err);
|
||||
}
|
||||
});
|
||||
|
||||
frag.appendChild(card);
|
||||
});
|
||||
grid.appendChild(frag);
|
||||
@@ -2074,6 +2305,33 @@
|
||||
try {
|
||||
const res = await pb.collection(NOTES_COLLECTION).getList(1, 50, { sort: '-created' });
|
||||
const items = res?.items || [];
|
||||
|
||||
// Add flag for notes shared WITH current user
|
||||
for (const note of items) {
|
||||
if (note.shared && note.shared_with) {
|
||||
const sharedWithNames = Array.isArray(note.shared_with)
|
||||
? note.shared_with
|
||||
: (typeof note.shared_with === 'string' ? note.shared_with.split(',').map(n => n.trim()) : []);
|
||||
|
||||
// Check if current user is in shared_with
|
||||
let currentUserIsRecipient = false;
|
||||
for (const sharedName of sharedWithNames) {
|
||||
const firstName = sharedName.split(' ')[0];
|
||||
try {
|
||||
const records = await pb.collection('Associations').getList(1, 50, {
|
||||
filter: `first_name = "${firstName}"`,
|
||||
});
|
||||
if (records.items.find(r => r.emailtext === userEmailCache)) {
|
||||
currentUserIsRecipient = true;
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error checking shared_with match:', e);
|
||||
}
|
||||
}
|
||||
note._sharedWithMe = currentUserIsRecipient;
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch all attachments per note so we can display accurate indicators
|
||||
const attachmentsByNote = {};
|
||||
@@ -2107,11 +2365,34 @@
|
||||
|
||||
function applySearch() {
|
||||
const q = (searchQuery || '').toLowerCase();
|
||||
// Filter out hidden notes from the main list
|
||||
let visibleNotes = notesList.filter(n => !n.hidden);
|
||||
|
||||
// Apply type/category filter
|
||||
switch (currentFilter) {
|
||||
case 'myshared':
|
||||
visibleNotes = visibleNotes.filter(n => n.shared);
|
||||
break;
|
||||
case 'sharedwithme':
|
||||
visibleNotes = visibleNotes.filter(n => n._sharedWithMe);
|
||||
break;
|
||||
case 'job':
|
||||
visibleNotes = visibleNotes.filter(n => n.type === 'job');
|
||||
break;
|
||||
case 'manager':
|
||||
visibleNotes = visibleNotes.filter(n => n.type === 'manager');
|
||||
break;
|
||||
case 'personal':
|
||||
visibleNotes = visibleNotes.filter(n => n.type === 'personal');
|
||||
break;
|
||||
// 'all' - no additional filter
|
||||
}
|
||||
|
||||
if (!q) {
|
||||
renderNotesList(notesList);
|
||||
renderNotesList(visibleNotes);
|
||||
return;
|
||||
}
|
||||
const filtered = notesList.filter((n) => {
|
||||
const filtered = visibleNotes.filter((n) => {
|
||||
const hay = [
|
||||
n.title,
|
||||
n[NOTE_BODY_PLAIN_FIELD],
|
||||
@@ -2128,12 +2409,38 @@
|
||||
if (!note) return;
|
||||
document.getElementById('notesListContainer').classList.add('hidden');
|
||||
document.getElementById('noteContainer').classList.add('hidden');
|
||||
document.getElementById('noteDetailContainer').classList.remove('hidden');
|
||||
const detailContainer = document.getElementById('noteDetailContainer');
|
||||
detailContainer.classList.remove('hidden');
|
||||
|
||||
// Set background color based on note type and sharing status
|
||||
const type = (note.type || 'personal').toLowerCase();
|
||||
let bgClass;
|
||||
if (note._sharedWithMe) {
|
||||
bgClass = 'bg-blue-100 border-blue-300';
|
||||
} else if (note.shared) {
|
||||
bgClass = 'bg-green-100 border-green-300';
|
||||
} else {
|
||||
const bgMap = {
|
||||
personal: 'bg-amber-100 border-amber-300',
|
||||
manager: 'bg-orange-200 border-orange-400',
|
||||
job: 'bg-pink-200 border-pink-400',
|
||||
};
|
||||
bgClass = bgMap[type] || 'bg-gray-100 border-gray-300';
|
||||
}
|
||||
|
||||
// Remove all bg- and border- classes and add new ones
|
||||
detailContainer.className = detailContainer.className
|
||||
.replace(/\bbg-\S+\b/g, '')
|
||||
.replace(/\bborder-\S+\b/g, '');
|
||||
detailContainer.classList.add(...bgClass.split(' '));
|
||||
|
||||
document.getElementById('noteDetailTitle').textContent = note.title || 'Untitled';
|
||||
document.getElementById('noteDetailMeta').textContent = note.created ? `Created ${new Date(note.created).toLocaleString()}` : '';
|
||||
const body = note[NOTE_BODY_PLAIN_FIELD] || (note[NOTE_BODY_HTML_FIELD] ? htmlToPlainText(note[NOTE_BODY_HTML_FIELD]) : '');
|
||||
document.getElementById('noteDetailBody').textContent = body || '(empty)';
|
||||
document.getElementById('noteDetailBodyDisplay').textContent = body || '(empty)';
|
||||
|
||||
// Store current note for editing
|
||||
window.currentEditingNote = note;
|
||||
|
||||
const audioEl = document.getElementById('noteDetailAudio');
|
||||
const audioStatus = document.getElementById('noteDetailAudioStatus');
|
||||
@@ -2279,6 +2586,57 @@
|
||||
loadNotesList();
|
||||
});
|
||||
|
||||
// Filter button listeners
|
||||
document.getElementById('filterAllBtn').addEventListener('click', () => {
|
||||
currentFilter = 'all';
|
||||
searchQuery = '';
|
||||
document.getElementById('notesSearch').value = '';
|
||||
applySearch();
|
||||
});
|
||||
|
||||
document.getElementById('filterMySharedBtn').addEventListener('click', () => {
|
||||
currentFilter = 'myshared';
|
||||
searchQuery = '';
|
||||
document.getElementById('notesSearch').value = '';
|
||||
applySearch();
|
||||
});
|
||||
|
||||
document.getElementById('filterSharedWithMeBtn').addEventListener('click', () => {
|
||||
currentFilter = 'sharedwithme';
|
||||
searchQuery = '';
|
||||
document.getElementById('notesSearch').value = '';
|
||||
applySearch();
|
||||
});
|
||||
|
||||
document.getElementById('filterJobBtn').addEventListener('click', () => {
|
||||
currentFilter = 'job';
|
||||
searchQuery = '';
|
||||
document.getElementById('notesSearch').value = '';
|
||||
applySearch();
|
||||
});
|
||||
|
||||
document.getElementById('filterManagerBtn').addEventListener('click', () => {
|
||||
currentFilter = 'manager';
|
||||
searchQuery = '';
|
||||
document.getElementById('notesSearch').value = '';
|
||||
applySearch();
|
||||
});
|
||||
|
||||
document.getElementById('filterPersonalBtn').addEventListener('click', () => {
|
||||
currentFilter = 'personal';
|
||||
searchQuery = '';
|
||||
document.getElementById('notesSearch').value = '';
|
||||
applySearch();
|
||||
});
|
||||
|
||||
document.getElementById('hiddenNotesBtn').addEventListener('click', () => {
|
||||
showHiddenNotes();
|
||||
});
|
||||
|
||||
document.getElementById('backToNotesFromHiddenBtn').addEventListener('click', () => {
|
||||
showNotesList();
|
||||
});
|
||||
|
||||
document.getElementById('conversationsBtn').addEventListener('click', () => {
|
||||
showConversations();
|
||||
});
|
||||
@@ -2309,6 +2667,91 @@
|
||||
showNoteForm();
|
||||
});
|
||||
|
||||
// Title edit listeners
|
||||
document.getElementById('noteDetailTitle').addEventListener('dblclick', () => {
|
||||
const titleEl = document.getElementById('noteDetailTitle');
|
||||
const titleEdit = document.getElementById('noteDetailTitleEdit');
|
||||
const titleInput = document.getElementById('noteDetailTitleInput');
|
||||
titleInput.value = titleEl.textContent;
|
||||
titleEl.classList.add('hidden');
|
||||
titleEdit.classList.remove('hidden');
|
||||
titleInput.focus();
|
||||
titleInput.select();
|
||||
});
|
||||
|
||||
document.getElementById('noteDetailTitleSave').addEventListener('click', async () => {
|
||||
const titleInput = document.getElementById('noteDetailTitleInput').value.trim();
|
||||
if (window.currentEditingNote) {
|
||||
try {
|
||||
await pb.collection(NOTES_COLLECTION).update(window.currentEditingNote.id, { title: titleInput });
|
||||
window.currentEditingNote.title = titleInput;
|
||||
document.getElementById('noteDetailTitle').textContent = titleInput || 'Untitled';
|
||||
document.getElementById('noteDetailTitleEdit').classList.add('hidden');
|
||||
document.getElementById('noteDetailTitle').classList.remove('hidden');
|
||||
loadNotesList();
|
||||
} catch (err) {
|
||||
console.error('Error updating title:', err);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('noteDetailTitleCancel').addEventListener('click', () => {
|
||||
document.getElementById('noteDetailTitleEdit').classList.add('hidden');
|
||||
document.getElementById('noteDetailTitle').classList.remove('hidden');
|
||||
});
|
||||
|
||||
// Body edit listeners
|
||||
document.getElementById('noteDetailBodyDisplay').addEventListener('dblclick', () => {
|
||||
const bodyDisplay = document.getElementById('noteDetailBodyDisplay');
|
||||
const bodyEdit = document.getElementById('noteDetailBodyEdit');
|
||||
const bodyInput = document.getElementById('noteDetailBodyInput');
|
||||
bodyInput.value = bodyDisplay.textContent;
|
||||
bodyDisplay.classList.add('hidden');
|
||||
bodyEdit.classList.remove('hidden');
|
||||
bodyInput.focus();
|
||||
});
|
||||
|
||||
document.getElementById('noteDetailBodySave').addEventListener('click', async () => {
|
||||
const bodyInput = document.getElementById('noteDetailBodyInput').value;
|
||||
if (window.currentEditingNote) {
|
||||
try {
|
||||
const payload = {
|
||||
[NOTE_BODY_PLAIN_FIELD]: bodyInput,
|
||||
[NOTE_BODY_HTML_FIELD]: plainTextToHTML(bodyInput),
|
||||
};
|
||||
await pb.collection(NOTES_COLLECTION).update(window.currentEditingNote.id, payload);
|
||||
window.currentEditingNote[NOTE_BODY_PLAIN_FIELD] = bodyInput;
|
||||
document.getElementById('noteDetailBodyDisplay').textContent = bodyInput || '(empty)';
|
||||
document.getElementById('noteDetailBodyEdit').classList.add('hidden');
|
||||
document.getElementById('noteDetailBodyDisplay').classList.remove('hidden');
|
||||
loadNotesList();
|
||||
} catch (err) {
|
||||
console.error('Error updating body:', err);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('noteDetailBodyCancel').addEventListener('click', () => {
|
||||
document.getElementById('noteDetailBodyEdit').classList.add('hidden');
|
||||
document.getElementById('noteDetailBodyDisplay').classList.remove('hidden');
|
||||
});
|
||||
|
||||
// Delete button listener
|
||||
document.getElementById('noteDetailDeleteBtn').addEventListener('click', async () => {
|
||||
if (!window.currentEditingNote) return;
|
||||
const confirmed = confirm('Are you sure you want to delete this note? This action cannot be undone.');
|
||||
if (!confirmed) return;
|
||||
|
||||
try {
|
||||
await pb.collection(NOTES_COLLECTION).update(window.currentEditingNote.id, { delete: true });
|
||||
showNotesList();
|
||||
loadNotesList();
|
||||
} catch (err) {
|
||||
console.error('Error deleting note:', err);
|
||||
alert('Failed to delete note: ' + err.message);
|
||||
}
|
||||
});
|
||||
|
||||
// Attachment menu handling
|
||||
const attachmentMenuBtn = document.getElementById('attachmentMenuBtn');
|
||||
const attachmentMenu = document.getElementById('attachmentMenu');
|
||||
|
||||
Reference in New Issue
Block a user