many admin panel changes

This commit is contained in:
mystie 2024-09-28 00:42:53 +03:00
parent 207c4dd842
commit 5bea0ff24b

View file

@ -3,160 +3,264 @@
{% set adminLink = 'active' %} {% set adminLink = 'active' %}
{% block additionalHeadItems %} {% block additionalHeadItems %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/toastify.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/toastify.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/coloris.min.css') }}">
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<h1 class="mb-3">Admin panel</h1> <h1 class="mb-3">Admin panel</h1>
<!-- this is actually not used anymore, but htmx requires a valid target so we have to use it --> <!-- this is actually not used anymore, but htmx requires a valid target so we have to use it -->
<div id="response-container"></div> <div id="response-container"></div>
<a class="btn d-lg-none mb-2" href="#preview">Skip to preview</a> <a class="btn d-lg-none mb-2" href="#preview">Skip to preview</a>
<form hx-trigger="click from:#saveConfig, keyup[ctrlKey&&key=='Enter']" hx-post="{{ url_for('api.updateConfig') }}" hx-target="#response-container" hx-swap="none">
<a class="visually-hidden-focusable" href="#preview">Skip to preview</a> <a class="visually-hidden-focusable" href="#preview">Skip to preview</a>
<div class="row"> <div class="row">
<div class="col-sm-6"> <div class="col mw-100">
<h2 id="instance">Instance</h2> <form hx-post="{{ url_for('api.updateConfig') }}" hx-target="#response-container" hx-swap="none">
<div class="form-group mb-3"> <h2 id="instance" class="mb-3 fw-normal d-flex align-items-center justify-content-between">
<label class="form-label" for="instance.title">Title <small class="text-body-secondary">(e.g. My question box)</small></label> Instance
<input type="text" id="instance.title" name="instance.title" value="{{ cfg.instance.title }}" oninput="updateText('instance.title', 'title')" class="form-control"> <button type="button" class="btn btn-outline-secondary btn-sm" data-bs-toggle="collapse" data-bs-target="#preview-collapse" aria-expanded="true" aria-controls="preview-collapse"><i class="bi bi-arrows-expand-vertical me-1"></i> Toggle preview</button>
<p class="form-text">Title of this CatAsk instance</p> </h2>
</div> <div class="form-group mb-3">
<div class="form-group mb-3"> <label class="form-label" for="instance.title">Title <small class="text-body-secondary">(e.g. My question box)</small></label>
<label class="form-label d-flex flex-column flex-lg-row align-items-lg-center justify-content-between" for="instance.description"> <input type="text" id="instance.title" name="instance.title" value="{{ cfg.instance.title }}" oninput="updateText('instance.title', 'title')" class="form-control">
<span>Description <small class="text-body-secondary">(e.g. Ask me a question!)</small></span> <p class="form-text">Title of this CatAsk instance</p>
<small class="text-body-secondary"><i class="bi bi-markdown me-1"></i> Markdown supported</small> </div>
</label> <div class="form-group mb-3">
<textarea id="instance.description" name="instance.description" oninput="updateText('instance.description', 'desc')" class="form-control" style="height: 200px; resize: vertical;">{{ cfg.instance.description }}</textarea> <label class="form-label d-flex flex-column flex-lg-row align-items-lg-center justify-content-between" for="instance.description">
<p class="form-text">Description of this CatAsk instance</p> <span>Description <small class="text-body-secondary">(e.g. Ask me a question!)</small></span>
</div> <small class="text-body-secondary"><i class="bi bi-markdown me-1"></i> Markdown supported</small>
<div class="form-group mb-3"> </label>
<label class="form-label d-flex flex-column flex-lg-row align-items-lg-center justify-content-between" for="instance.rules"> <textarea id="instance.description" name="instance.description" oninput="updateText('instance.description', 'desc')" class="form-control" style="height: 200px; resize: vertical;">{{ cfg.instance.description }}</textarea>
<span>Rules</span> <p class="form-text">Description of this CatAsk instance</p>
<small class="text-body-secondary"><i class="bi bi-markdown me-1"></i> Markdown supported</small> </div>
</label> <div class="form-group mb-3">
<textarea id="instance.rules" name="instance.rules" class="form-control" style="height: 200px; resize: vertical;" oninput="updateText('instance.rules', 'rules-content')">{{ cfg.instance.rules }}</textarea> <label class="form-label d-flex flex-column flex-lg-row align-items-lg-center justify-content-between" for="instance.rules">
<p class="form-text">Rules of this CatAsk instance</p> <span>Rules</span>
</div> <small class="text-body-secondary"><i class="bi bi-markdown me-1"></i> Markdown supported</small>
<div class="form-group mb-3"> </label>
<label class="form-label" for="instance.image">Relative image path <small class="text-body-secondary">(default: /static/img/ca_screenshot.png)</small></label> <textarea id="instance.rules" name="instance.rules" class="form-control" style="height: 200px; resize: vertical;" oninput="updateText('instance.rules', 'rules-content')">{{ cfg.instance.rules }}</textarea>
<input type="text" id="instance.image" name="instance.image" value="{{ cfg.instance.image }}" class="form-control"> <p class="form-text">Rules of this CatAsk instance</p>
<p class="form-text">Image that's going to be used in a link preview</p> </div>
</div> <div class="form-group mb-3">
<div class="form-group mb-4"> <label class="form-label" for="instance.image">Relative image path <small class="text-body-secondary">(default: /static/img/ca_screenshot.png)</small></label>
<label class="form-label" for="instance.fullBaseUrl">Base URL <small class="text-body-secondary">(e.g. https://ask.example.com)</small></label> <input type="text" id="instance.image" name="instance.image" value="{{ cfg.instance.image }}" class="form-control">
<input type="text" id="instance.fullBaseUrl" name="instance.fullBaseUrl" value="{{ cfg.instance.fullBaseUrl }}" class="form-control"> <p class="form-text">Image that's going to be used in a link preview</p>
<p class="form-text">Full URL to homepage of this CatAsk instance without a trailing slash</p> </div>
</div> <div class="form-group mb-2">
<h2 id="general">General</h2> <label class="form-label" for="instance.fullBaseUrl">Base URL <small class="text-body-secondary">(e.g. https://ask.example.com)</small></label>
<div class="form-group mb-3"> <input type="text" id="instance.fullBaseUrl" name="instance.fullBaseUrl" value="{{ cfg.instance.fullBaseUrl }}" class="form-control">
<label class="form-label" for="charLimit">Question character limit</label> <p class="form-text">Full URL to homepage of this CatAsk instance without a trailing slash</p>
<input type="number" id="charLimit" name="charLimit" value="{{ cfg.charLimit }}" class="form-control"> </div>
<p class="form-text">Max length of a question in characters; questions extending this character limit will not be added to the database</p> <div class="form-group mb-4">
</div> <button type="submit" class="btn btn-primary" id="saveConfig">
<div class="form-group mb-4"> <span class="spinner-border spinner-border-sm htmx-indicator" aria-hidden="true"></span>
<label class="form-label" for="anonName">Name for anonymous users</label> <span class="visually-hidden" role="status">Loading...</span>
<input type="text" id="anonName" name="anonName" value="{{ cfg.anonName }}" class="form-control"> Save
<p class="form-text">This name will be used for questions asked to you by anonymous users</p> </button>
</div> </div>
</form>
<h2 id="customize" class="mb-3 fw-normal">Customize</h2>
<h3 class="fw-light">Favicon</h3>
<form hx-trigger="click from:#saveFavicon, keyup[ctrlKey&&key=='Enter']" hx-post="{{ url_for('api.uploadFavicon') }}" hx-target="#response-container" hx-swap="none" hx-encoding="multipart/form-data">
<p class="m-0">Current favicon: <img src="{{ url_for('static', filename='icons/favicon/apple-touch-icon.png') }}" width="32" height="32" alt="{{ cfg.instance.title }}'s icon" class="rounded"></p>
<div>
<label for="favicon" class="form-label">Upload favicon</label>
<input class="form-control" type="file" id="favicon" name="favicon">
</div>
<div class="mb-4">
<button type="submit" class="btn btn-primary mt-3" id="saveFavicon">
<span class="spinner-border spinner-border-sm htmx-indicator" aria-hidden="true"></span>
<span class="visually-hidden" role="status">Loading...</span>
Upload
</button>
</div>
</form>
<form hx-trigger="click from:#saveConfig, keyup[ctrlKey&&key=='Enter']" hx-post="{{ url_for('api.updateConfig') }}" hx-target="#response-container" hx-swap="none">
<h3 class="fw-light">Accent color</h3>
{# <h4 class="fw-light">Color</h4> #}
<div class="form-group d-flex flex-column">
<label class="form-label" for="style.accentLight">Light theme</label>
<input type="text" name="style.accentLight" id="style.accentLight" value="{{ cfg.style.accentLight }}" class="form-control" data-coloris>
<p class="form-text">Default: <b>#6345d9</b></p>
</div>
<div class="form-group d-flex flex-column">
<label class="form-label" for="style.accentDark">Dark theme</label>
<input type="text" name="style.accentDark" id="style.accentDark" value="{{ cfg.style.accentDark }}" class="form-control" data-coloris>
<p class="form-text">Default: <b>#7259d9</b></p>
</div>
{# brain doesn't feel like implementing this rn (9/27/24)
<h4 class="fw-light mt-2">Background</h4>
<div class="form-group d-flex flex-column">
<label class="form-label" for="style.bgLight">Light theme</label>
<input type="text" name="style.bgLight" id="style.bgLight" value="{{ cfg.style.bgLight }}" class="form-control" data-coloris>
<p class="form-text">Default: <b>#ffffff</b></p>
</div>
<div class="form-group d-flex flex-column">
<label class="form-label" for="style.bgDark">Dark theme</label>
<input type="text" name="style.bgDark" id="style.bgDark" value="{{ cfg.style.bgDark }}" class="form-control" data-coloris>
<p class="form-text">Default: <b>#202020</b></p>
</div>
#}
<div class="form-check mb-3">
<input
class="form-check-input"
type="checkbox"
name="_style.tintColors"
id="_style.tintColors"
value="{{ cfg.style.tintColors }}"
{% if cfg.style.tintColors == true %}checked{% endif %}>
<input type="hidden" id="style.tintColors" name="style.tintColors" value="{{ cfg.style.tintColors }}">
<label for="_style.tintColors" class="form-check-label">Tint all colors with accent color</label>
</div>
{#
<div class="form-check mb-3">
<input
class="form-check-input"
type="checkbox"
name="_style.tintBackground"
id="_style.tintBackground"
value="{{ cfg.style.tintBackground }}"
{% if cfg.style.tintBackground == true %}checked{% endif %}>
<input type="hidden" id="style.tintBackground" name="style.tintBackground" value="{{ cfg.style.tintBackground }}">
<label for="_style.tintBackground" class="form-check-label">Tint background with accent color</label>
</div>
#}
<h3 class="fw-light">Navbar link style</h3>
<div class="form-check">
<input class="form-check-input" type="radio" name="style.navStyle" id="style.navStyle.underline" value="underline" {% if cfg.style.navStyle == 'underline' %}checked{% endif %}>
<label class="form-check-label" for="style.navStyle.underline">
Underline <span class="text-body-secondary">(default)</span>
</label>
</div>
<div class="form-check mb-4">
<input class="form-check-input" type="radio" name="style.navStyle" id="style.navStyle.pills" value="pills" {% if cfg.style.navStyle == 'pills' %}checked{% endif %}>
<label class="form-check-label" for="style.navStyle.pills">
Pills
</label>
</div>
<h3 class="fw-light">Info box layout</h3>
<div class="form-check">
<input class="form-check-input" type="radio" name="style.infoBoxLayout" id="style.infoBoxLayout.column" value="column" {% if cfg.style.infoBoxLayout == 'column' %}checked{% endif %}>
<label class="form-check-label" for="style.infoBoxLayout.column">
Column <span class="text-body-secondary">(default)</span>
</label>
</div>
<div class="form-check mb-4">
<input class="form-check-input" type="radio" name="style.infoBoxLayout" id="style.infoBoxLayout.row" value="row" {% if cfg.style.infoBoxLayout == 'row' %}checked{% endif %}>
<label class="form-check-label" for="style.infoBoxLayout.row">
Row
</label>
</div>
<h3 class="fw-light">Trimmed content</h3>
<div class="form-group mb-3">
<label class="form-label" for="trimContentAfter">Trim content after (characters)</label>
<input type="number" id="trimContentAfter" name="trimContentAfter" value="{{ cfg.trimContentAfter }}" class="form-control">
<p class="form-text">Maximum length of content before it gets trimmed (used in sharing options); set to 0 to disable</p>
</div>
<h2 id="general" class="mb-3 fw-normal">General</h2>
<div class="form-group mb-3">
<label class="form-label" for="charLimit">Question character limit</label>
<input type="number" id="charLimit" name="charLimit" value="{{ cfg.charLimit }}" class="form-control">
<p class="form-text">Max length of a question in characters; questions extending this character limit will not be added to the database</p>
</div>
<div class="form-group mb-4">
<label class="form-label" for="anonName">Name for anonymous users</label>
<input type="text" id="anonName" name="anonName" value="{{ cfg.anonName }}" class="form-control">
<p class="form-text">This name will be used for questions asked to you by anonymous users</p>
</div>
<!-- reserved for 1.5.0 or later --> <div class="form-check mb-2">
<!-- <h3>Info block layout</h3> --> <input
<!-- <div class="btn-group mb-4 w-100" role="group" aria-label="Info block layout toggle button group"> --> class="form-check-input"
<!-- <input type="radio" class="btn-check" name="infoBlockLayout" id="columnLayout" autocomplete="off" checked> --> type="checkbox"
<!-- <label class="btn btn-outline-secondary w-100" for="columnLayout">Column</label> --> name="_lockInbox"
<!-- --> id="_lockInbox"
<!-- <input type="radio" class="btn-check" name="infoBlockLayout" id="rowLayout" autocomplete="off"> --> value="{{ cfg.lockInbox }}"
<!-- <label class="btn btn-outline-secondary w-100" for="rowLayout">Row</label> --> {% if cfg.lockInbox == true %}checked{% endif %}>
<!-- </div> --> <input type="hidden" id="lockInbox" name="lockInbox" value="{{ cfg.lockInbox }}">
<label for="_lockInbox" class="form-check-label">Lock inbox and don't allow new questions</label>
<div class="form-check mb-2"> </div>
<input <div class="form-check mb-2">
class="form-check-input" <input
type="checkbox" class="form-check-input"
name="_lockInbox" type="checkbox"
id="_lockInbox" name="_allowAnonQuestions"
value="{{ cfg.lockInbox }}" id="_allowAnonQuestions"
{% if cfg.lockInbox == true %}checked{% endif %}> value="{{ cfg.allowAnonQuestions }}"
<input type="hidden" id="lockInbox" name="lockInbox" value="{{ cfg.lockInbox }}"> {% if cfg.allowAnonQuestions == true %}checked{% endif %}>
<label for="_lockInbox" class="form-check-label">Lock inbox and don't allow new questions</label> <input type="hidden" id="allowAnonQuestions" name="allowAnonQuestions" value="{{ cfg.allowAnonQuestions }}">
</div> <label for="_allowAnonQuestions" class="form-check-label">Allow anonymous questions</label>
<div class="form-check mb-2"> </div>
<input <div class="form-check mb-2">
class="form-check-input" <input
type="checkbox" class="form-check-input"
name="_allowAnonQuestions" type="checkbox"
id="_allowAnonQuestions" name="_noDeleteConfirm"
value="{{ cfg.allowAnonQuestions }}" id="_noDeleteConfirm"
{% if cfg.allowAnonQuestions == true %}checked{% endif %}> value="{{ cfg.noDeleteConfirm }}"
<input type="hidden" id="allowAnonQuestions" name="allowAnonQuestions" value="{{ cfg.allowAnonQuestions }}"> {% if cfg.noDeleteConfirm == true %}checked{% endif %}>
<label for="_allowAnonQuestions" class="form-check-label">Allow anonymous questions</label> <input type="hidden" id="noDeleteConfirm" name="noDeleteConfirm" value="{{ cfg.noDeleteConfirm }}">
</div> <label for="_noDeleteConfirm" class="form-check-label">Disable confirmation when deleting questions</label>
<div class="form-check mb-2"> </div>
<input <div class="form-check mb-3">
class="form-check-input" <input
type="checkbox" class="form-check-input"
name="_noDeleteConfirm" type="checkbox"
id="_noDeleteConfirm" name="_showQuestionCount"
value="{{ cfg.noDeleteConfirm }}" id="_showQuestionCount"
{% if cfg.noDeleteConfirm == true %}checked{% endif %}> value="{{ cfg.showQuestionCount }}"
<input type="hidden" id="noDeleteConfirm" name="noDeleteConfirm" value="{{ cfg.noDeleteConfirm }}"> {% if cfg.showQuestionCount == true %}checked{% endif %}>
<label for="_noDeleteConfirm" class="form-check-label">Disable confirmation when deleting questions</label> <input type="hidden" id="showQuestionCount" name="showQuestionCount" value="{{ cfg.showQuestionCount }}">
</div> <label for="_showQuestionCount" class="form-check-label">Show question count in homepage</label>
<div class="form-check mb-3"> </div>
<input <div class="form-group mb-3">
class="form-check-input" <button type="submit" class="btn btn-primary mt-3" id="saveConfig">
type="checkbox" <span class="spinner-border spinner-border-sm htmx-indicator" aria-hidden="true"></span>
name="_showQuestionCount" <span class="visually-hidden" role="status">Loading...</span>
id="_showQuestionCount" Save
value="{{ cfg.showQuestionCount }}" </button>
{% if cfg.showQuestionCount == true %}checked{% endif %}> </div>
<input type="hidden" id="showQuestionCount" name="showQuestionCount" value="{{ cfg.showQuestionCount }}"> </form>
<label for="_showQuestionCount" class="form-check-label">Show question count in homepage</label> <hr class="mt-4 mb-4">
</div> <form hx-trigger="click from:#save-blacklist, keyup[ctrlKey&&key=='Enter']" hx-post="{{ url_for('admin.index') }}" hx-target="#response-container" hx-swap="none">
<div class="form-group mb-3"> <input type="hidden" name="action" value="update_word_blacklist">
<button type="submit" class="btn btn-primary mt-3" id="saveConfig"> <div class="form-group mb-3">
<span class="spinner-border spinner-border-sm htmx-indicator" aria-hidden="true"></span> <label class="form-label" for="blacklist_cat"><h2 id="blacklist" class="fw-normal">Word blacklist</h2></label>
<span class="visually-hidden" role="status">Loading...</span> <p class="text-body-secondary">Blacklisted words for questions; one word per line</p>
Save <textarea id="blacklist_cat" name="blacklist" style="height: 300px; resize: vertical;" class="form-control">{{ blacklist }}</textarea>
</button> <button type="submit" class="btn btn-primary mt-3" id="save-blacklist">
</div> <span class="me-1 spinner-border spinner-border-sm htmx-indicator" aria-hidden="true"></span>
</form> <span class="visually-hidden" role="status">Loading...</span>
<hr class="mt-4 mb-4"> Save
<form hx-trigger="click from:#save-blacklist, keyup[ctrlKey&&key=='Enter']" hx-post="{{ url_for('admin.index') }}" hx-target="#response-container" hx-swap="none"> </button>
<input type="hidden" name="action" value="update_word_blacklist"> </div>
<div class="form-group mb-3"> </form>
<label class="form-label" for="blacklist_cat"><h2 id="blacklist">Word blacklist</h2></label>
<p class="text-body-secondary">Blacklisted words for questions; one word per line</p>
<textarea id="blacklist_cat" name="blacklist" style="height: 300px; resize: vertical;" class="form-control">{{ blacklist }}</textarea>
<button type="submit" class="btn btn-primary mt-3" id="save-blacklist">
<span class="me-1 spinner-border spinner-border-sm htmx-indicator" aria-hidden="true"></span>
<span class="visually-hidden" role="status">Loading...</span>
Save
</button>
</div>
</form>
</div> </div>
<div class="col-sm-6" id="preview"> <div class="collapse show collapse-horizontal col" id="preview-collapse">
<h2>Preview</h2> <div>
<button class="text-warning-emphasis d-inline-flex align-items-center btn btn-sm mw-100" type="button" data-bs-toggle="collapse" data-bs-target="#preview-warning"><i class="bi bi-exclamation-triangle fs-5 me-2"></i> The preview might not always be accurate</button> <div id="preview">
<div class="collapse" id="preview-warning"> <h2 class="mb-3 fw-normal">Preview</h2>
<small class="mt-2 text-body-secondary">The preview is not guaranteed to render the same as on other pages, because it uses a different renderer (<b>marked (js)</b> while everything else uses <b>mistune (python)</b>)<br>The reason for this is live markdown rendering support</small> <button class="text-warning-emphasis d-inline-flex align-items-center btn btn-sm btn-outline-secondary mw-100" type="button" data-bs-toggle="collapse" data-bs-target="#preview-warning"><i class="bi bi-exclamation-triangle me-2"></i> The preview might not be accurate <i class="bi bi-chevron-down ms-1 small"></i></button>
</div> <div class="collapse" id="preview-warning">
<small class="mt-2 text-body-secondary">The preview is not guaranteed to render the same as on other pages, because it uses a different renderer (<b>marked (js)</b> while everything else uses <b>mistune (python)</b>)<br>The reason for this is live markdown rendering support</small>
</div>
</div>
<h3 class="h1 text-center fw-bold mt-4" id="title">{{ cfg.instance.title }}</h3> <h3 class="h1 text-center fw-bold mt-4" id="title">{{ cfg.instance.title }}</h3>
{% autoescape off %} {% autoescape off %}
<h4 class="h5 text-center fw-light" id="desc">{{ cfg.instance.description | render_markdown }}</h4> <h4 class="h5 text-center fw-light" id="desc">{{ cfg.instance.description | render_markdown }}</h4>
{% endautoescape %} {% endautoescape %}
<div class="m-auto col-sm-10"> <div class="m-auto col-sm-10">
<div class="accordion" id="rules-accordion"> <div class="accordion" id="rules-accordion">
<div class="accordion-item"> <div class="accordion-item">
<h2 class="accordion-header"> <h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#rules" aria-expanded="false" aria-controls="collapseTwo"> <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#rules" aria-expanded="false" aria-controls="collapseTwo">
<i class="bi bi-exclamation-triangle me-2 fs-4"></i> Rules <i class="bi bi-exclamation-triangle me-2 fs-4"></i> Rules
</button> </button>
</h2> </h2>
<div id="rules" class="accordion-collapse collapse" data-bs-parent="#rules-accordion"> <div id="rules" class="accordion-collapse collapse" data-bs-parent="#rules-accordion">
<div class="accordion-body"> <div class="accordion-body">
<div class="markdown-content" id="rules-content">{{ cfg.instance.rules | render_markdown }}</div> <div class="markdown-content" id="rules-content">{{ cfg.instance.rules | render_markdown }}</div>
</div> </div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -167,45 +271,67 @@
{% block scripts %} {% block scripts %}
<script src="{{ url_for('static', filename='js/toastify.min.js') }}"></script> <script src="{{ url_for('static', filename='js/toastify.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/marked.min.js') }}"></script> <script src="{{ url_for('static', filename='js/marked.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/coloris.min.js') }}"></script>
<script> <script>
marked.use({ Coloris({
breaks: true, theme: 'square',
gfm: true, themeMode: 'auto',
}); formatToggle: true,
function updateText(input, element) { alpha: false,
const inputEl = document.getElementById(input); swatches: [
const replaceEl = document.getElementById(element); '#c70f0f', // Red
replaceEl.innerHTML = marked.parse(inputEl.value); '#db5d0e', // Orange
} '#968829', // Yellow
'#217d1a', // Green
'#28b59b', // Turquoise
'#338FFF', // Blue
'#3358ff',
'#6345d9', // Indigo
'#A833FF', // Purple
'#d1298b' // Pink
],
});
marked.use({
breaks: true,
gfm: true,
});
function updateText(input, element) {
const inputEl = document.getElementById(input);
const replaceEl = document.getElementById(element);
replaceEl.innerHTML = marked.parse(inputEl.value);
}
</script> </script>
<script> <script>
// fix handling checkboxes and radios // fix handling checkboxes
document.querySelectorAll('.form-check-input, .btn-check').forEach(function(checkbox) { document.querySelectorAll('.form-check-input[type=checkbox]').forEach(function(checkbox) {
checkbox.addEventListener('change', function() { checkbox.addEventListener('change', function() {
checkbox.nextElementSibling.value = this.checked ? 'True' : 'False'; checkbox.nextElementSibling.value = this.checked ? 'True' : 'False';
}); });
}); });
</script> </script>
<script> <script>
document.addEventListener('htmx:afterRequest', function(event) { const forms = document.querySelectorAll('form');
console.log("reached event listener"); forms.forEach((form) => {
const jsonResponse = event.detail.xhr.response; form.addEventListener('htmx:afterRequest', function(event) {
if (jsonResponse) { console.log("reached event listener");
const parsed = JSON.parse(jsonResponse); const jsonResponse = event.detail.xhr.response;
const alertType = event.detail.successful ? 'success' : 'danger'; if (jsonResponse) {
message = event.detail.successful ? parsed.message : parsed.error; const parsed = JSON.parse(jsonResponse);
if (event.detail.target.id != "question-count") { const alertType = event.detail.successful ? 'success' : 'danger';
Toastify({ message = event.detail.successful ? parsed.message : parsed.error;
text: message, if (event.detail.target.id != "question-count") {
duration: 3000, Toastify({
gravity: "top", text: message,
position: "right", duration: 3000,
stopOnFocus: true, gravity: "top",
className: `alert alert-${alertType} shadow alert-dismissible`, position: "right",
close: true stopOnFocus: true,
}).showToast(); className: `alert alert-${alertType} shadow alert-dismissible`,
close: true
}).showToast();
}
} }
} });
}); });
</script> </script>
{% endblock %} {% endblock %}