// interests.js — wires the tag-combobox on the profile edit form.
//
// Server-rendered structure (see profile_edit.templ):
//
//
//
//
// coffee
//
//
// …
//
//
//
//
//
// JS owns the in-memory list, re-renders chips, and keeps the hidden input
// in sync (comma-joined) so the existing server-side parseInterests handler
// keeps working unchanged.
(function () {
"use strict";
function init(root) {
var chipsEl = root.querySelector("[data-tag-chips]");
var input = root.querySelector("[data-tag-input]");
var hidden = root.querySelector("[data-tag-hidden]");
if (!chipsEl || !input || !hidden) return;
var maxTags = parseInt(root.getAttribute("data-tag-max"), 10) || 30;
var maxLen = parseInt(root.getAttribute("data-tag-maxlen"), 10) || 64;
// Seed from server-rendered chips so a JS-failed initial render still
// matches what the user sees.
var tags = [];
var seen = Object.create(null);
Array.prototype.forEach.call(
chipsEl.querySelectorAll("[data-tag]"),
function (el) {
var v = (el.getAttribute("data-tag") || "").trim();
if (!v) return;
var key = v.toLowerCase();
if (seen[key]) return;
seen[key] = true;
tags.push(v);
}
);
function render() {
// Rebuild chips from the array — cheap, list is tiny.
var frag = document.createDocumentFragment();
tags.forEach(function (t) {
var chip = document.createElement("span");
chip.className = "tag-chip";
chip.setAttribute("data-tag", t);
var label = document.createElement("span");
label.className = "tag-chip-label";
label.textContent = t;
chip.appendChild(label);
var btn = document.createElement("button");
btn.type = "button";
btn.className = "tag-chip-remove";
btn.setAttribute("aria-label", "remove " + t);
btn.setAttribute("data-tag-remove", "");
btn.textContent = "\u00d7"; // ×
chip.appendChild(btn);
frag.appendChild(chip);
});
chipsEl.replaceChildren(frag);
hidden.value = tags.join(", ");
}
function addTag(raw) {
if (typeof raw !== "string") return false;
var v = raw.trim();
if (!v) return false;
// Reject embedded commas — splitting would be unexpected here.
// The user can paste multiple values; we handle that in onInput.
if (v.indexOf(",") !== -1) return false;
if (v.length > maxLen) v = v.slice(0, maxLen);
var key = v.toLowerCase();
if (tags.some(function (t) { return t.toLowerCase() === key; })) {
return false;
}
if (tags.length >= maxTags) return false;
tags.push(v);
return true;
}
function commitFromInput() {
var raw = input.value;
if (!raw) return;
// Allow paste-style "foo, bar, baz".
var parts = raw.split(",");
var any = false;
parts.forEach(function (p) {
if (addTag(p)) any = true;
});
if (any) render();
input.value = "";
}
input.addEventListener("keydown", function (e) {
if (e.key === "Enter" || e.key === ",") {
e.preventDefault();
commitFromInput();
return;
}
if (e.key === "Backspace" && input.value === "" && tags.length > 0) {
// Pop the last chip for fast correction.
tags.pop();
render();
}
});
// The `change` event fires when the user picks a value from the
//