From cff7f0e835e8a73eb48fd59ce11dc5e61230efe6 Mon Sep 17 00:00:00 2001 From: Zack3D Date: Mon, 6 Oct 2025 15:26:45 -0700 Subject: [PATCH] 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..(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. --- frontend-backup/furryplace-sdk.js | 164 ++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) diff --git a/frontend-backup/furryplace-sdk.js b/frontend-backup/furryplace-sdk.js index 482d518..c6fc698 100644 --- a/frontend-backup/furryplace-sdk.js +++ b/frontend-backup/furryplace-sdk.js @@ -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