catask/templates/inbox.html
2024-11-26 15:01:18 +03:00

169 lines
7.9 KiB
HTML

{% extends 'base.html' %}
{% block title %}Inbox {% if len(questions) > 0 %}({{ len(questions) }}){% endif %}{% endblock %}
{% set inboxLink = 'active' %}
{% block additionalHeadItems %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/toastify.css') }}">
{% endblock %}
{% block content %}
{% if questions != [] %}
<h3 class="fs-4"><span id="question-count-inbox">{{ len(questions) }}</span> <span class="fw-light">question(s)</span></h3>
<div class="row">
{% for question in questions %}
<div class="col-lg-8 m-auto">
<div class="card mb-3 mt-3 alert-placeholder question" id="question-{{ question.id }}">
<div class="card-header">
<div class="d-flex justify-content-between align-items-center">
<h5 class="card-title mt-1 mb-1 markdown-content">
{% if question.from_who %}
{{ question.from_who | render_markdown }}
{% else %}
<i class="bi bi-incognito" data-bs-toggle="tooltip" data-bs-title="This question was asked anonymously" data-bs-placement="top"></i> {{ cfg.anonName }}
{% endif %}
</h5>
<h6 class="card-subtitle fw-light text-body-secondary">
{#
reserved for version 1.6.0 or later
{% if question.private %}
<span class="me-2"><i class="bi bi-lock"></i> <span class="fw-medium" data-bs-toggle="tooltip" data-bs-title="This question was asked privately">Private</span></span>
{% endif %}
#}
<span data-bs-toggle="tooltip" data-bs-title="{{ question.creation_date.strftime('%B %d, %Y %H:%M') }}">{{ formatRelativeTime(str(question.creation_date)) }}</span>
</h6>
</div>
<div class="card-text markdown-content">
{% if question.cw %}
<p class="text-center mb-2 fw-bold cw-text">{{ question.cw }}</p>
<div class="collapse question-cw" id="question-cw-{{ question.id }}">
{{ question.content | render_markdown }}
</div>
<button class="z-0 cw-btn btn btn-sm btn-secondary shadow text-center w-100 sticky-bottom" type="button" data-bs-toggle="collapse" data-bs-target="#question-cw-{{ question.id }}" aria-expanded="false" aria-controls="question-cw-{{ question.id }}">
<span class="fw-medium cw-btn-text">Show content</span>
<span class="text-body-secondary cw-btn-chars">({{ len(question.content) }} characters)</span>
</button>
{% else %}
{{ question.content | render_markdown }}
{% endif %}
</div>
</div>
<div class="card-body">
<form hx-trigger="click from:#answer-btn-{{ question.id }}, keyup[ctrlKey&&key=='Enter']" hx-disabled-elt="find button[type=submit]" hx-post="{{ url_for('api.addAnswer', question_id=question.id) }}" hx-target="#question-{{ question.id }}" hx-swap="none">
<div class="form-group d-sm-grid d-md-block gap-2">
<div class="collapse" id="cw-{{ question.id }}-collapse">
<div class="form-floating mb-2">
<input class="form-control" type="text" name="cw" id="cw" placeholder="Content warning">
<label for="cw">Content warning</label>
</div>
</div>
<div class="form-floating">
<textarea class="form-control mb-2" required name="answer" id="answer-{{ question.id }}" placeholder="Write your answer..."></textarea>
<label for="answer-{{ question.id }}">Write your answer...</label>
</div>
<div class="d-sm-flex justify-content-between align-items-center">
<button class="btn btn-sm btn-outline-secondary mb-2" type="button" data-bs-toggle="collapse" data-bs-target="#cw-{{ question.id }}-collapse" aria-expanded="false" aria-controls="cw-{{ question.id }}-collapse">
<i class="bi bi-plus-lg me-1"></i> Add CW
</button>
<div class="d-flex flex-column flex-sm-row-reverse gap-2">
<button type="submit" class="btn btn-primary" id="answer-btn-{{ question.id }}">
<span class="spinner-border spinner-border-sm htmx-indicator" aria-hidden="true"></span>
<span class="visually-hidden" role="status">Loading...</span>
Answer
</button>
{% if not cfg.noDeleteConfirm %}
<button type="button" class="btn btn-outline-danger" data-bs-toggle="modal" data-bs-target="#question-{{ question.id }}-modal">Delete</button>
{% else %}
<button type="button" class="btn btn-outline-danger" hx-delete="{{ url_for('api.deleteQuestion', question_id=question.id) }}" hx-target="#question-{{ question.id }}" hx-swap="none">Delete</button>
{% endif %}
</div>
</div>
</div>
</form>
</div>
</div>
{% if not cfg.noDeleteConfirm %}
<div class="modal fade" id="question-{{ question.id }}-modal" tabindex="-1" aria-labelledby="q-{{ question.id }}-modal-label" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header border-0">
<h1 class="modal-title fs-5 fw-normal" id="q-{{ question.id }}-modal-label">Confirmation</h1>
<button type="button" class="btn-close d-flex align-items-center fs-5" data-bs-dismiss="modal" aria-label="Close"><i class="bi bi-x-lg"></i></button>
</div>
<div class="modal-body pt-0 pb-0">
<p>Are you sure you want to delete this question?</p>
</div>
<div class="modal-footer pt-1 border-0">
<button type="button" class="btn btn-outline-secondary flex-fill flex-sm-grow-0" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger flex-fill flex-sm-grow-0" data-bs-dismiss="modal" hx-delete="{{ url_for('api.deleteQuestion', question_id=question.id) }}" hx-target="#question-{{ question.id }}" hx-swap="none">Confirm</button>
</div>
</div>
</div>
</div>
{% endif %}
</div>
{% endfor %}
</div>
{% else %}
<h2 class="text-center mt-5">Inbox is currently empty.</h2>
{% endif %}
{% endblock %}
{% block scripts %}
<script src="{{ url_for('static', filename='js/toastify.min.js') }}"></script>
<script>
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl));
document.addEventListener('DOMContentLoaded', function () {
const collapseElements = document.querySelectorAll('.collapse.question-cw');
const toggleButtons = document.querySelectorAll('.cw-btn');
const cwTexts = document.querySelectorAll('.cw-text');
collapseElements.forEach(function (collapseElement, index) {
let toggleButton = toggleButtons[index];
let cwText = cwTexts[index];
let buttonText = toggleButton.querySelector('.cw-btn-text');
let buttonCharsText = toggleButton.querySelector('.cw-btn-chars');
collapseElement.addEventListener('show.bs.collapse', function () {
buttonText.textContent = 'Hide content';
buttonCharsText.classList.add('d-none');
cwText.classList.remove('text-center');
cwText.classList.remove('fw-bold');
});
collapseElement.addEventListener('hide.bs.collapse', function () {
buttonText.textContent = 'Show content';
buttonCharsText.classList.remove('d-none');
cwText.classList.add('text-center');
cwText.classList.add('fw-bold');
});
});
});
document.addEventListener('htmx:afterRequest', function(event) {
const jsonResponse = event.detail.xhr.response;
if (jsonResponse) {
const parsed = JSON.parse(jsonResponse);
const alertType = event.detail.successful ? 'success' : 'danger';
msgType = event.detail.successful ? parsed.message : parsed.error;
const targetElementId = event.detail.target.id;
if (targetElementId != "question-count") {
document.getElementById(targetElementId).outerHTML = '';
Toastify({
text: msgType,
duration: 3000,
gravity: "top",
position: "right",
stopOnFocus: true,
className: `alert alert-${alertType} shadow alert-dismissible`,
close: true
}).showToast();
const questions = document.querySelectorAll('.question');
const count = questions.length;
document.getElementById('question-count-inbox').textContent = count;
document.title = `Inbox (${count}) | {{ const.appName }}`;
}
}
})
</script>
{% endblock %}