fix: resolve tag autocomplete click bug and display name fallback

Tag autocomplete suggestions were silently broken by CSP (script-src
'self') which blocks inline event handlers. Replaced onclick attributes
with data-tag-name + delegated mousedown/touchend listeners in app.js.
Also changed hx-params="*" to hx-params="none" to avoid sending
unrelated form fields to the search endpoint.

Display name in "av <name>" on fave cards was empty for users without
a custom display name. Changed SQL queries to use
COALESCE(NULLIF(u.display_name, ''), u.username) for automatic fallback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ole-Morten Duesund 2026-04-04 00:17:38 +02:00
commit 9c3ca14578
4 changed files with 29 additions and 9 deletions

View file

@ -43,7 +43,7 @@ func (s *FaveStore) GetByID(id int64) (*model.Fave, error) {
var createdAt, updatedAt string
err := s.db.QueryRow(
`SELECT f.id, f.user_id, f.description, f.url, f.image_path, f.privacy,
f.created_at, f.updated_at, u.username, u.display_name
f.created_at, f.updated_at, u.username, COALESCE(NULLIF(u.display_name, ''), u.username)
FROM faves f
JOIN users u ON u.id = f.user_id
WHERE f.id = ?`, id,
@ -97,7 +97,7 @@ func (s *FaveStore) ListByUser(userID int64, limit, offset int) ([]*model.Fave,
rows, err := s.db.Query(
`SELECT f.id, f.user_id, f.description, f.url, f.image_path, f.privacy,
f.created_at, f.updated_at, u.username, u.display_name
f.created_at, f.updated_at, u.username, COALESCE(NULLIF(u.display_name, ''), u.username)
FROM faves f
JOIN users u ON u.id = f.user_id
WHERE f.user_id = ?
@ -126,7 +126,7 @@ func (s *FaveStore) ListPublicByUser(userID int64, limit, offset int) ([]*model.
rows, err := s.db.Query(
`SELECT f.id, f.user_id, f.description, f.url, f.image_path, f.privacy,
f.created_at, f.updated_at, u.username, u.display_name
f.created_at, f.updated_at, u.username, COALESCE(NULLIF(u.display_name, ''), u.username)
FROM faves f
JOIN users u ON u.id = f.user_id
WHERE f.user_id = ? AND f.privacy = 'public'
@ -153,7 +153,7 @@ func (s *FaveStore) ListPublic(limit, offset int) ([]*model.Fave, int, error) {
rows, err := s.db.Query(
`SELECT f.id, f.user_id, f.description, f.url, f.image_path, f.privacy,
f.created_at, f.updated_at, u.username, u.display_name
f.created_at, f.updated_at, u.username, COALESCE(NULLIF(u.display_name, ''), u.username)
FROM faves f
JOIN users u ON u.id = f.user_id
WHERE f.privacy = 'public'
@ -185,7 +185,7 @@ func (s *FaveStore) ListByTag(tagName string, limit, offset int) ([]*model.Fave,
rows, err := s.db.Query(
`SELECT f.id, f.user_id, f.description, f.url, f.image_path, f.privacy,
f.created_at, f.updated_at, u.username, u.display_name
f.created_at, f.updated_at, u.username, COALESCE(NULLIF(u.display_name, ''), u.username)
FROM faves f
JOIN users u ON u.id = f.user_id
JOIN fave_tags ft ON ft.fave_id = f.id

View file

@ -70,6 +70,26 @@
// --- Tag autocomplete combobox pattern ---
var activeIndex = -1;
// Delegated event handler for tag suggestion selection.
// Uses mousedown (not click) to fire before blur removes the element.
// Uses touchend for mobile support. Both prevent default to keep
// the tags input focused. Inline handlers (onclick/onmousedown) are
// blocked by CSP script-src 'self', so we must use addEventListener.
document.addEventListener("mousedown", function (event) {
var li = event.target.closest("[data-tag-name]");
if (li) {
event.preventDefault();
addTag(null, li.getAttribute("data-tag-name"));
}
});
document.addEventListener("touchend", function (event) {
var li = event.target.closest("[data-tag-name]");
if (li) {
event.preventDefault();
addTag(null, li.getAttribute("data-tag-name"));
}
});
// Handle keyboard navigation in the tag suggestions listbox.
document.addEventListener("keydown", function (event) {
var input = document.getElementById("tags");
@ -152,7 +172,7 @@
}
// Add a selected tag to the tag input.
window.addTag = function (element, tagName) {
function addTag(element, tagName) {
var input = document.getElementById("tags");
if (!input) return;
@ -162,7 +182,7 @@
input.focus();
closeSuggestions();
};
}
function getCookie(name) {
var match = document.cookie.match(new RegExp("(^| )" + name + "=([^;]+)"));

View file

@ -64,7 +64,7 @@
hx-get="{{basePath}}/tags/search"
hx-trigger="keyup changed delay:300ms"
hx-target="#tag-suggestions"
hx-params="*"
hx-params="none"
hx-vals='{"q": ""}'>
<small id="tags-help">Skriv for å søke i eksisterende merkelapper. Maks {{maxTags}} stk. Bruk piltaster for å velge.</small>
</label>

View file

@ -2,6 +2,6 @@
id="tag-option-{{$i}}"
class="tag-suggestion"
aria-selected="false"
onclick="addTag(this, '{{$tag.Name}}')"
data-tag-name="{{$tag.Name}}"
tabindex="-1">{{$tag.Name}}</li>
{{end}}