| 1 |
{% include "wizards/partials/step_nav.html" %} |
| 2 |
|
| 3 |
<div class="wizard-step"> |
| 4 |
<h2 class="subtitle-h2">Sections</h2> |
| 5 |
<p class="step-description">Add tabbed content sections (e.g. Features, Installation, Specs). Sections are optional.</p> |
| 6 |
|
| 7 |
<div id="sections-list"> |
| 8 |
{% for section in sections %} |
| 9 |
<div class="section-row" data-id="{{ section.id }}"> |
| 10 |
<span class="section-row-title">{{ section.title }}</span> |
| 11 |
<span class="section-row-length">{{ section.body.chars().count() }} chars</span> |
| 12 |
<button type="button" class="btn-secondary section-delete-btn" data-id="{{ section.id }}">Delete</button> |
| 13 |
</div> |
| 14 |
{% endfor %} |
| 15 |
</div> |
| 16 |
|
| 17 |
<details class="section-add-details"> |
| 18 |
<summary>Add Section</summary> |
| 19 |
<div class="section-add-body"> |
| 20 |
<div class="form-group"> |
| 21 |
<label for="new-section-title">Title</label> |
| 22 |
<input type="text" id="new-section-title" placeholder="e.g. Features, Installation..." autocomplete="off"> |
| 23 |
</div> |
| 24 |
<div class="form-group"> |
| 25 |
<label for="new-section-body">Body (Markdown)</label> |
| 26 |
<textarea id="new-section-body" rows="6" placeholder="Section content..."></textarea> |
| 27 |
</div> |
| 28 |
<button type="button" class="btn-secondary" id="add-section-btn">Add Section</button> |
| 29 |
<span id="section-add-status" class="field-status ml-2"></span> |
| 30 |
</div> |
| 31 |
</details> |
| 32 |
|
| 33 |
<form hx-post="/dashboard/project/{{ project_slug }}/new-item/{{ item_id }}/step/sections" |
| 34 |
hx-target="#wizard-step" hx-swap="innerHTML" |
| 35 |
hx-push-url="/dashboard/project/{{ project_slug }}/new-item/{{ item_id }}/step/pricing"> |
| 36 |
<div class="wizard-actions"> |
| 37 |
<button type="button" class="btn-secondary" |
| 38 |
hx-get="/dashboard/project/{{ project_slug }}/new-item/{{ item_id }}/step/content" |
| 39 |
hx-target="#wizard-step" hx-swap="innerHTML" |
| 40 |
hx-push-url="/dashboard/project/{{ project_slug }}/new-item/{{ item_id }}/step/content">Back</button> |
| 41 |
<button type="submit" class="btn-primary">Continue</button> |
| 42 |
</div> |
| 43 |
</form> |
| 44 |
</div> |
| 45 |
|
| 46 |
<script> |
| 47 |
(function() { |
| 48 |
var itemId = '{{ item_id }}'; |
| 49 |
|
| 50 |
document.getElementById('add-section-btn').addEventListener('click', function() { |
| 51 |
var title = document.getElementById('new-section-title').value.trim(); |
| 52 |
var body = document.getElementById('new-section-body').value; |
| 53 |
var status = document.getElementById('section-add-status'); |
| 54 |
if (!title) { status.textContent = 'Title is required'; return; } |
| 55 |
this.disabled = true; |
| 56 |
status.textContent = ''; |
| 57 |
|
| 58 |
fetch('/api/items/' + itemId + '/sections', { |
| 59 |
method: 'POST', |
| 60 |
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()), |
| 61 |
body: JSON.stringify({ title: title, body: body }) |
| 62 |
}) |
| 63 |
.then(function(res) { |
| 64 |
if (!res.ok) return res.json().then(function(d) { throw new Error(d.error || 'Failed'); }); |
| 65 |
return res.json(); |
| 66 |
}) |
| 67 |
.then(function(sec) { |
| 68 |
var row = document.createElement('div'); |
| 69 |
row.className = 'section-row'; |
| 70 |
row.dataset.id = sec.id; |
| 71 |
row.innerHTML = |
| 72 |
'<span class="section-row-title"></span>' + |
| 73 |
'<span class="section-row-length"></span>' + |
| 74 |
'<button type="button" class="btn-secondary section-delete-btn"></button>'; |
| 75 |
row.querySelector('.section-row-title').textContent = sec.title; |
| 76 |
row.querySelector('.section-row-length').textContent = (sec.body || '').length + ' chars'; |
| 77 |
var delBtn = row.querySelector('.section-delete-btn'); |
| 78 |
delBtn.dataset.id = sec.id; |
| 79 |
delBtn.textContent = 'Delete'; |
| 80 |
document.getElementById('sections-list').appendChild(row); |
| 81 |
attachDeleteHandler(delBtn); |
| 82 |
document.getElementById('new-section-title').value = ''; |
| 83 |
document.getElementById('new-section-body').value = ''; |
| 84 |
}) |
| 85 |
.catch(function(err) { status.textContent = err.message; }) |
| 86 |
.finally(function() { document.getElementById('add-section-btn').disabled = false; }); |
| 87 |
}); |
| 88 |
|
| 89 |
function attachDeleteHandler(btn) { |
| 90 |
btn.addEventListener('click', function() { |
| 91 |
var id = this.dataset.id; |
| 92 |
var row = this.closest('.section-row'); |
| 93 |
fetch('/api/sections/' + id, { method: 'DELETE', headers: csrfHeaders() }) |
| 94 |
.then(function(res) { if (res.ok) row.remove(); else showToast('Failed to delete'); }) |
| 95 |
.catch(function() { showToast('Failed to delete'); }); |
| 96 |
}); |
| 97 |
} |
| 98 |
|
| 99 |
document.querySelectorAll('.section-delete-btn').forEach(attachDeleteHandler); |
| 100 |
})(); |
| 101 |
</script> |
| 102 |
|