feat: double-click inline editing for card text
All checks were successful
Deploy to dev.bl.pixeldev.eu / deploy (push) Successful in 2s
All checks were successful
Deploy to dev.bl.pixeldev.eu / deploy (push) Successful in 2s
Replace the Edit button with dblclick-to-edit on the paragraph itself: - Double-click #card-text → hides the <p>, shows a textarea in place - Ctrl+Enter → saves (PUT /api/text) - Escape → cancels and restores original text - Textarea auto-sizes to content height on open - Subtle dashed underline on hover signals editability - Edit button removed; Reset button stays Closes #9
This commit is contained in:
parent
1110e95e49
commit
ca521f79b1
98
index.html
98
index.html
@ -80,7 +80,12 @@
|
||||
0 0 20px 5px rgba(34, 197, 94, .3),
|
||||
0 0 50px 10px rgba(34, 197, 94, .12);
|
||||
}
|
||||
#card-text { margin: 0 0 .9rem; }
|
||||
#card-text {
|
||||
margin: 0 0 .4rem; cursor: text;
|
||||
border-bottom: 1px dashed transparent; transition: border-color .15s;
|
||||
}
|
||||
#card-text:hover { border-bottom-color: var(--border); }
|
||||
#card-dblclick-hint { color: var(--meta); font-size: .68rem; margin-bottom: .7rem; }
|
||||
#card-meta { color: var(--meta); font-size: .72rem; margin-bottom: .7rem; min-height: 1rem; }
|
||||
.card-actions { display: flex; gap: .5rem; justify-content: flex-end; }
|
||||
.card-btn {
|
||||
@ -98,15 +103,14 @@
|
||||
.card-btn:disabled { opacity: .4; cursor: default; }
|
||||
.card-btn-danger { color: #f57c7c; border-color: rgba(245,124,124,.3); }
|
||||
.card-btn-danger:hover { background: rgba(245,124,124,.1); }
|
||||
#edit-form { display: none; flex-direction: column; gap: .6rem; margin-top: .5rem; }
|
||||
#edit-form.open { display: flex; }
|
||||
#edit-wrap { display: none; flex-direction: column; gap: .5rem; margin-bottom: .4rem; }
|
||||
#edit-wrap.open { display: flex; }
|
||||
#edit-input {
|
||||
width: 100%; min-height: 90px; background: var(--input-bg); color: var(--fg);
|
||||
border: 1px solid var(--border); border-radius: 8px; padding: .55rem .7rem;
|
||||
font-family: inherit; font-size: .9rem; resize: vertical; outline: none;
|
||||
transition: border-color .15s;
|
||||
width: 100%; min-height: 80px; background: var(--input-bg); color: var(--fg);
|
||||
border: 1px solid var(--accent); border-radius: 8px; padding: .55rem .7rem;
|
||||
font-family: inherit; font-size: inherit; line-height: 1.6; resize: vertical; outline: none;
|
||||
}
|
||||
#edit-input:focus { border-color: var(--accent); }
|
||||
.edit-hint { font-size: .72rem; color: var(--meta); }
|
||||
#card-status { font-size: .75rem; color: var(--meta); min-height: 1rem; text-align: right; }
|
||||
</style>
|
||||
</head>
|
||||
@ -116,18 +120,14 @@
|
||||
|
||||
<div class="card" id="text-card">
|
||||
<p id="card-text">Loading…</p>
|
||||
<div id="card-meta"></div>
|
||||
<div class="card-actions" id="card-actions">
|
||||
<button class="card-btn" id="edit-btn">✏️ Edit</button>
|
||||
<button class="card-btn card-btn-danger" id="reset-btn">↺ Reset</button>
|
||||
</div>
|
||||
<div id="edit-form">
|
||||
<div id="edit-wrap">
|
||||
<textarea id="edit-input" placeholder="Enter text…"></textarea>
|
||||
<div style="display:flex; gap:.5rem; justify-content:flex-end; align-items:center;">
|
||||
<span id="card-status"></span>
|
||||
<button class="card-btn" id="cancel-btn">✕ Cancel</button>
|
||||
<button class="card-btn" id="save-btn">💾 Save</button>
|
||||
<span class="edit-hint">Ctrl+Enter to save · Esc to cancel</span>
|
||||
</div>
|
||||
<div id="card-meta"></div>
|
||||
<div class="card-actions">
|
||||
<span id="card-status"></span>
|
||||
<button class="card-btn card-btn-danger" id="reset-btn">↺ Reset</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -154,14 +154,12 @@
|
||||
(function() {
|
||||
var BASE = '/test';
|
||||
var textEl = document.getElementById('card-text');
|
||||
var metaEl = document.getElementById('card-meta');
|
||||
var editForm = document.getElementById('edit-form');
|
||||
var editWrap = document.getElementById('edit-wrap');
|
||||
var editInput = document.getElementById('edit-input');
|
||||
var metaEl = document.getElementById('card-meta');
|
||||
var statusEl = document.getElementById('card-status');
|
||||
var editBtn = document.getElementById('edit-btn');
|
||||
var saveBtn = document.getElementById('save-btn');
|
||||
var cancelBtn = document.getElementById('cancel-btn');
|
||||
var resetBtn = document.getElementById('reset-btn');
|
||||
var editing = false;
|
||||
|
||||
function setMeta(updated_at) {
|
||||
metaEl.textContent = updated_at
|
||||
@ -179,52 +177,58 @@
|
||||
function load() {
|
||||
fetch(BASE + '/api/text')
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
textEl.textContent = d.content || '';
|
||||
setMeta(d.updated_at);
|
||||
})
|
||||
.then(function(d) { textEl.textContent = d.content || ''; setMeta(d.updated_at); })
|
||||
.catch(function() { textEl.textContent = '(failed to load)'; });
|
||||
}
|
||||
load();
|
||||
|
||||
// Edit mode toggle
|
||||
editBtn.addEventListener('click', function() {
|
||||
// Enter edit mode on double-click
|
||||
textEl.addEventListener('dblclick', function() {
|
||||
if (editing) return;
|
||||
editing = true;
|
||||
editInput.value = textEl.textContent;
|
||||
editForm.classList.add('open');
|
||||
editBtn.style.display = 'none';
|
||||
resetBtn.style.display = 'none';
|
||||
textEl.style.display = 'none';
|
||||
editWrap.classList.add('open');
|
||||
// Auto-size textarea to content
|
||||
editInput.style.height = 'auto';
|
||||
editInput.style.height = editInput.scrollHeight + 'px';
|
||||
editInput.focus();
|
||||
editInput.select();
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener('click', function() {
|
||||
editForm.classList.remove('open');
|
||||
editBtn.style.display = '';
|
||||
resetBtn.style.display = '';
|
||||
// Cancel on Escape, save on Ctrl+Enter
|
||||
editInput.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') { cancelEdit(); }
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { e.preventDefault(); saveEdit(); }
|
||||
});
|
||||
|
||||
function cancelEdit() {
|
||||
editing = false;
|
||||
editWrap.classList.remove('open');
|
||||
textEl.style.display = '';
|
||||
}
|
||||
|
||||
// UPDATE
|
||||
saveBtn.addEventListener('click', function() {
|
||||
function saveEdit() {
|
||||
var content = editInput.value.trim();
|
||||
if (!content) { setStatus('Cannot be empty.', '#f57c7c'); return; }
|
||||
saveBtn.disabled = true;
|
||||
setStatus('Saving…', '');
|
||||
fetch(BASE + '/api/text', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ content: content })
|
||||
}).then(function(r) { return r.json(); }).then(function(d) {
|
||||
if (d.error) { setStatus('Error: ' + d.error, '#f57c7c'); saveBtn.disabled = false; return; }
|
||||
if (d.error) { setStatus('Error: ' + d.error, '#f57c7c'); return; }
|
||||
textEl.textContent = d.content;
|
||||
setMeta(d.updated_at);
|
||||
editForm.classList.remove('open');
|
||||
editBtn.style.display = '';
|
||||
resetBtn.style.display = '';
|
||||
setStatus('Saved!', '#6fcf6f');
|
||||
saveBtn.disabled = false;
|
||||
}).catch(function() { setStatus('Network error.', '#f57c7c'); saveBtn.disabled = false; });
|
||||
});
|
||||
setStatus('Saved ✓', '#6fcf6f');
|
||||
cancelEdit();
|
||||
}).catch(function() { setStatus('Network error.', '#f57c7c'); });
|
||||
}
|
||||
|
||||
// DELETE (reset)
|
||||
resetBtn.addEventListener('click', function() {
|
||||
if (editing) cancelEdit();
|
||||
if (!confirm('Reset to default text?')) return;
|
||||
resetBtn.disabled = true;
|
||||
fetch(BASE + '/api/text', { method: 'DELETE' })
|
||||
@ -232,7 +236,7 @@
|
||||
.then(function(d) {
|
||||
textEl.textContent = d.content;
|
||||
setMeta(d.updated_at);
|
||||
setStatus('Reset!', '#6fcf6f');
|
||||
setStatus('Reset ✓', '#6fcf6f');
|
||||
resetBtn.disabled = false;
|
||||
}).catch(function() { setStatus('Network error.', '#f57c7c'); resetBtn.disabled = false; });
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user