feat(sdk): add localized social links to info modal

Build and render social links in the info modal using localized
site content. Detect the preferred locale (localStorage or document
lang, fallback to 'en') and fetch content via getSiteContent(locale).
Parse keys social.<id>.(url|text|icon|order), apply defaults
(labels and ordering via DEFAULT_SOCIAL_ORDER), support icons, and
render with separators.

Cache per-locale rendering using data attributes to avoid
unnecessary rebuilds, and invoke it from injectInfoSections so
links appear when the modal opens.
This commit is contained in:
2025-10-06 15:26:45 -07:00
parent f746913297
commit cff7f0e835
+164
View File
@@ -106,6 +106,8 @@
let infoModalObserver = null;
let shopModalObserver = null;
const DEFAULT_SOCIAL_ORDER = ['discord', 'twitter', 'bluesky', 'instagram', 'youtube', 'tiktok', 'reddit', 'github'];
// Find the button container in the DOM
function findButtonContainer() {
// Look for the container with class "flex flex-col items-center gap-3"
@@ -301,6 +303,166 @@
return section;
}
function getPreferredLocale() {
if (typeof localStorage !== 'undefined') {
const storedLocale = localStorage.getItem('locale');
if (storedLocale) {
return storedLocale;
}
}
if (typeof document !== 'undefined' && document.documentElement && document.documentElement.lang) {
return document.documentElement.lang;
}
return 'en';
}
function getDefaultSocialOrder(id) {
const index = DEFAULT_SOCIAL_ORDER.indexOf(id);
return index === -1 ? DEFAULT_SOCIAL_ORDER.length + 10 : index;
}
function ensureSocialEntry(map, id) {
if (!map[id]) {
map[id] = { id, order: getDefaultSocialOrder(id) };
}
return map[id];
}
function formatSocialLabel(id) {
if (!id) return '';
return id.charAt(0).toUpperCase() + id.slice(1);
}
function buildSocialEntriesFromContent(content) {
const data = content || {};
const entries = {};
Object.entries(data).forEach(([key, value]) => {
if (!value) return;
const match = key.match(/^social\.([^.]+)\.(url|text|icon|order)$/);
if (!match) return;
const [, id, field] = match;
const entry = ensureSocialEntry(entries, id);
if (field === 'order') {
const numericOrder = Number(value);
if (!Number.isNaN(numericOrder)) {
entry.order = numericOrder;
}
} else {
entry[field] = value;
}
});
return Object.values(entries)
.filter(entry => entry.url)
.map(entry => {
if (!entry.text) {
entry.text = formatSocialLabel(entry.id);
}
return entry;
})
.sort((a, b) => {
if (a.order !== b.order) {
return a.order - b.order;
}
return a.id.localeCompare(b.id);
});
}
function createSocialLinkElement(entry) {
const link = document.createElement('a');
link.href = entry.url;
link.target = '_blank';
link.rel = 'noopener noreferrer';
link.className = 'link inline-flex items-center gap-1 text-nowrap';
link.setAttribute('data-social-id', entry.id);
if (entry.icon) {
const iconWrapper = document.createElement('span');
iconWrapper.innerHTML = entry.icon;
const iconElement = iconWrapper.firstElementChild;
if (iconElement) {
iconElement.classList.add('inline');
if (!iconElement.classList.contains('size-4')) {
iconElement.classList.add('size-4');
}
link.insertBefore(iconElement, link.firstChild);
}
}
const label = document.createElement('span');
label.textContent = entry.text || entry.id;
link.appendChild(label);
return link;
}
async function rebuildInfoModalSocialLinks(modalBox) {
try {
const contentContainer = modalBox.querySelector('div[class*="flex"]');
if (!contentContainer) return;
const firstSection = contentContainer.querySelector('section');
if (!firstSection) return;
let socialContainer = firstSection.querySelector('[data-furryplace-socials]');
if (!socialContainer) {
socialContainer = firstSection.querySelector('div.w-full.text-center.text-sm');
if (socialContainer) {
socialContainer.setAttribute('data-furryplace-socials', 'true');
} else {
socialContainer = document.createElement('div');
socialContainer.className = 'w-full text-center text-sm';
socialContainer.setAttribute('data-furryplace-socials', 'true');
firstSection.appendChild(socialContainer);
}
}
const locale = getPreferredLocale();
if (socialContainer.dataset.fpSocialLocale === locale && socialContainer.dataset.fpSocialLoaded === 'true') {
return;
}
const content = await getSiteContent(locale);
if (!content) return;
const socialEntries = buildSocialEntriesFromContent(content);
if (socialEntries.length === 0) {
socialContainer.innerHTML = '';
socialContainer.dataset.fpSocialLocale = locale;
socialContainer.dataset.fpSocialLoaded = 'false';
return;
}
socialContainer.innerHTML = '';
const paragraph = document.createElement('p');
paragraph.className = 'flex flex-wrap items-center justify-center gap-2';
socialEntries.forEach((entry, index) => {
const link = createSocialLinkElement(entry);
paragraph.appendChild(link);
if (index < socialEntries.length - 1) {
const separator = document.createElement('span');
separator.className = 'text-base-content/50';
separator.textContent = '|';
paragraph.appendChild(separator);
}
});
socialContainer.appendChild(paragraph);
socialContainer.dataset.fpSocialLocale = locale;
socialContainer.dataset.fpSocialLoaded = 'true';
} catch (error) {
console.warn('[FurryPlace SDK] Failed to rebuild info modal social links:', error);
}
}
// Inject custom sections into info modal
function injectInfoSections() {
const modalBox = findInfoModal();
@@ -347,6 +509,8 @@
}
}
});
rebuildInfoModalSocialLinks(modalBox);
}
// Watch for info modal opening