| 1 |
|
| 2 |
|
| 3 |
(function() { |
| 4 |
'use strict'; |
| 5 |
|
| 6 |
function csrfHeaders() { |
| 7 |
var token = document.querySelector('meta[name="csrf-token"]'); |
| 8 |
return token ? { 'X-CSRF-Token': token.content } : {}; |
| 9 |
} |
| 10 |
|
| 11 |
function escapeHtml(s) { |
| 12 |
var d = document.createElement('div'); |
| 13 |
d.textContent = s; |
| 14 |
return d.innerHTML; |
| 15 |
} |
| 16 |
|
| 17 |
function showToast(msg) { |
| 18 |
if (window.showToast) { window.showToast(msg); return; } |
| 19 |
alert(msg); |
| 20 |
} |
| 21 |
|
| 22 |
function bodyFor(id) { |
| 23 |
var el = document.querySelector('textarea[data-body-for="' + id + '"]'); |
| 24 |
return el ? el.value : ''; |
| 25 |
} |
| 26 |
|
| 27 |
function updateCount(delta) { |
| 28 |
var el = document.getElementById('psection-count'); |
| 29 |
if (el) el.textContent = parseInt(el.textContent || '0') + delta; |
| 30 |
} |
| 31 |
|
| 32 |
function attachRowHandlers(row) { |
| 33 |
var delBtn = row.querySelector('.psection-del-btn'); |
| 34 |
var editBtn = row.querySelector('.psection-edit-btn'); |
| 35 |
|
| 36 |
delBtn.addEventListener('click', function() { |
| 37 |
var id = this.dataset.id; |
| 38 |
if (!confirm('Delete this page?')) return; |
| 39 |
fetch('/api/project-sections/' + id, { method: 'DELETE', headers: csrfHeaders() }) |
| 40 |
.then(function(res) { |
| 41 |
if (res.ok) { |
| 42 |
var hidden = document.querySelector('textarea[data-body-for="' + id + '"]'); |
| 43 |
if (hidden) hidden.remove(); |
| 44 |
row.remove(); |
| 45 |
updateCount(-1); |
| 46 |
} else { |
| 47 |
apiErrorMessage(res, 'Failed to delete').then(function(m) { showToast(m); }); |
| 48 |
} |
| 49 |
}) |
| 50 |
.catch(function() { showToast('Failed to delete'); }); |
| 51 |
}); |
| 52 |
|
| 53 |
editBtn.addEventListener('click', function() { |
| 54 |
var id = this.dataset.id; |
| 55 |
var title = this.dataset.title || row.querySelector('span').textContent; |
| 56 |
document.getElementById('edit-psec-id').value = id; |
| 57 |
document.getElementById('edit-psec-title').value = title; |
| 58 |
document.getElementById('edit-psec-body').value = bodyFor(id); |
| 59 |
document.getElementById('psec-edit-status').textContent = ''; |
| 60 |
document.getElementById('psection-edit-modal').classList.remove('hidden'); |
| 61 |
}); |
| 62 |
} |
| 63 |
|
| 64 |
function init() { |
| 65 |
var addBtn = document.getElementById('add-psec-btn'); |
| 66 |
if (!addBtn) return; |
| 67 |
var projectId = addBtn.dataset.projectId; |
| 68 |
|
| 69 |
addBtn.addEventListener('click', function() { |
| 70 |
var title = document.getElementById('new-psec-title').value.trim(); |
| 71 |
var body = document.getElementById('new-psec-body').value; |
| 72 |
var status = document.getElementById('psec-add-status'); |
| 73 |
if (!title) { status.textContent = 'Title is required'; return; } |
| 74 |
this.disabled = true; |
| 75 |
status.textContent = ''; |
| 76 |
|
| 77 |
fetch('/api/projects/' + projectId + '/sections', { |
| 78 |
method: 'POST', |
| 79 |
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()), |
| 80 |
body: JSON.stringify({ title: title, body: body }) |
| 81 |
}) |
| 82 |
.then(function(res) { |
| 83 |
if (!res.ok) return res.json().then(function(d) { throw new Error(d.error || 'Failed'); }); |
| 84 |
return res.json(); |
| 85 |
}) |
| 86 |
.then(function(sec) { |
| 87 |
var empty = document.getElementById('psections-empty'); |
| 88 |
if (empty) empty.remove(); |
| 89 |
var list = document.getElementById('psections-list'); |
| 90 |
var row = document.createElement('div'); |
| 91 |
row.className = 'psection-row'; |
| 92 |
row.dataset.id = sec.id; |
| 93 |
row.innerHTML = |
| 94 |
'<span class="psection-row-title">' + escapeHtml(sec.title) + '</span>' + |
| 95 |
'<code class="psection-row-anchor">#section-' + escapeHtml(sec.slug) + '</code>' + |
| 96 |
'<span class="psection-row-length">' + (sec.body || '').length + ' chars</span>' + |
| 97 |
'<button type="button" class="btn-secondary psection-edit-btn" data-id="' + sec.id + '" data-title="' + escapeHtml(sec.title) + '">Edit</button>' + |
| 98 |
'<button type="button" class="btn-secondary psection-del-btn" data-id="' + sec.id + '">Delete</button>'; |
| 99 |
list.appendChild(row); |
| 100 |
var hidden = document.createElement('textarea'); |
| 101 |
hidden.className = 'hidden'; |
| 102 |
hidden.dataset.bodyFor = sec.id; |
| 103 |
hidden.value = sec.body || ''; |
| 104 |
list.appendChild(hidden); |
| 105 |
attachRowHandlers(row); |
| 106 |
updateCount(1); |
| 107 |
document.getElementById('new-psec-title').value = ''; |
| 108 |
document.getElementById('new-psec-body').value = ''; |
| 109 |
document.getElementById('psection-add-details').removeAttribute('open'); |
| 110 |
}) |
| 111 |
.catch(function(err) { status.textContent = err.message; }) |
| 112 |
.finally(function() { addBtn.disabled = false; }); |
| 113 |
}); |
| 114 |
|
| 115 |
document.getElementById('save-psec-btn').addEventListener('click', function() { |
| 116 |
var id = document.getElementById('edit-psec-id').value; |
| 117 |
var title = document.getElementById('edit-psec-title').value.trim(); |
| 118 |
var body = document.getElementById('edit-psec-body').value; |
| 119 |
var status = document.getElementById('psec-edit-status'); |
| 120 |
if (!title) { status.textContent = 'Title is required'; return; } |
| 121 |
this.disabled = true; |
| 122 |
|
| 123 |
fetch('/api/project-sections/' + id, { |
| 124 |
method: 'PUT', |
| 125 |
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()), |
| 126 |
body: JSON.stringify({ title: title, body: body }) |
| 127 |
}) |
| 128 |
.then(function(res) { |
| 129 |
if (!res.ok) return res.json().then(function(d) { throw new Error(d.error || 'Failed'); }); |
| 130 |
return res.json(); |
| 131 |
}) |
| 132 |
.then(function(sec) { |
| 133 |
var row = document.querySelector('.psection-row[data-id="' + id + '"]'); |
| 134 |
if (row) { |
| 135 |
row.querySelector('.psection-row-title').textContent = sec.title; |
| 136 |
row.querySelector('.psection-row-anchor').textContent = '#section-' + sec.slug; |
| 137 |
row.querySelector('.psection-row-length').textContent = (sec.body || '').length + ' chars'; |
| 138 |
row.querySelector('.psection-edit-btn').dataset.title = sec.title; |
| 139 |
} |
| 140 |
var hidden = document.querySelector('textarea[data-body-for="' + id + '"]'); |
| 141 |
if (hidden) hidden.value = sec.body || ''; |
| 142 |
document.getElementById('psection-edit-modal').classList.add('hidden'); |
| 143 |
}) |
| 144 |
.catch(function(err) { status.textContent = err.message; }) |
| 145 |
.finally(function() { document.getElementById('save-psec-btn').disabled = false; }); |
| 146 |
}); |
| 147 |
|
| 148 |
document.getElementById('cancel-psec-btn').addEventListener('click', function() { |
| 149 |
document.getElementById('psection-edit-modal').classList.add('hidden'); |
| 150 |
}); |
| 151 |
|
| 152 |
document.querySelectorAll('.psection-row').forEach(attachRowHandlers); |
| 153 |
} |
| 154 |
|
| 155 |
|
| 156 |
if (document.getElementById('add-psec-btn')) init(); |
| 157 |
document.body.addEventListener('htmx:afterSettle', function(e) { |
| 158 |
if (e.target && e.target.querySelector && e.target.querySelector('#add-psec-btn')) init(); |
| 159 |
}); |
| 160 |
})(); |
| 161 |
|