Add delete & edit for project panel entries
- deleteProjectEntry(id) and updateProjectEntry(id, text) DB helpers on project_entries store - Edit toggle button beside Add Entry; switches to Done when active, resets on panel open/close - In edit mode, panel-sourced entries show ✎ (inline edit) and × (delete) action buttons - Delete re-renders timeline and updates breakout card count - Inline edit saves on Enter/blur, cancels on Escape Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -538,6 +538,7 @@
|
|||||||
.side-panel-actions {
|
.side-panel-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -556,6 +557,7 @@
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding: 6px 0;
|
padding: 6px 0;
|
||||||
border-bottom: 1px solid var(--bg-accent);
|
border-bottom: 1px solid var(--bg-accent);
|
||||||
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel-entry:last-child { border-bottom: none; }
|
.panel-entry:last-child { border-bottom: none; }
|
||||||
@@ -573,6 +575,60 @@
|
|||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
line-height: 1.45;
|
line-height: 1.45;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-entry-body textarea {
|
||||||
|
width: 100%;
|
||||||
|
background: var(--bg-accent);
|
||||||
|
border: 1px solid var(--accent);
|
||||||
|
border-radius: 4px;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
line-height: 1.45;
|
||||||
|
padding: 4px 6px;
|
||||||
|
resize: vertical;
|
||||||
|
min-height: 48px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-entry-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding-top: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-entry-action-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 2px 5px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
line-height: 1;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
transition: background 0.15s, color 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-entry-action-btn:hover { background: var(--bg-accent); color: var(--text-primary); }
|
||||||
|
.panel-entry-action-btn.del:hover { color: #e05252; }
|
||||||
|
|
||||||
|
.panel-edit-mode-btn {
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid var(--text-secondary);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 8px 14px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.15s, color 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-edit-mode-btn:hover,
|
||||||
|
.panel-edit-mode-btn.active {
|
||||||
|
border-color: var(--accent);
|
||||||
|
color: var(--accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Breakout card in main timeline ─────────────────────── */
|
/* ── Breakout card in main timeline ─────────────────────── */
|
||||||
@@ -1481,6 +1537,7 @@
|
|||||||
<div class="side-panel-input">
|
<div class="side-panel-input">
|
||||||
<textarea id="panelEntryInput" placeholder="Add note to project… (Ctrl+Enter to save)"></textarea>
|
<textarea id="panelEntryInput" placeholder="Add note to project… (Ctrl+Enter to save)"></textarea>
|
||||||
<div class="side-panel-actions">
|
<div class="side-panel-actions">
|
||||||
|
<button id="panelEditModeBtn" class="panel-edit-mode-btn">Edit</button>
|
||||||
<button id="panelSubmitBtn" class="submit-btn">Add Entry</button>
|
<button id="panelSubmitBtn" class="submit-btn">Add Entry</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -2068,6 +2125,10 @@
|
|||||||
|
|
||||||
async function openProjectPanel(projectId, projectName) {
|
async function openProjectPanel(projectId, projectName) {
|
||||||
activePanelProjectId = projectId;
|
activePanelProjectId = projectId;
|
||||||
|
panelEditMode = false;
|
||||||
|
const editBtn = document.getElementById('panelEditModeBtn');
|
||||||
|
editBtn.textContent = 'Edit';
|
||||||
|
editBtn.classList.remove('active');
|
||||||
document.getElementById('sidePanelTitle').textContent = '🔗 ' + projectName;
|
document.getElementById('sidePanelTitle').textContent = '🔗 ' + projectName;
|
||||||
document.getElementById('sidePanel').style.display = 'flex';
|
document.getElementById('sidePanel').style.display = 'flex';
|
||||||
document.querySelector('main').classList.add('main--panel-open');
|
document.querySelector('main').classList.add('main--panel-open');
|
||||||
@@ -2079,8 +2140,14 @@
|
|||||||
document.getElementById('sidePanel').style.display = 'none';
|
document.getElementById('sidePanel').style.display = 'none';
|
||||||
document.querySelector('main').classList.remove('main--panel-open');
|
document.querySelector('main').classList.remove('main--panel-open');
|
||||||
activePanelProjectId = null;
|
activePanelProjectId = null;
|
||||||
|
panelEditMode = false;
|
||||||
|
const editBtn = document.getElementById('panelEditModeBtn');
|
||||||
|
editBtn.textContent = 'Edit';
|
||||||
|
editBtn.classList.remove('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let panelEditMode = false;
|
||||||
|
|
||||||
async function renderProjectTimeline(projectId) {
|
async function renderProjectTimeline(projectId) {
|
||||||
const container = document.getElementById('sidePanelTimeline');
|
const container = document.getElementById('sidePanelTimeline');
|
||||||
container.innerHTML = '<div style="color:var(--text-secondary);font-size:0.85rem;padding:8px 0">Loading…</div>';
|
container.innerHTML = '<div style="color:var(--text-secondary);font-size:0.85rem;padding:8px 0">Loading…</div>';
|
||||||
@@ -2119,9 +2186,16 @@
|
|||||||
const sourceLabel = e._source === 'timeline'
|
const sourceLabel = e._source === 'timeline'
|
||||||
? `<span style="font-size:0.7rem;color:var(--text-secondary);margin-left:4px">[journal]</span>`
|
? `<span style="font-size:0.7rem;color:var(--text-secondary);margin-left:4px">[journal]</span>`
|
||||||
: '';
|
: '';
|
||||||
return `<div class="panel-entry">
|
const actions = (panelEditMode && e._source === 'panel')
|
||||||
|
? `<div class="panel-entry-actions">
|
||||||
|
<button class="panel-entry-action-btn" data-action="edit" title="Edit">✎</button>
|
||||||
|
<button class="panel-entry-action-btn del" data-action="delete" title="Delete">×</button>
|
||||||
|
</div>`
|
||||||
|
: '';
|
||||||
|
return `<div class="panel-entry" data-id="${e.id}" data-source="${e._source}" data-text="${escapeHtml(e.text)}">
|
||||||
<div class="panel-entry-time">${time}${sourceLabel}</div>
|
<div class="panel-entry-time">${time}${sourceLabel}</div>
|
||||||
<div class="panel-entry-body">${mood}${escapeHtml(e.text)}${tags ? '<div style="margin-top:4px">'+tags+'</div>' : ''}</div>
|
<div class="panel-entry-body">${mood}${escapeHtml(e.text)}${tags ? '<div style="margin-top:4px">'+tags+'</div>' : ''}</div>
|
||||||
|
${actions}
|
||||||
</div>`;
|
</div>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
return `<div class="panel-date-group"><div class="panel-date-label">${label}</div>${rows}</div>`;
|
return `<div class="panel-date-group"><div class="panel-date-label">${label}</div>${rows}</div>`;
|
||||||
@@ -2277,6 +2351,33 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function deleteProjectEntry(id) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const tx = db.transaction(PROJECT_ENTRIES_STORE, 'readwrite');
|
||||||
|
const store = tx.objectStore(PROJECT_ENTRIES_STORE);
|
||||||
|
const req = store.delete(id);
|
||||||
|
req.onsuccess = () => resolve();
|
||||||
|
req.onerror = () => reject(req.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateProjectEntry(id, text) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const tx = db.transaction(PROJECT_ENTRIES_STORE, 'readwrite');
|
||||||
|
const store = tx.objectStore(PROJECT_ENTRIES_STORE);
|
||||||
|
const getReq = store.get(id);
|
||||||
|
getReq.onsuccess = () => {
|
||||||
|
const entry = getReq.result;
|
||||||
|
if (!entry) { reject(new Error('Entry not found')); return; }
|
||||||
|
entry.text = text;
|
||||||
|
const putReq = store.put(entry);
|
||||||
|
putReq.onsuccess = () => resolve();
|
||||||
|
putReq.onerror = () => reject(putReq.error);
|
||||||
|
};
|
||||||
|
getReq.onerror = () => reject(getReq.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function getProjectEntries(projectId) {
|
function getProjectEntries(projectId) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const tx = db.transaction(PROJECT_ENTRIES_STORE, 'readonly');
|
const tx = db.transaction(PROJECT_ENTRIES_STORE, 'readonly');
|
||||||
@@ -2906,6 +3007,70 @@
|
|||||||
if (e.key === 'Enter' && e.ctrlKey) { e.preventDefault(); if (dbReady) panelSubmit(); }
|
if (e.key === 'Enter' && e.ctrlKey) { e.preventDefault(); if (dbReady) panelSubmit(); }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.getElementById('panelEditModeBtn').addEventListener('click', () => {
|
||||||
|
panelEditMode = !panelEditMode;
|
||||||
|
const btn = document.getElementById('panelEditModeBtn');
|
||||||
|
btn.textContent = panelEditMode ? 'Done' : 'Edit';
|
||||||
|
btn.classList.toggle('active', panelEditMode);
|
||||||
|
if (activePanelProjectId != null) renderProjectTimeline(activePanelProjectId);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('sidePanelTimeline').addEventListener('click', async e => {
|
||||||
|
const actionBtn = e.target.closest('[data-action]');
|
||||||
|
if (!actionBtn) return;
|
||||||
|
const entry = actionBtn.closest('.panel-entry');
|
||||||
|
if (!entry || entry.dataset.source !== 'panel') return;
|
||||||
|
const id = parseInt(entry.dataset.id);
|
||||||
|
|
||||||
|
if (actionBtn.dataset.action === 'delete') {
|
||||||
|
try {
|
||||||
|
await deleteProjectEntry(id);
|
||||||
|
await renderProjectTimeline(activePanelProjectId);
|
||||||
|
const countEl = document.querySelector(`.breakout-card[data-project-id="${activePanelProjectId}"] .breakout-card-count`);
|
||||||
|
if (countEl) {
|
||||||
|
const count = await getCombinedProjectEntryCount(activePanelProjectId);
|
||||||
|
countEl.textContent = `${count} ${count === 1 ? 'entry' : 'entries'}`;
|
||||||
|
}
|
||||||
|
showToast('Entry deleted');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to delete project entry:', err);
|
||||||
|
showToast('Error deleting entry');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actionBtn.dataset.action === 'edit') {
|
||||||
|
const body = entry.querySelector('.panel-entry-body');
|
||||||
|
const currentText = entry.dataset.text;
|
||||||
|
body.innerHTML = `<textarea>${escapeHtml(currentText)}</textarea>`;
|
||||||
|
const textarea = body.querySelector('textarea');
|
||||||
|
textarea.focus();
|
||||||
|
textarea.setSelectionRange(textarea.value.length, textarea.value.length);
|
||||||
|
|
||||||
|
const save = async () => {
|
||||||
|
const newText = textarea.value.trim();
|
||||||
|
if (!newText || newText === currentText) {
|
||||||
|
await renderProjectTimeline(activePanelProjectId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await updateProjectEntry(id, newText);
|
||||||
|
await renderProjectTimeline(activePanelProjectId);
|
||||||
|
showToast('Entry updated');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to update project entry:', err);
|
||||||
|
showToast('Error updating entry');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
textarea.addEventListener('blur', save, { once: true });
|
||||||
|
textarea.addEventListener('keydown', ke => {
|
||||||
|
if (ke.key === 'Enter' && !ke.shiftKey) { ke.preventDefault(); textarea.removeEventListener('blur', save); save(); }
|
||||||
|
if (ke.key === 'Escape') { textarea.removeEventListener('blur', save); renderProjectTimeline(activePanelProjectId); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
init();
|
init();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user