feat: full inline issue editing (title + body + assignee)
CI / test (push) Failing after 4s
CI / lint (push) Failing after 4s

Replaced bare prompt() with proper edit form in card modal:
- showEditForm: renders title input + body textarea + assignee field
- saveIssueEdit: PATCH /api/issues/... with title, body, assignee
- 'Annuler' button returns to card detail view
- Old editIssueInline (prompt only) removed
- Added .form-label CSS style
This commit is contained in:
2026-07-08 10:18:28 -04:00
parent 68f2b3f03b
commit 3e78d4343c
2 changed files with 40 additions and 6 deletions
+39 -6
View File
@@ -211,6 +211,10 @@
function renderCardDetail(owner, repo, data) {
const issue = data.issue || {}, comments = data.comments || [], checklists = data.checklists || [], card = data.card || {};
let h = '';
// Edit mode toggle: store data for later
window._issueData = {owner, repo, issue, comments, checklists, card};
h += `<div class="detail-section"><span class="detail-state badge ${issue.state}">${issue.state}</span> <strong>${escHtml(issue.title||'')}</strong></div>`;
h += `<div class="detail-meta">`;
(issue.labels||[]).forEach(l => h += `<span class="card-label" style="background:#${l.color||'666'}33;color:#${l.color||'ccc'}">${escHtml(l.name)}</span>`);
@@ -238,11 +242,45 @@
comments.forEach(c => h += `<div class="comment"><img src="${c.user?.avatar_url||''}" width="24" height="24" class="avatar"><strong>${escHtml(c.user?.login||'')}</strong><span class="comment-date">${(c.created_at||'').substr(0,10)}</span><div class="comment-body">${escHtml(c.body||'')}</div></div>`);
h += `</div>`;
h += `<div class="detail-actions"><a href="${issue.html_url||''}" target="_blank" class="btn btn-sm">🔗 Ouvrir dans Gitea</a>`;
h += `<button onclick="editIssueInline('${owner}','${repo}',${issue.number})" class="btn btn-sm">✏️ Éditer</button>`;
h += `<button onclick="showEditForm('${owner}','${repo}',${issue.number})" class="btn btn-sm">✏️ Éditer</button>`;
h += `<button onclick="closeModal()" class="btn btn-sm">Fermer</button></div>`;
return h;
}
function showEditForm(owner, repo, issueId) {
const content = document.getElementById('card-modal-content');
const data = window._issueData || {};
const issue = data.issue || {};
fetch(`/api/collaborators/${owner}/${repo}`).then(r=>r.json()).then(cdata => {
let h = '<form onsubmit="saveIssueEdit(event,\''+owner+'\',\''+repo+'\','+issueId+')">';
h += '<label class="form-label">Titre</label>';
h += '<input type="text" id="edit-title" value="'+escHtml(issue.title||'')+'" class="form-input" required>';
h += '<label class="form-label">Description</label>';
h += '<textarea id="edit-body" rows="5" class="form-input">'+escHtml(issue.body||'')+'</textarea>';
h += '<div class="form-row">';
h += '<input type="text" id="edit-assignee" value="'+(issue.assignee?issue.assignee.login:'')+'" placeholder="Assigné à (login)" class="form-input" style="flex:1">';
h += '</div>';
h += '<div class="detail-actions" style="margin-top:8px">';
h += '<button type="submit" class="btn btn-primary">💾 Sauvegarder</button>';
h += '<button type="button" onclick="openCardDetail(\''+owner+'\',\''+repo+'\','+issueId+')" class="btn btn-sm">Annuler</button>';
h += '</div></form>';
content.innerHTML = h;
});
}
async function saveIssueEdit(evt, owner, repo, issueId) {
evt.preventDefault();
const title = document.getElementById('edit-title').value.trim();
const body = document.getElementById('edit-body').value.trim();
const assignee = document.getElementById('edit-assignee').value.trim();
if (!title) return;
const params = new URLSearchParams({title, body, assignee});
await fetch(`/api/issues/${owner}/${repo}/${issueId}?${params}`, {
method: 'PATCH', headers: {'X-CSRF-Token': getCSRFToken()}
});
openCardDetail(owner, repo, issueId);
}
function escHtml(s) { return (s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
async function updateCardMeta(o,r,i,priority,dueDate) { await fetch(`/api/card/${o}/${r}/${i}?priority=${priority}&due_date=${dueDate}`,{method:'POST',headers:{'X-CSRF-Token':getCSRFToken()}}); }
async function toggleCheckItem(id,checked) { await fetch(`/api/checklist-items/${id}?checked=${checked}`,{method:'PATCH',headers:{'X-CSRF-Token':getCSRFToken()}}); }
@@ -259,11 +297,6 @@
openCardDetail(o,r,issueId);
}
async function createChecklist(o,r,issueId) { await fetch(`/api/checklists/${o}/${r}/${issueId}`,{method:'POST',headers:{'X-CSRF-Token':getCSRFToken()}}); openCardDetail(o,r,issueId); }
async function editIssueInline(o,r,issueId) {
const t=prompt('Nouveau titre:',''); if(!t)return;
await fetch(`/api/issues/${o}/${r}/${issueId}?title=${encodeURIComponent(t)}`,{method:'PATCH',headers:{'X-CSRF-Token':getCSRFToken()}});
closeModal(); window.location.reload();
}
async function registerWebhook(o,r) {
try {
const resp=await fetch(`/api/webhook/register/${o}/${r}`,{method:'POST',headers:{'X-CSRF-Token':getCSRFToken()}});
+1
View File
@@ -199,6 +199,7 @@ body { min-height: 100vh; }
margin-bottom: 8px;
}
.form-input:focus, .form-select:focus { outline: none; border-color: var(--accent); }
.form-label { font-size: 12px; color: var(--text-dim); display: block; margin: 8px 0 4px 0; }
.form-input-sm { background: var(--bg); border: 1px solid var(--border); border-radius: 4px; padding: 4px 6px; color: var(--text); font-size: 12px; }
.form-select-sm { background: var(--bg); border: 1px solid var(--border); border-radius: 4px; padding: 2px 4px; color: var(--text); font-size: 12px; }
.form-row { display: flex; gap: 8px; }