1.2.0-alpha

This commit is contained in:
mst 2024-09-08 15:46:55 +03:00
parent e0925a3d1c
commit 30d1206ebc
24 changed files with 161 additions and 80 deletions

45
app.py
View file

@ -99,8 +99,15 @@ def render_markdown(text):
def index(): def index():
conn = func.connectToDb() conn = func.connectToDb()
cursor = conn.cursor(dictionary=True) cursor = conn.cursor(dictionary=True)
cursor.execute("SELECT * FROM questions WHERE answered=%s ORDER BY creation_date DESC", (True,))
questions = cursor.fetchall() cursor.execute("SELECT * FROM questions WHERE answered=%s AND pinned=%s ORDER BY creation_date DESC", (True, True))
pinned_questions = cursor.fetchall()
cursor.execute("SELECT * FROM questions WHERE answered=%s AND pinned=%s ORDER BY creation_date DESC", (True, False))
non_pinned_questions = cursor.fetchall()
questions = pinned_questions + non_pinned_questions
cursor.execute("SELECT * FROM answers ORDER BY creation_date DESC") cursor.execute("SELECT * FROM answers ORDER BY creation_date DESC")
answers = cursor.fetchall() answers = cursor.fetchall()
@ -123,7 +130,7 @@ def index():
def inbox(): def inbox():
conn = func.connectToDb() conn = func.connectToDb()
cursor = conn.cursor(dictionary=True) cursor = conn.cursor(dictionary=True)
cursor.execute("SELECT * FROM questions WHERE answered=%s", (False,)) cursor.execute("SELECT * FROM questions WHERE answered=%s ORDER BY creation_date DESC", (False,))
questions = cursor.fetchall() questions = cursor.fetchall()
cursor.close() cursor.close()
@ -146,13 +153,13 @@ def login():
admin_password = request.form.get('admin_password') admin_password = request.form.get('admin_password')
if admin_password == os.environ.get('ADMIN_PASSWORD'): if admin_password == os.environ.get('ADMIN_PASSWORD'):
session['logged_in'] = True session['logged_in'] = True
return redirect(url_for('admin.index')) return redirect(url_for('index'))
else: else:
flash("Wrong password", 'danger') flash("Wrong password", 'danger')
return redirect(url_for('admin.login')) return redirect(url_for('admin.login'))
else: else:
if logged_in: if logged_in:
return redirect('admin.index') return redirect('index')
else: else:
return render_template('admin/login.html') return render_template('admin/login.html')
@ -276,6 +283,34 @@ def returnToInbox():
return {'message': 'Successfully returned question to inbox.'}, 200 return {'message': 'Successfully returned question to inbox.'}, 200
@api_bp.route('/pin_question/', methods=['POST'])
def pinQuestion():
question_id = request.args.get('question_id', '')
if not question_id:
abort(400, "Missing 'question_id' attribute or 'question_id' is empty")
conn = func.connectToDb()
cursor = conn.cursor()
cursor.execute("UPDATE questions SET pinned=%s WHERE id=%s", (True, question_id))
cursor.close()
conn.close()
return {'message': 'Successfully pinned question.'}, 200
@api_bp.route('/unpin_question/', methods=['POST'])
def unpinQuestion():
question_id = request.args.get('question_id', '')
if not question_id:
abort(400, "Missing 'question_id' attribute or 'question_id' is empty")
conn = func.connectToDb()
cursor = conn.cursor()
cursor.execute("UPDATE questions SET pinned=%s WHERE id=%s", (False, question_id))
cursor.close()
conn.close()
return {'message': 'Successfully unpinned question.'}, 200
@api_bp.route('/add_answer/', methods=['POST']) @api_bp.route('/add_answer/', methods=['POST'])
@loginRequired @loginRequired
def addAnswer(): def addAnswer():

View file

@ -2,6 +2,6 @@ antiSpamFile = 'wordlist.txt'
blacklistFile = 'word_blacklist.txt' blacklistFile = 'word_blacklist.txt'
configFile = 'config.json' configFile = 'config.json'
appName = 'CatAsk' appName = 'CatAsk'
version = '1.1.0' version = '1.2.0'
# id (identifier) is to be interpreted as described in https://semver.org/#spec-item-9 # id (identifier) is to be interpreted as described in https://semver.org/#spec-item-9
version_id = '-alpha' version_id = '-alpha'

View file

@ -110,9 +110,12 @@ def renderMarkdown(text):
'i', 'i',
'br', 'br',
's', 's',
'del' 'del',
'a'
] ]
allowed_attrs = {} allowed_attrs = {
'a': 'href'
}
# hard_wrap=True means that newlines will be # hard_wrap=True means that newlines will be
# converted into <br> tags # converted into <br> tags
# #
@ -141,8 +144,8 @@ def generateMetadata(question=None, answer=None):
# if question is specified, generate metadata for that question # if question is specified, generate metadata for that question
if question and answer: if question and answer:
metadata.update({ metadata.update({
'title': trimContent(f"{question['content']}", 150) + " | " + cfg['instance']['title'], 'title': trimContent(question['content'], 150) + " | " + cfg['instance']['title'],
'description': trimContent(f"{answer['content']}", 150), 'description': trimContent(answer['content'], 150),
'url': cfg['instance']['fullBaseUrl'] + url_for('viewQuestion', question_id=question['id']), 'url': cfg['instance']['fullBaseUrl'] + url_for('viewQuestion', question_id=question['id']),
'image': cfg['instance']['image'] 'image': cfg['instance']['image']
}) })

View file

@ -11,7 +11,8 @@ CREATE TABLE IF NOT EXISTS questions (
creation_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, creation_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
content TEXT NOT NULL, content TEXT NOT NULL,
answered BOOLEAN NOT NULL DEFAULT FALSE, answered BOOLEAN NOT NULL DEFAULT FALSE,
answer_id INT answer_id INT,
pinned BOOLEAN NOT NULL DEFAULT FALSE
) ENGINE=InnoDB; ) ENGINE=InnoDB;
ALTER TABLE questions ALTER TABLE questions

View file

@ -25,7 +25,7 @@
--bs-primary-rgb: 127,98,240; --bs-primary-rgb: 127,98,240;
--bs-primary-subtle: color-mix(in srgb, var(--bs-primary) 10%, transparent); --bs-primary-subtle: color-mix(in srgb, var(--bs-primary) 10%, transparent);
--bs-danger-bg-subtle: #2c0b0e; --bs-danger-bg-subtle: #2c0b0e;
--bs-link-color: color-mix(in srgb, var(--bs-primary) 70%, white); --bs-link-color: color-mix(in srgb, var(--bs-primary) 60%, white);
} }
.btn { .btn {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 686 B

After

Width:  |  Height:  |  Size: 710 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -8,6 +8,9 @@
id="svg9" id="svg9"
sodipodi:docname="catask.svg" sodipodi:docname="catask.svg"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)" inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)"
inkscape:export-filename="catask-16.png"
inkscape:export-xdpi="4.0421052"
inkscape:export-ydpi="4.0421052"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@ -22,8 +25,8 @@
inkscape:pagecheckerboard="0" inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1" inkscape:deskcolor="#d1d1d1"
inkscape:zoom="0.48026316" inkscape:zoom="0.48026316"
inkscape:cx="346.68493" inkscape:cx="316.49315"
inkscape:cy="368.54794" inkscape:cy="533.04109"
inkscape:window-width="1280" inkscape:window-width="1280"
inkscape:window-height="962" inkscape:window-height="962"
inkscape:window-x="0" inkscape:window-x="0"
@ -42,7 +45,7 @@
d="M372.86 340.836C361.458 364.034 337.594 380 310 380H69.9999C42.4057 380 18.5407 364.033 7.13956 340.835C0.0540733 303.832 2.43122 265.23 21.5492 228.243C22.3857 226.625 24.2343 225.797 25.9907 226.281C61.5026 236.067 97.5669 264.887 131.247 340.717C131.983 342.374 133.752 343.367 135.546 343.105C160.424 339.463 177.96 339.001 190 339.001C202.04 339.001 219.576 339.463 244.454 343.105C246.248 343.367 248.017 342.375 248.753 340.717C282.433 264.887 318.497 236.067 354.009 226.281C355.765 225.797 357.614 226.625 358.45 228.243C377.568 265.23 379.945 303.833 372.86 340.836Z" d="M372.86 340.836C361.458 364.034 337.594 380 310 380H69.9999C42.4057 380 18.5407 364.033 7.13956 340.835C0.0540733 303.832 2.43122 265.23 21.5492 228.243C22.3857 226.625 24.2343 225.797 25.9907 226.281C61.5026 236.067 97.5669 264.887 131.247 340.717C131.983 342.374 133.752 343.367 135.546 343.105C160.424 339.463 177.96 339.001 190 339.001C202.04 339.001 219.576 339.463 244.454 343.105C246.248 343.367 248.017 342.375 248.753 340.717C282.433 264.887 318.497 236.067 354.009 226.281C355.765 225.797 357.614 226.625 358.45 228.243C377.568 265.23 379.945 303.833 372.86 340.836Z"
fill="#DA75FF" fill="#DA75FF"
id="path1" id="path1"
style="fill:#e59bff;fill-opacity:1" /> style="fill:#caacff;fill-opacity:1" />
<mask <mask
id="path-3-inside-1_603_9" id="path-3-inside-1_603_9"
fill="white"> fill="white">
@ -77,12 +80,14 @@
gradientUnits="userSpaceOnUse"> gradientUnits="userSpaceOnUse">
<stop <stop
stop-color="#CE95FF" stop-color="#CE95FF"
id="stop4" /> id="stop4"
offset="0"
style="stop-color:#9a69ce;stop-opacity:1;" />
<stop <stop
offset="1" offset="1"
stop-color="#8B52BC" stop-color="#8B52BC"
id="stop5" id="stop5"
style="stop-color:#6a3a93;stop-opacity:1;" /> style="stop-color:#6f42c1;stop-opacity:1;" />
</linearGradient> </linearGradient>
<linearGradient <linearGradient
id="paint1_linear_603_9" id="paint1_linear_603_9"
@ -95,13 +100,13 @@
<stop <stop
stop-color="#CE5AFF" stop-color="#CE5AFF"
id="stop6" id="stop6"
offset="0" offset="0.2899459"
style="stop-color:#ffffff;stop-opacity:1;" /> style="stop-color:#ffffff;stop-opacity:1;" />
<stop <stop
offset="1" offset="1"
stop-color="#C12FFF" stop-color="#C12FFF"
id="stop7" id="stop7"
style="stop-color:#d973ff;stop-opacity:1;" /> style="stop-color:#caacff;stop-opacity:1;" />
</linearGradient> </linearGradient>
<linearGradient <linearGradient
id="paint2_linear_603_9" id="paint2_linear_603_9"

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 686 B

After

Width:  |  Height:  |  Size: 710 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -42,7 +42,7 @@
<input type="hidden" id="lockInbox" name="lockInbox" value="{{ cfg.lockInbox }}"> <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> <label for="_lockInbox" class="form-check-label">Lock inbox and don't allow new questions</label>
</div> </div>
<div class="form-check mb-3"> <div class="form-check mb-2">
<input <input
class="form-check-input" class="form-check-input"
type="checkbox" type="checkbox"
@ -53,6 +53,17 @@
<input type="hidden" id="allowAnonQuestions" name="allowAnonQuestions" value="{{ cfg.allowAnonQuestions }}"> <input type="hidden" id="allowAnonQuestions" name="allowAnonQuestions" value="{{ cfg.allowAnonQuestions }}">
<label for="_allowAnonQuestions" class="form-check-label">Allow anonymous questions</label> <label for="_allowAnonQuestions" class="form-check-label">Allow anonymous questions</label>
</div> </div>
<div class="form-check mb-3">
<input
class="form-check-input"
type="checkbox"
name="_showQuestionCount"
id="_showQuestionCount"
value="{{ cfg.showQuestionCount }}"
{% if cfg.showQuestionCount == true %}checked{% endif %}>
<input type="hidden" id="showQuestionCount" name="showQuestionCount" value="{{ cfg.showQuestionCount }}">
<label for="_showQuestionCount" class="form-check-label">Show question count in homepage</label>
</div>
<div class="form-group mb-3"> <div class="form-group mb-3">
<button type="submit" class="btn btn-primary mt-3"> <button type="submit" class="btn btn-primary mt-3">
<span class="spinner-border spinner-border-sm htmx-indicator" aria-hidden="true"></span> <span class="spinner-border spinner-border-sm htmx-indicator" aria-hidden="true"></span>

View file

@ -41,21 +41,21 @@
<body class="ms-2 me-2 mb-2"> <body class="ms-2 me-2 mb-2">
<a class="visually-hidden-focusable" href="#main-content">Skip to content</a> <a class="visually-hidden-focusable" href="#main-content">Skip to content</a>
<div class="container-fluid"> <div class="container-fluid">
{% if logged_in %} <div class="d-flex {% if logged_in %}justify-content-between {% endif %}align-items-center mt-3 {% if logged_in %}mb-3{% endif %}">
<div class="d-flex justify-content-between align-items-center mt-3 {% if logged_in %}mb-3{% endif %}">
<ul class="nav nav-underline position-relative {% if not logged_in %}mb-3{% endif %}"> <ul class="nav nav-underline position-relative {% if not logged_in %}mb-3{% endif %}">
<li class="nav-item d-flex align-items-center"><a href="{{ url_for('index') }}"><img src="{{ url_for('static', filename='icons/catask.svg') }}" width="32" height="32"></a></li> <li class="nav-item d-flex align-items-center"><a href="{{ url_for('index') }}"><img src="{{ url_for('static', filename='icons/catask.svg') }}" width="32" height="32"></a></li>
<li class="nav-item"><a class="nav-link {{ homeLink }}" id="home-link" href="{{ url_for('index') }}">Home</a> <li class="nav-item"><a class="nav-link {{ homeLink }}" id="home-link" href="{{ url_for('index') }}">Home</a>
{% if logged_in %}
<li class="nav-item"><a class="nav-link {{ inboxLink }}" id="inbox-link" href="{{ url_for('inbox') }}">Inbox</a> <li class="nav-item"><a class="nav-link {{ inboxLink }}" id="inbox-link" href="{{ url_for('inbox') }}">Inbox</a>
<li class="nav-item"><a class="nav-link {{ adminLink }}" id="admin-link" href="{{ url_for('admin.index') }}">Admin</a></li> <li class="nav-item"><a class="nav-link {{ adminLink }}" id="admin-link" href="{{ url_for('admin.index') }}">Admin</a></li>
{% endif %}
</ul> </ul>
{% if logged_in %}
<ul class="nav nav-underline m-0"> <ul class="nav nav-underline m-0">
<li><a class="nav-link" href="{{ url_for('admin.logout') }}">Logout</a></li> <li><a class="nav-link" href="{{ url_for('admin.logout') }}">Logout</a></li>
</ul> </ul>
</div>
{% else %}
<div class="mt-3 mb-3" aria-hidden="true"></div>
{% endif %} {% endif %}
</div>
{% with messages = get_flashed_messages(with_categories=True) %} {% with messages = get_flashed_messages(with_categories=True) %}
{% if messages %} {% if messages %}
{% for category, message in messages %} {% for category, message in messages %}

View file

@ -3,11 +3,20 @@
{% set inboxLink = 'active' %} {% set inboxLink = 'active' %}
{% block content %} {% block content %}
{% if questions != [] %} {% if questions != [] %}
{% for question in questions %} <h3 class="fs-4">{{ len(questions) }} <span class="fw-light">question(s)</span></h3>
<div class="card mb-3 mt-3 alert-placeholder" id="question-{{ question.id }}"> <div class="row">
{% for question in questions %}
<div class="col-sm-8 m-auto">
<div class="card mb-3 mt-3 alert-placeholder" id="question-{{ question.id }}">
<div class="card-header"> <div class="card-header">
<div class="d-flex justify-content-between align-items-center"> <div class="d-flex justify-content-between align-items-center">
<h5 class="card-title mt-1 mb-1">{% if question.from_who %}{{ question.from_who }}{% else %}Anonymous{% endif %}</h5> <h5 class="card-title mt-1 mb-1">
{% if question.from_who %}
{{ question.from_who }}
{% 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" data-bs-toggle="tooltip" data-bs-title="{{ question.creation_date }}" data-bs-placement="top">{{ formatRelativeTime(str(question.creation_date)) }}</h6> <h6 class="card-subtitle fw-light text-body-secondary" data-bs-toggle="tooltip" data-bs-title="{{ question.creation_date }}" data-bs-placement="top">{{ formatRelativeTime(str(question.creation_date)) }}</h6>
</div> </div>
<div class="card-text markdown-content">{{ question.content | render_markdown }}</div> <div class="card-text markdown-content">{{ question.content | render_markdown }}</div>
@ -27,8 +36,8 @@
</div> </div>
</form> </form>
</div> </div>
</div> </div>
<div class="modal fade" id="question-{{ question.id }}-modal" tabindex="-1" aria-labelledby="q-{{ question.id }}-modal-label" aria-hidden="true"> <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-dialog modal-dialog-centered">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
@ -44,8 +53,10 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
{% endfor %}
</div> </div>
{% endfor %}
{% else %} {% else %}
<h2 class="text-center mt-5">Inbox is currently empty.</h2> <h2 class="text-center mt-5">Inbox is currently empty.</h2>
{% endif %} {% endif %}

View file

@ -40,6 +40,10 @@
</div> </div>
{% if combined %} {% if combined %}
<div class="col-sm-8"> <div class="col-sm-8">
{% if cfg.showQuestionCount == true %}
<h3 class="fs-4">{{ len(combined) }} <span class="fw-light">question(s)</span></h3>
{% endif %}
<div id="top-response-container"></div>
{% for item in combined %} {% for item in combined %}
<div class="card mt-3 mb-3" id="question-{{ item.question.id }}"> <div class="card mt-3 mb-3" id="question-{{ item.question.id }}">
<div class="card-header"> <div class="card-header">
@ -51,30 +55,41 @@
<i class="bi bi-incognito" data-bs-toggle="tooltip" data-bs-title="This question was asked anonymously" data-bs-placement="top"></i> {{ cfg.anonName }} <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 %} {% endif %}
</h5> </h5>
<h6 class="card-subtitle fw-light text-body-secondary" data-bs-toggle="tooltip" data-bs-title="{{ item.question.creation_date }}" data-bs-placement="top">{{ formatRelativeTime(str(item.question.creation_date)) }}</h6> <h6 class="card-subtitle fw-light text-body-secondary" data-bs-toggle="tooltip" data-bs-title="{{ item.question.creation_date.strftime("%B %d, %Y %H:%M") }}" data-bs-placement="top">{{ formatRelativeTime(str(item.question.creation_date)) }}</h6>
</div> </div>
<div class="card-text markdown-content">{{ item.question.content | render_markdown }}</div> <div class="card-text markdown-content">{{ item.question.content | render_markdown }}</div>
</div> </div>
<div class="card-body"> <div class="card-body">
<a href="{{ url_for('viewQuestion', question_id=item.question.id) }}" class="text-decoration-none text-reset">
{% for answer in item.answers %} {% for answer in item.answers %}
<div class="markdown-content">{{ answer.content }}</div> <div class="markdown-content">{{ answer.content | render_markdown }}</div>
</div> </div>
</a>
<div class="card-footer pt-0 pb-0 ps-3 pe-2 text-body-secondary d-flex justify-content-between align-items-center"> <div class="card-footer pt-0 pb-0 ps-3 pe-2 text-body-secondary d-flex justify-content-between align-items-center">
<span class="fs-6">answered {{ formatRelativeTime(str(answer.creation_date)) }}</span> <div>
<span class="fs-6" data-bs-toggle="tooltip" data-bs-title="{{ answer.creation_date.strftime("%B %d, %Y %H:%M") }}">answered {{ formatRelativeTime(str(answer.creation_date)) }}</span>
{% if item.question.pinned %}
<span class="ms-1"><i class="bi bi-pin"></i> <span class="fw-medium">Pinned</span></span>
{% endif %}
</div>
{% endfor %} {% endfor %}
<div class="d-flex align-items-center">
<a href="{{ url_for('viewQuestion', question_id=item.question.id) }}" class="btn pt-2 pb-2 text-body-secondary" data-bs-toggle="tooltip" data-bs-title="View question"><i class="bi bi-box-arrow-up-right"></i></a>
<div class="dropdown"> <div class="dropdown">
<button class="btn btn-sm pt-2 pb-2 no-arrow text-body-secondary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false"><i style="font-size: 1.2rem;" class="bi bi-three-dots"></i></button> <button class="btn btn-sm pt-2 pb-2 no-arrow text-body-secondary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false"><i style="font-size: 1.2rem;" class="bi bi-three-dots"></i></button>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><button class="dropdown-item" onclick="copy({{ item.question.id }})">Copy link</button></li> <li><button class="dropdown-item" onclick="copy({{ item.question.id }})">Copy link</button></li>
{% if logged_in %} {% if logged_in %}
{% if not item.question.pinned %}
<li><button class="dropdown-item" hx-post="{{ url_for('api.pinQuestion', question_id=item.question.id) }}" hx-target="#top-response-container" hx-swap="none">Pin</button></li>
{% else %}
<li><button class="dropdown-item" hx-post="{{ url_for('api.unpinQuestion', question_id=item.question.id) }}" hx-target="#top-response-container" hx-swap="none">Unpin</button></li>
{% endif %}
<li><button class="bg-hover-danger text-danger dropdown-item" hx-post="{{ url_for('api.returnToInbox', question_id=item.question.id) }}" hx-target="#question-{{ item.question.id }}" hx-swap="none">Return to inbox</button></li> <li><button class="bg-hover-danger text-danger dropdown-item" hx-post="{{ url_for('api.returnToInbox', question_id=item.question.id) }}" hx-target="#question-{{ item.question.id }}" hx-swap="none">Return to inbox</button></li>
{% endif %} {% endif %}
</ul> </ul>
</div> </div>
</div> </div>
</div> </div>
</div>
{% endfor %} {% endfor %}
</div> </div>
{% endif %} {% endif %}

View file

@ -2,7 +2,7 @@
{% block title %}"{{ trimContent(question.content, 15) }}" - "{{ trimContent(answer.content, 15) }}"{% endblock %} {% block title %}"{{ trimContent(question.content, 15) }}" - "{{ trimContent(answer.content, 15) }}"{% endblock %}
{% block content %} {% block content %}
<div class="row"> <div class="row">
<div class="col-sm-6 m-auto"> <div class="col-sm-8 m-auto">
<div class="card mt-2 mb-2" id="question-{{ question.id }}"> <div class="card mt-2 mb-2" id="question-{{ question.id }}">
<div class="card-header"> <div class="card-header">
<div class="d-flex justify-content-between align-items-center"> <div class="d-flex justify-content-between align-items-center">
@ -12,7 +12,7 @@
<div class="card-text markdown-content">{{ question.content | render_markdown }}</div> <div class="card-text markdown-content">{{ question.content | render_markdown }}</div>
</div> </div>
<div class="card-body"> <div class="card-body">
<p class="mb-0">{{ answer.content }}</p> <div class="markdown-content">{{ answer.content | render_markdown }}</div>
</div> </div>
<div class="card-footer pt-0 pb-0 ps-3 pe-2 text-body-secondary d-flex justify-content-between align-items-center"> <div class="card-footer pt-0 pb-0 ps-3 pe-2 text-body-secondary d-flex justify-content-between align-items-center">
<span class="fs-6">answered {{ formatRelativeTime(str(answer.creation_date)) }}</span> <span class="fs-6">answered {{ formatRelativeTime(str(answer.creation_date)) }}</span>