fix(web): mobile edit via inline fullscreen instead of hidden detail pane

Detail pane is display:none on mobile, so edit mode was unreachable.
Render edit fields directly in the inline expansion with exp-full
takeover. ESC and Cmd+Enter work from within inputs.

Closes #32
This commit is contained in:
2026-05-20 19:14:02 -04:00
parent 82bc6e7ba1
commit 7d1e0f895c
2 changed files with 69 additions and 2 deletions
+65 -2
View File
@@ -658,6 +658,25 @@
</div>`; </div>`;
} }
function renderInlineEditMode(e) {
return `<div class="exp-inner exp-inner--edit">
<div class="peek-edit-fields">
<div class="peek-edit-field"><label class="peek-edit-lbl">title</label>
<input class="peek-edit-in" id="edit-title" value="${escAttr(e.title || '')}"></div>
<div class="peek-edit-field"><label class="peek-edit-lbl">description</label>
<input class="peek-edit-in" id="edit-desc" value="${escAttr(e.description || '')}"></div>
<div class="peek-edit-field"><label class="peek-edit-lbl">content</label>
<textarea class="peek-edit-ta" id="edit-body" rows="7">${escHtml(e.body || '')}</textarea></div>
<div class="peek-edit-field"><label class="peek-edit-lbl">tags</label>
<input class="peek-edit-in" id="edit-tags" value="${escAttr((e.tags || []).join(' '))}" placeholder="space-separated"></div>
</div>
<div class="exp-acts">
<button class="action-btn primary" onclick="event.stopPropagation();nibApp.saveEdit('${e.id}')">save</button>
<button class="action-btn" onclick="event.stopPropagation();nibApp.exitMode()">cancel</button>
</div>
</div>`;
}
function renderInlineDetail(e) { function renderInlineDetail(e) {
const tags = (e.tags || []).map(t => `<span class="detail-tag">#${t}</span>`).join(''); const tags = (e.tags || []).map(t => `<span class="detail-tag">#${t}</span>`).join('');
let actions = ''; let actions = '';
@@ -1457,11 +1476,31 @@
state.peekMode = mode; state.peekMode = mode;
if (mode === 'run') state.runChecked = new Set(); if (mode === 'run') state.runChecked = new Set();
if (mode === 'fill') { state.fillValues = {}; state.fillActive = 0; } if (mode === 'fill') { state.fillValues = {}; state.fillActive = 0; }
if (mode === 'edit' && isMobileBreakpoint()) {
const e = state.entities[state.selectedIndex];
const sel = $(`.entity-item.selected, .card-row.selected`);
if (!e || !sel) return;
const clip = sel.querySelector('.entity-exp-clip');
if (clip) clip.innerHTML = renderInlineEditMode(e);
sel.classList.add('exp-full');
const titleInput = sel.querySelector('#edit-title');
if (titleInput) titleInput.focus();
return;
}
renderDetailPane(); renderDetailPane();
}, },
exitMode() { exitMode() {
state.peekMode = 'preview'; state.peekMode = 'preview';
if (isMobileBreakpoint()) {
const e = state.entities[state.selectedIndex];
const sel = $(`.entity-item.selected, .card-row.selected`);
if (sel && e) {
const clip = sel.querySelector('.entity-exp-clip');
if (clip) clip.innerHTML = renderInlineDetail(e);
}
return;
}
renderDetailPane(); renderDetailPane();
}, },
@@ -1500,7 +1539,21 @@
await loadEntities(); await loadEntities();
await loadTags(); await loadTags();
const idx = state.entities.findIndex(x => x.id === id); const idx = state.entities.findIndex(x => x.id === id);
if (idx >= 0) selectEntity(idx); if (idx >= 0) {
if (isMobileBreakpoint()) {
state.selectedIndex = idx;
renderEntityList();
const sel = $(`.entity-item[data-id="${id}"], .card-row[data-id="${id}"]`);
if (sel) {
sel.classList.add('selected');
const clip = sel.querySelector('.entity-exp-clip');
const e = state.entities[idx];
if (clip && e) clip.innerHTML = renderInlineDetail(e);
}
} else {
selectEntity(idx);
}
}
showToast('saved'); showToast('saved');
}, },
@@ -1580,7 +1633,17 @@
document.addEventListener('keydown', (ev) => { document.addEventListener('keydown', (ev) => {
const tag = (ev.target.tagName || '').toLowerCase(); const tag = (ev.target.tagName || '').toLowerCase();
if (tag === 'input' || tag === 'textarea') { if (tag === 'input' || tag === 'textarea') {
if (ev.key === 'Escape') ev.target.blur(); if (ev.key === 'Escape' && state.peekMode === 'edit') {
ev.target.blur();
nibApp.exitMode();
return;
}
if (ev.key === 'Escape') { ev.target.blur(); return; }
if ((ev.metaKey || ev.ctrlKey) && ev.key === 'Enter' && state.peekMode === 'edit') {
const e = state.entities[state.selectedIndex];
if (e) nibApp.saveEdit(e.id);
return;
}
return; return;
} }
+4
View File
@@ -1859,5 +1859,9 @@ kbd { background: var(--raised); border: 1px solid var(--border); border-radius:
.card-row.exp-full .entity-exp { grid-template-rows: 1fr; } .card-row.exp-full .entity-exp { grid-template-rows: 1fr; }
.entity-item.exp-full .exp-inner, .entity-item.exp-full .exp-inner,
.card-row.exp-full .exp-inner { padding-top: 1rem; padding-bottom: 2rem; } .card-row.exp-full .exp-inner { padding-top: 1rem; padding-bottom: 2rem; }
.exp-inner--edit { display: flex; flex-direction: column; min-height: 100%; }
.exp-inner--edit .peek-edit-fields { flex: 1; padding: 16px; }
.exp-inner--edit .peek-edit-ta { flex: 1; min-height: 150px; }
.exp-inner--edit .exp-acts { padding: 12px 16px; border-top: 1px solid var(--border); position: sticky; bottom: 0; background: var(--bg); }
main.focus-peek #entity-panel { display: block; overflow: auto; min-width: 0; } main.focus-peek #entity-panel { display: block; overflow: auto; min-width: 0; }
} }