Implement quick add functionality for incorrect answers; return collapsed partial for quick suggestions in the answers table.
This commit is contained in:
@@ -50,6 +50,12 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td {% if answer.proposed %}class="proposed-answer" data-aid="{{answer.pk}}" data-question-type="anatomy"{% endif %}>
|
<td {% if answer.proposed %}class="proposed-answer" data-aid="{{answer.pk}}" data-question-type="anatomy"{% endif %}>
|
||||||
<pre class="mb-0">{{ answer.answer }}</pre>
|
<pre class="mb-0">{{ answer.answer }}</pre>
|
||||||
|
{% if answer.status == "0" %}
|
||||||
|
<div class="mt-1">
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary toggle-words-btn" aria-expanded="false">Add words</button>
|
||||||
|
<div class="word-buttons d-none mt-2" aria-hidden="true"></div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ answer.get_status_display|default:answer.status }}</td>
|
<td>{{ answer.get_status_display|default:answer.status }}</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -182,6 +188,98 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<script>
|
||||||
|
// HTMX quick-add helpers for word buttons inside the answers table
|
||||||
|
var SUGGEST_URL = "{% url 'anatomy:question_suggest_incorrect_answers' question.pk %}";
|
||||||
|
var CSRF_TOKEN = "{{ csrf_token }}";
|
||||||
|
|
||||||
|
function toggleWords(btn) {
|
||||||
|
var container = btn.nextElementSibling;
|
||||||
|
if (!container) return;
|
||||||
|
// populate if empty
|
||||||
|
if (!container.dataset.populated) {
|
||||||
|
var td = btn.closest('td');
|
||||||
|
var pre = td ? td.querySelector('pre') : null;
|
||||||
|
var text = pre ? pre.textContent || pre.innerText : '';
|
||||||
|
buildWordButtons(container, text);
|
||||||
|
container.dataset.populated = '1';
|
||||||
|
}
|
||||||
|
var expanded = !container.classList.contains('d-none');
|
||||||
|
if (expanded) {
|
||||||
|
container.classList.add('d-none');
|
||||||
|
btn.setAttribute('aria-expanded', 'false');
|
||||||
|
container.setAttribute('aria-hidden', 'true');
|
||||||
|
} else {
|
||||||
|
container.classList.remove('d-none');
|
||||||
|
btn.setAttribute('aria-expanded', 'true');
|
||||||
|
container.setAttribute('aria-hidden', 'false');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildWordButtons(container, text) {
|
||||||
|
if (!text) return;
|
||||||
|
var toks = (text.toLowerCase().match(/\w+/g) || []);
|
||||||
|
var seen = {};
|
||||||
|
toks.forEach(function(t){
|
||||||
|
if (t.length < 3) return;
|
||||||
|
if (seen[t]) return;
|
||||||
|
seen[t]=true;
|
||||||
|
var b = document.createElement('button');
|
||||||
|
b.type = 'button';
|
||||||
|
b.className = 'btn btn-sm btn-outline-primary me-1 mb-1';
|
||||||
|
b.textContent = t;
|
||||||
|
b.dataset.word = t;
|
||||||
|
// do not attach inline handlers here; use delegated listener
|
||||||
|
container.appendChild(b);
|
||||||
|
});
|
||||||
|
if (!Object.keys(seen).length) {
|
||||||
|
var span = document.createElement('div');
|
||||||
|
span.className = 'small text-muted';
|
||||||
|
span.textContent = 'No suitable words';
|
||||||
|
container.appendChild(span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delegated click handler to keep markup unobtrusive and work for HTMX swaps
|
||||||
|
document.addEventListener('click', function(e){
|
||||||
|
var toggle = e.target.closest('.toggle-words-btn');
|
||||||
|
if (toggle) {
|
||||||
|
e.preventDefault();
|
||||||
|
toggleWords(toggle);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var wordBtn = e.target.closest('.word-buttons button');
|
||||||
|
if (wordBtn) {
|
||||||
|
e.preventDefault();
|
||||||
|
var word = wordBtn.dataset.word || wordBtn.textContent.trim();
|
||||||
|
if (word) addQuickSuggestion(word);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function addQuickSuggestion(word) {
|
||||||
|
if (!word) return;
|
||||||
|
if (typeof htmx === 'undefined') {
|
||||||
|
// Fallback: simple POST via fetch
|
||||||
|
fetch(SUGGEST_URL, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'X-CSRFToken': CSRF_TOKEN },
|
||||||
|
body: new URLSearchParams({ suggestion: word, quick: '1' })
|
||||||
|
}).then(function(r){ return r.text(); }).then(function(html){
|
||||||
|
var wrapper = document.getElementById('incorrect-answers');
|
||||||
|
if (wrapper) wrapper.innerHTML = html;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
htmx.ajax('POST', SUGGEST_URL, {
|
||||||
|
values: { suggestion: word, quick: '1' },
|
||||||
|
headers: { 'X-CSRFToken': CSRF_TOKEN },
|
||||||
|
target: '#incorrect-answers',
|
||||||
|
swap: 'innerHTML'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block css %}
|
{% block css %}
|
||||||
|
|||||||
@@ -1469,6 +1469,10 @@ def question_suggest_incorrect_answers(request, pk):
|
|||||||
existing.append(suggestion)
|
existing.append(suggestion)
|
||||||
question.answer_suggest_incorrect = existing
|
question.answer_suggest_incorrect = existing
|
||||||
question.save()
|
question.save()
|
||||||
|
# If this is a quick add (from the answers table) return the collapsed partial
|
||||||
|
if request.POST.get("quick"):
|
||||||
|
return render(request, "anatomy/partials/suggest_incorrect_collapsed.html", {"question": question})
|
||||||
|
|
||||||
# Recompute candidates and render the edit fragment
|
# Recompute candidates and render the edit fragment
|
||||||
form = SuggestIncorrectAnswerForm(instance=question)
|
form = SuggestIncorrectAnswerForm(instance=question)
|
||||||
candidates = build_candidates(question)
|
candidates = build_candidates(question)
|
||||||
|
|||||||
Reference in New Issue
Block a user