feat: add admin CLI, SEO meta tags, security headers, and production Makefile targets

This commit is contained in:
2026-05-11 03:30:51 +00:00
parent fda3a202dc
commit 140cceff64
49 changed files with 2116 additions and 188 deletions
+34 -43
View File
@@ -8,8 +8,10 @@ class Application {
this.initMessageSearch();
this.initNotificationDismiss();
this.initProfileEdit();
this.initProjectForm();
this.initFormDisable();
this.initContentRenderer();
this.initEmojiPickers();
}
loadCSS(href) {
@@ -38,7 +40,8 @@ class Application {
initModals() {
document.querySelectorAll("[data-modal]").forEach((trigger) => {
trigger.addEventListener("click", () => {
trigger.addEventListener("click", (e) => {
e.preventDefault();
const modalId = trigger.dataset.modal;
const modal = document.getElementById(modalId);
if (modal) {
@@ -53,12 +56,11 @@ class Application {
modal.classList.remove("visible");
}
});
const closeBtn = modal.querySelector(".modal-close");
if (closeBtn) {
modal.querySelectorAll(".modal-close").forEach((closeBtn) => {
closeBtn.addEventListener("click", () => {
modal.classList.remove("visible");
});
}
});
});
}
@@ -186,49 +188,38 @@ class Application {
});
}
initProjectForm() {
const platformsInput = document.getElementById("platforms-input");
if (!platformsInput) {
return;
}
const hiddenInput = document.getElementById("platforms");
const tagsContainer = document.getElementById("platforms-tags");
initContentRenderer() {
document.querySelectorAll("[data-render]").forEach((el) => {
contentRenderer.applyTo(el);
});
contentRenderer.highlightAll();
}
platformsInput.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
e.preventDefault();
const val = platformsInput.value.trim();
if (!val) {
return;
initEmojiPickers() {
document.querySelectorAll(".comment-form textarea, .emoji-picker-target").forEach((textarea) => {
if (textarea.dataset.emojiInitialized) return;
textarea.dataset.emojiInitialized = "true";
const btn = document.createElement("button");
btn.type = "button";
btn.className = "emoji-toggle-btn";
btn.title = "Add emoji";
btn.innerHTML = "\u{1F600}";
const picker = new EmojiPicker(textarea);
btn.addEventListener("click", () => picker.toggle());
const parent = textarea.parentElement;
if (parent) {
const actions = parent.querySelector(".comment-form-actions");
if (actions) {
actions.insertBefore(btn, actions.firstChild);
}
const tag = document.createElement("span");
tag.className = "platform-tag";
tag.textContent = val;
const remove = document.createElement("button");
remove.type = "button";
remove.textContent = "x";
remove.style.marginLeft = "4px";
remove.style.fontSize = "0.75rem";
remove.style.padding = "0";
remove.addEventListener("click", () => {
tag.remove();
updatePlatforms();
});
tag.appendChild(remove);
tagsContainer.appendChild(tag);
platformsInput.value = "";
updatePlatforms();
}
});
function updatePlatforms() {
const tags = tagsContainer.querySelectorAll(".platform-tag");
const values = Array.from(tags).map((t) =>
t.textContent.replace("x", "").trim()
);
hiddenInput.value = values.join(",");
}
}
}
const app = new Application();