Add in mark as reaad button to list view, use ajax to preform the action without page refresh.

This commit is contained in:
2025-11-02 09:11:48 +00:00
parent 61ad033520
commit f0fc70eac6
7 changed files with 212 additions and 27 deletions

View File

@@ -5,16 +5,33 @@
{{ if .Messages }}
<ul class="list-group mb-4">
{{ range .Messages }}
<li class="list-group-item d-flex justify-content-between align-items-center">
<li class="list-group-item d-flex justify-content-between align-items-center {{ if .IsRead }}read{{ end }}" data-msg-id="{{ .ID }}">
<div>
<a href="/account/messages/read?id={{ .ID }}" class="fw-bold text-dark">{{ .Subject }}</a><br>
<small class="text-muted">{{ .CreatedAt.Format "02 Jan 2006 15:04" }}</small>
</div>
<form method="POST" action="/account/messages/archive" class="m-0">
<input type="hidden" name="csrf_token" value="{{ $.CSRFToken }}">
<input type="hidden" name="id" value="{{ .ID }}">
<button type="submit" class="btn btn-sm btn-outline-secondary">Archive</button>
</form>
<div class="d-flex gap-2 align-items-center">
{{/* Archive form (existing) */}}
<form method="POST" action="/account/messages/archive" class="m-0">
<input type="hidden" name="csrf_token" value="{{ $.CSRFToken }}">
<input type="hidden" name="id" value="{{ .ID }}">
<button type="submit" class="btn btn-sm btn-outline-secondary">Archive</button>
</form>
{{/* Mark-read: only show when unread */}}
{{ if not .IsRead }}
<!-- Non-AJAX fallback form (submit will refresh) -->
<form method="POST" action="/account/messages/mark-read" class="m-0 d-inline-block mark-read-form">
<input type="hidden" name="csrf_token" value="{{ $.CSRFToken }}">
<input type="hidden" name="id" value="{{ .ID }}">
<button type="submit" class="btn btn-sm btn-outline-primary mark-read-btn"
data-msg-id="{{ .ID }}"
data-csrf="{{ $.CSRFToken }}">Mark read</button>
</form>
{{ end }}
</div>
</li>
{{ end }}
</ul>
@@ -49,4 +66,74 @@
<a href="/account/messages/archive" class="btn btn-outline-secondary ms-2">View Archived</a>
</div>
</div>
{{/* AJAX enhancement: unobtrusive — safe fallback to regular form when JS disabled */}}
<script>
;(function(){
// Ensure browser supports fetch + FormData; otherwise we fallback to regular form submit.
if (!window.fetch || !window.FormData) return;
// Helper to decrement topbar message count badge (assumes badge element id="message-count")
function decrementMessageCount() {
var el = document.getElementById('message-count');
if (!el) return;
var current = parseInt(el.textContent || el.innerText || '0', 10) || 0;
var next = Math.max(0, current - 1);
if (next <= 0) {
// remove badge or hide it
el.remove();
} else {
el.textContent = String(next);
}
}
// Handle clicks on mark-read buttons, submit via fetch, update DOM
document.addEventListener('click', function(e){
var btn = e.target.closest('.mark-read-btn');
if (!btn) return;
// Prevent the default form POST (non-AJAX fallback)
e.preventDefault();
var msgID = btn.dataset.msgId;
var csrf = btn.dataset.csrf;
if (!msgID) {
// fallback to normal submit if something's wrong
var frm = btn.closest('form');
if (frm) frm.submit();
return;
}
// Build urlencoded body like a regular form
var body = new URLSearchParams();
body.append('id', msgID);
if (csrf) body.append('csrf_token', csrf);
fetch('/account/messages/mark-read', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: body.toString(),
credentials: 'same-origin'
}).then(function(resp){
if (resp.ok) {
// UI update: remove the mark-read button, give item a .read class, update topbar count
var li = document.querySelector('li[data-msg-id="' + msgID + '"]');
if (li) {
li.classList.add('read');
// remove any mark-read form/button inside
var form = li.querySelector('.mark-read-form');
if (form) form.remove();
}
decrementMessageCount();
} else {
// If server returned non-2xx, fall back to full reload to show flash
resp.text().then(function(){ window.location.reload(); }).catch(function(){ window.location.reload(); });
}
}).catch(function(){ window.location.reload(); });
}, false);
})();
</script>
{{ end }}

View File

@@ -6,17 +6,63 @@
<hr>
<p>{{ .Message.Body }}</p>
<form method="POST" action="/account/messages/archive" class="d-inline">
<input type="hidden" name="csrf_token" value="{{ $.CSRFToken }}">
<input type="hidden" name="id" value="{{ .Message.ID }}">
<button type="submit" class="btn btn-outline-danger mt-3">Archive</button>
</form>
<div class="mt-4">
<button id="mark-read-btn" data-id="{{ .Message.ID }}" class="btn btn-outline-success">Mark As Read</button>
<a href="/account/messages" class="btn btn-secondary mt-3">Back to Inbox</a>
<form method="POST" action="/account/messages/archive" class="d-inline">
<input type="hidden" name="csrf_token" value="{{ $.CSRFToken }}">
<input type="hidden" name="id" value="{{ .Message.ID }}">
<button type="submit" class="btn btn-outline-danger">Archive</button>
</form>
<a href="/account/messages" class="btn btn-secondary">Back to Inbox</a>
</div>
{{ else }}
<div class="alert alert-danger text-center">
Message not found or access denied.
</div>
{{ end }}
</div>
<script>
document.addEventListener("DOMContentLoaded", function () {
const btn = document.getElementById("mark-read-btn");
if (!btn) return;
btn.addEventListener("click", async function () {
const id = this.dataset.id;
const res = await fetch("/account/messages/mark-read", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: new URLSearchParams({
id: id,
csrf_token: "{{ $.CSRFToken }}"
})
});
if (res.ok) {
this.classList.remove("btn-outline-success");
this.classList.add("btn-success");
this.textContent = "Marked As Read ✔";
const badge = document.getElementById("message-count");
if (badge) {
let count = parseInt(badge.textContent);
if (!isNaN(count)) {
count = Math.max(count - 1, 0);
if (count === 0) {
badge.remove();
} else {
badge.textContent = count;
}
}
}
} else {
alert("Failed to mark as read.");
}
});
});
</script>
{{ end }}