Update static/js/09-profile.js

This commit is contained in:
admin 2026-10-08 00:45:25 +00:00
parent f0d7f26c63
commit ca7eae57d2

View File

@ -1,6 +1,5 @@
// static/js/09-profile.js // static/js/09-profile.js
"use strict"; "use strict";
window.Glow = window.Glow || {}; window.Glow = window.Glow || {};
Glow.escapeHtml = value => Glow.escapeHtml = value =>
@ -13,16 +12,13 @@ Glow.escapeHtml = value =>
Glow.renderProfileBody = profile => { Glow.renderProfileBody = profile => {
const wrap = document.createElement("div"); const wrap = document.createElement("div");
wrap.className = "profile-rendered"; wrap.className = "profile-rendered";
const content = String(profile?.bio_html || "").trim(); const content = String(profile?.bio_html || "").trim();
if (!content) { if (!content) {
const empty = document.createElement("div"); const empty = document.createElement("div");
empty.className = "profile-empty"; empty.className = "profile-empty";
empty.textContent = "No profile bio yet."; empty.textContent = "No profile bio yet.";
return empty; return empty;
} }
wrap.innerHTML = content; wrap.innerHTML = content;
return wrap; return wrap;
}; };
@ -31,11 +27,9 @@ Glow.openProfile = async username => {
Glow.state.activeView = "profile"; Glow.state.activeView = "profile";
Glow.state.activeProfile = null; Glow.state.activeProfile = null;
Glow.updateViewUI(); Glow.updateViewUI();
const box = Glow.els.profileBox; const box = Glow.els.profileBox;
box.setAttribute("aria-busy", "true"); box.setAttribute("aria-busy", "true");
box.replaceChildren(); box.replaceChildren();
try { try {
const data = await Glow.apiGetPublicProfile(username); const data = await Glow.apiGetPublicProfile(username);
Glow.state.activeProfile = data.profile; Glow.state.activeProfile = data.profile;
@ -44,14 +38,11 @@ Glow.openProfile = async username => {
box.replaceChildren(); box.replaceChildren();
const state = document.createElement("div"); const state = document.createElement("div");
state.className = "empty-state"; state.className = "empty-state";
const card = document.createElement("div"); const card = document.createElement("div");
card.className = "empty-card"; card.className = "empty-card";
card.textContent = error.message || "Could not load profile."; card.textContent = error.message || "Could not load profile.";
state.append(card); state.append(card);
box.append(state); box.append(state);
Glow.showToast(error.message || "Could not load profile.", "error"); Glow.showToast(error.message || "Could not load profile.", "error");
} finally { } finally {
box.setAttribute("aria-busy", "false"); box.setAttribute("aria-busy", "false");
@ -63,15 +54,12 @@ Glow.openMyProfile = async (edit = false) => {
Glow.showToast("Sign in to view your profile.", "error"); Glow.showToast("Sign in to view your profile.", "error");
return; return;
} }
Glow.state.activeView = "profile"; Glow.state.activeView = "profile";
Glow.state.activeProfile = null; Glow.state.activeProfile = null;
Glow.updateViewUI(); Glow.updateViewUI();
const box = Glow.els.profileBox; const box = Glow.els.profileBox;
box.setAttribute("aria-busy", "true"); box.setAttribute("aria-busy", "true");
box.replaceChildren(); box.replaceChildren();
try { try {
const data = await Glow.apiGetMyProfile(); const data = await Glow.apiGetMyProfile();
Glow.state.activeProfile = data.profile; Glow.state.activeProfile = data.profile;
@ -83,30 +71,45 @@ Glow.openMyProfile = async (edit = false) => {
} }
}; };
Glow.uploadBannerForProfile = async file => {
const result = await Glow.uploadAttachment(file);
return result?.attachment || null;
};
Glow.renderProfileView = (editing = false) => { Glow.renderProfileView = (editing = false) => {
const box = Glow.els.profileBox; const box = Glow.els.profileBox;
const profile = Glow.state.activeProfile; const profile = Glow.state.activeProfile;
box.replaceChildren(); box.replaceChildren();
if (!profile) { if (!profile) {
const state = document.createElement("div"); const state = document.createElement("div");
state.className = "empty-state"; state.className = "empty-state";
const card = document.createElement("div"); const card = document.createElement("div");
card.className = "empty-card"; card.className = "empty-card";
card.textContent = "Profile not found."; card.textContent = "Profile not found.";
state.append(card); state.append(card);
box.append(state); box.append(state);
return; return;
} }
box.style.background =
profile.profile_background_color || "transparent";
const card = document.createElement("div"); const card = document.createElement("div");
card.className = "profile-card"; card.className = "profile-card";
const banner = document.createElement("div"); const banner = document.createElement("div");
banner.className = "profile-banner"; banner.className = "profile-banner";
if (profile.profile_banner_color) {
banner.style.background =
`linear-gradient(135deg, ${profile.profile_banner_color}, transparent 72%), ${profile.profile_banner_color}`;
}
if (profile.banner_image_url) {
banner.style.backgroundImage =
`linear-gradient(135deg, rgba(0,0,0,0.18), rgba(0,0,0,0.04)), url("${profile.banner_image_url}")`;
banner.style.backgroundSize = "cover";
banner.style.backgroundPosition = "center";
}
const content = document.createElement("div"); const content = document.createElement("div");
content.className = "profile-content"; content.className = "profile-content";
@ -122,7 +125,6 @@ Glow.renderProfileView = (editing = false) => {
const actions = document.createElement("div"); const actions = document.createElement("div");
actions.className = "profile-actions"; actions.className = "profile-actions";
if (profile.is_self) { if (profile.is_self) {
const editButton = document.createElement("button"); const editButton = document.createElement("button");
editButton.className = "button"; editButton.className = "button";
@ -139,10 +141,8 @@ Glow.renderProfileView = (editing = false) => {
settingsButton.addEventListener("click", () => { settingsButton.addEventListener("click", () => {
Glow.openSettings?.(); Glow.openSettings?.();
}); });
actions.append(editButton, settingsButton); actions.append(editButton, settingsButton);
} }
header.append(avatar, actions); header.append(avatar, actions);
const name = document.createElement("div"); const name = document.createElement("div");
@ -172,7 +172,11 @@ Glow.renderProfileView = (editing = false) => {
statusBadge.className = "profile-badge"; statusBadge.className = "profile-badge";
statusBadge.textContent = profile.status; statusBadge.textContent = profile.status;
badges.append(roleBadge, statusBadge); const presenceBadge = document.createElement("span");
presenceBadge.className = "profile-badge";
presenceBadge.textContent = profile.presence_mode || "online";
badges.append(roleBadge, statusBadge, presenceBadge);
const grid = document.createElement("div"); const grid = document.createElement("div");
grid.className = "profile-grid"; grid.className = "profile-grid";
@ -182,38 +186,38 @@ Glow.renderProfileView = (editing = false) => {
} else { } else {
const bioSection = document.createElement("section"); const bioSection = document.createElement("section");
bioSection.className = "profile-section"; bioSection.className = "profile-section";
const bioTitle = document.createElement("h3"); const bioTitle = document.createElement("h3");
bioTitle.textContent = "Bio"; bioTitle.textContent = "Bio";
bioSection.append(bioTitle, Glow.renderProfileBody(profile)); bioSection.append(bioTitle, Glow.renderProfileBody(profile));
const metaSection = document.createElement("section"); const metaSection = document.createElement("section");
metaSection.className = "profile-section"; metaSection.className = "profile-section";
const metaTitle = document.createElement("h3"); const metaTitle = document.createElement("h3");
metaTitle.textContent = "Profile"; metaTitle.textContent = "Profile";
const metaList = document.createElement("dl"); const metaList = document.createElement("dl");
metaList.className = "profile-meta-list"; metaList.className = "profile-meta-list";
const addMeta = (label, value) => { const addMeta = (label, value) => {
const dt = document.createElement("dt"); const dt = document.createElement("dt");
dt.textContent = label; dt.textContent = label;
const dd = document.createElement("dd"); const dd = document.createElement("dd");
dd.textContent = value || "—"; dd.textContent = value || "—";
metaList.append(dt, dd); metaList.append(dt, dd);
}; };
addMeta("Display name", profile.display_name || profile.username); addMeta("Display name", profile.display_name || profile.username);
addMeta("Status message", profile.status_message || "");
addMeta("Presence mode", profile.presence_mode || "online");
addMeta("Website", profile.website || ""); addMeta("Website", profile.website || "");
addMeta("Location", profile.location || ""); addMeta("Location", profile.location || "");
addMeta( addMeta(
"Joined", "Joined",
profile.created_at ? new Date(profile.created_at).toLocaleDateString() : "" profile.created_at ? new Date(profile.created_at).toLocaleDateString() : ""
); );
addMeta(
"Last active",
profile.last_active_at ? new Date(profile.last_active_at).toLocaleString() : ""
);
addMeta("Bio mode", profile.bio_mode || "txt"); addMeta("Bio mode", profile.bio_mode || "txt");
addMeta("Name color", profile.name_color || "role default"); addMeta("Name color", profile.name_color || "role default");
@ -221,7 +225,16 @@ Glow.renderProfileView = (editing = false) => {
grid.append(bioSection, metaSection); grid.append(bioSection, metaSection);
} }
content.append(header, name, handle, badges, grid); content.append(header, name, handle);
if (profile.status_message) {
const statusMessage = document.createElement("div");
statusMessage.className = "profile-status-message";
statusMessage.textContent = profile.status_message;
content.append(statusMessage);
}
content.append(badges, grid);
card.append(banner, content); card.append(banner, content);
box.append(card); box.append(card);
}; };
@ -229,7 +242,6 @@ Glow.renderProfileView = (editing = false) => {
Glow.buildProfileEditor = profile => { Glow.buildProfileEditor = profile => {
const section = document.createElement("section"); const section = document.createElement("section");
section.className = "profile-section"; section.className = "profile-section";
const title = document.createElement("h3"); const title = document.createElement("h3");
title.textContent = "Edit profile"; title.textContent = "Edit profile";
@ -243,6 +255,13 @@ Glow.buildProfileEditor = profile => {
displayName.placeholder = "Display name"; displayName.placeholder = "Display name";
displayName.value = profile.display_name || ""; displayName.value = profile.display_name || "";
const statusMessage = document.createElement("input");
statusMessage.className = "field";
statusMessage.type = "text";
statusMessage.maxLength = 160;
statusMessage.placeholder = "Status message";
statusMessage.value = profile.status_message || "";
const website = document.createElement("input"); const website = document.createElement("input");
website.className = "field"; website.className = "field";
website.type = "url"; website.type = "url";
@ -257,9 +276,13 @@ Glow.buildProfileEditor = profile => {
location.placeholder = "Location"; location.placeholder = "Location";
location.value = profile.location || ""; location.value = profile.location || "";
const bannerInput = document.createElement("input");
bannerInput.className = "field";
bannerInput.type = "file";
bannerInput.accept = ".png,.jpg,.jpeg,.gif,.webp";
const modeRow = document.createElement("div"); const modeRow = document.createElement("div");
modeRow.className = "profile-mode-row"; modeRow.className = "profile-mode-row";
let selectedMode = profile.bio_mode || "txt"; let selectedMode = profile.bio_mode || "txt";
const body = document.createElement("textarea"); const body = document.createElement("textarea");
@ -273,14 +296,12 @@ Glow.buildProfileEditor = profile => {
button.className = `mode-pill${selectedMode === mode ? " active" : ""}`; button.className = `mode-pill${selectedMode === mode ? " active" : ""}`;
button.type = "button"; button.type = "button";
button.textContent = mode.toUpperCase(); button.textContent = mode.toUpperCase();
button.addEventListener("click", () => { button.addEventListener("click", () => {
selectedMode = mode; selectedMode = mode;
for (const el of modeRow.querySelectorAll(".mode-pill")) { for (const el of modeRow.querySelectorAll(".mode-pill")) {
el.classList.toggle("active", el.textContent.toLowerCase() === mode); el.classList.toggle("active", el.textContent.toLowerCase() === mode);
} }
}); });
modeRow.append(button); modeRow.append(button);
} }
@ -298,17 +319,28 @@ Glow.buildProfileEditor = profile => {
save.textContent = "Save profile"; save.textContent = "Save profile";
save.addEventListener("click", async () => { save.addEventListener("click", async () => {
try { try {
let bannerAttachmentId = profile.banner_attachment_id || null;
const file = bannerInput.files?.[0];
if (file) {
const uploaded = await Glow.uploadBannerForProfile(file);
bannerAttachmentId = uploaded?.id || bannerAttachmentId;
}
const data = await Glow.apiUpdateMyProfile({ const data = await Glow.apiUpdateMyProfile({
display_name: displayName.value, display_name: displayName.value,
status_message: statusMessage.value,
website: website.value, website: website.value,
location: location.value, location: location.value,
bio: body.value, bio: body.value,
bio_mode: selectedMode bio_mode: selectedMode,
banner_attachment_id: bannerAttachmentId
}); });
Glow.state.activeProfile = { Glow.state.activeProfile = {
...data.profile, ...data.profile,
name_color: Glow.state.settings?.name_color || profile.name_color || null name_color: Glow.state.settings?.name_color || profile.name_color || null,
profile_background_color: Glow.state.settings?.profile_background_color || null,
profile_banner_color: Glow.state.settings?.profile_banner_color || null
}; };
Glow.showToast("Profile updated."); Glow.showToast("Profile updated.");
Glow.renderProfileView(false); Glow.renderProfileView(false);
@ -323,17 +355,28 @@ Glow.buildProfileEditor = profile => {
preview.textContent = "Preview"; preview.textContent = "Preview";
preview.addEventListener("click", async () => { preview.addEventListener("click", async () => {
try { try {
let bannerAttachmentId = profile.banner_attachment_id || null;
const file = bannerInput.files?.[0];
if (file) {
const uploaded = await Glow.uploadBannerForProfile(file);
bannerAttachmentId = uploaded?.id || bannerAttachmentId;
}
const data = await Glow.apiUpdateMyProfile({ const data = await Glow.apiUpdateMyProfile({
display_name: displayName.value, display_name: displayName.value,
status_message: statusMessage.value,
website: website.value, website: website.value,
location: location.value, location: location.value,
bio: body.value, bio: body.value,
bio_mode: selectedMode bio_mode: selectedMode,
banner_attachment_id: bannerAttachmentId
}); });
Glow.state.activeProfile = { Glow.state.activeProfile = {
...data.profile, ...data.profile,
name_color: Glow.state.settings?.name_color || profile.name_color || null name_color: Glow.state.settings?.name_color || profile.name_color || null,
profile_background_color: Glow.state.settings?.profile_background_color || null,
profile_banner_color: Glow.state.settings?.profile_banner_color || null
}; };
Glow.renderProfileView(false); Glow.renderProfileView(false);
} catch (error) { } catch (error) {
@ -342,8 +385,17 @@ Glow.buildProfileEditor = profile => {
}); });
actions.append(save, preview); actions.append(save, preview);
editor.append(displayName, website, location, modeRow, help, body, actions); editor.append(
displayName,
statusMessage,
website,
location,
bannerInput,
modeRow,
help,
body,
actions
);
section.append(title, editor); section.append(title, editor);
return section; return section;
}; };