| const { menavExtractDomain, menavSanitizeClassList, menavSanitizeUrl } = require('../shared'); |
|
|
| |
| module.exports = function addElement(type, parentId, data) { |
| if (type === 'site') { |
| |
| const parent = document.querySelector(`[data-type="category"][data-name="${parentId}"]`); |
| if (!parent) return null; |
|
|
| |
| const sitesContainer = parent.querySelector('[data-container="sites"]'); |
| if (!sitesContainer) return null; |
|
|
| |
| let siteCardStyle = ''; |
| try { |
| const pageEl = parent.closest('.page'); |
| const pageId = pageEl && pageEl.id ? String(pageEl.id).trim() : ''; |
| const cfg = |
| window.MeNav && typeof window.MeNav.getConfig === 'function' |
| ? window.MeNav.getConfig() |
| : null; |
|
|
| let templateName = ''; |
|
|
| const pageTemplates = |
| cfg && cfg.data && cfg.data.pageTemplates && typeof cfg.data.pageTemplates === 'object' |
| ? cfg.data.pageTemplates |
| : null; |
|
|
| const templateFromMap = |
| pageTemplates && pageId && pageTemplates[pageId] |
| ? String(pageTemplates[pageId]).trim() |
| : ''; |
|
|
| |
| const legacyPageConfig = cfg && cfg.data && pageId ? cfg.data[pageId] : null; |
| const templateFromLegacy = |
| legacyPageConfig && legacyPageConfig.template |
| ? String(legacyPageConfig.template).trim() |
| : ''; |
|
|
| if (templateFromMap) { |
| templateName = templateFromMap; |
| } else if (templateFromLegacy) { |
| templateName = templateFromLegacy; |
| } |
|
|
| |
| if (templateName === 'projects') siteCardStyle = 'repo'; |
| } catch (e) { |
| siteCardStyle = ''; |
| } |
|
|
| |
| const newSite = document.createElement('a'); |
| newSite.className = siteCardStyle ? `site-card site-card-${siteCardStyle}` : 'site-card'; |
|
|
| const siteName = data.name || '未命名站点'; |
| const siteUrl = data.url || '#'; |
| const siteIcon = data.icon || 'fas fa-link'; |
| const siteDescription = data.description || (data.url ? menavExtractDomain(data.url) : ''); |
| const siteFaviconUrl = data && data.faviconUrl ? String(data.faviconUrl).trim() : ''; |
| const siteForceIconModeRaw = |
| data && data.forceIconMode ? String(data.forceIconMode).trim() : ''; |
| const siteForceIconMode = |
| siteForceIconModeRaw === 'manual' || siteForceIconModeRaw === 'favicon' |
| ? siteForceIconModeRaw |
| : ''; |
|
|
| const safeSiteUrl = menavSanitizeUrl(siteUrl, 'addElement(site).url'); |
| const safeSiteIcon = menavSanitizeClassList(siteIcon, 'addElement(site).icon'); |
|
|
| newSite.setAttribute('href', safeSiteUrl); |
| newSite.title = siteName + (siteDescription ? ' - ' + siteDescription : ''); |
| newSite.setAttribute( |
| 'data-tooltip', |
| siteName + (siteDescription ? ' - ' + siteDescription : '') |
| ); |
| if (/^https?:\/\//i.test(safeSiteUrl)) { |
| newSite.target = '_blank'; |
| newSite.rel = 'noopener'; |
| } |
|
|
| |
| newSite.setAttribute('data-type', 'site'); |
| newSite.setAttribute('data-name', siteName); |
| |
| newSite.setAttribute('data-url', String(data.url || '').trim()); |
| newSite.setAttribute('data-icon', safeSiteIcon); |
| if (siteFaviconUrl) newSite.setAttribute('data-favicon-url', siteFaviconUrl); |
| if (siteForceIconMode) newSite.setAttribute('data-force-icon-mode', siteForceIconMode); |
| newSite.setAttribute('data-description', siteDescription); |
|
|
| |
| if (siteCardStyle === 'repo') { |
| const repoHeader = document.createElement('div'); |
| repoHeader.className = 'repo-header'; |
|
|
| const repoIcon = document.createElement('i'); |
| repoIcon.className = `${safeSiteIcon || 'fas fa-code'} repo-icon`; |
| repoIcon.setAttribute('aria-hidden', 'true'); |
|
|
| const repoTitle = document.createElement('div'); |
| repoTitle.className = 'repo-title'; |
| repoTitle.textContent = siteName; |
|
|
| repoHeader.appendChild(repoIcon); |
| repoHeader.appendChild(repoTitle); |
|
|
| const repoDesc = document.createElement('div'); |
| repoDesc.className = 'repo-desc'; |
| repoDesc.textContent = siteDescription; |
|
|
| newSite.appendChild(repoHeader); |
| newSite.appendChild(repoDesc); |
|
|
| const hasStats = data && (data.language || data.stars || data.forks || data.issues); |
|
|
| if (hasStats) { |
| const repoStats = document.createElement('div'); |
| repoStats.className = 'repo-stats'; |
|
|
| if (data.language) { |
| const languageItem = document.createElement('div'); |
| languageItem.className = 'stat-item'; |
|
|
| const langDot = document.createElement('span'); |
| langDot.className = 'lang-dot'; |
| langDot.style.backgroundColor = data.languageColor || '#909296'; |
|
|
| languageItem.appendChild(langDot); |
| languageItem.appendChild(document.createTextNode(String(data.language))); |
| repoStats.appendChild(languageItem); |
| } |
|
|
| if (data.stars) { |
| const starsItem = document.createElement('div'); |
| starsItem.className = 'stat-item'; |
|
|
| const starIcon = document.createElement('i'); |
| starIcon.className = 'far fa-star'; |
| starIcon.setAttribute('aria-hidden', 'true'); |
| starsItem.appendChild(starIcon); |
| starsItem.appendChild(document.createTextNode(String(data.stars))); |
| repoStats.appendChild(starsItem); |
| } |
|
|
| if (data.forks) { |
| const forksItem = document.createElement('div'); |
| forksItem.className = 'stat-item'; |
|
|
| const forkIcon = document.createElement('i'); |
| forkIcon.className = 'fas fa-code-branch'; |
| forkIcon.setAttribute('aria-hidden', 'true'); |
| forksItem.appendChild(forkIcon); |
| forksItem.appendChild(document.createTextNode(String(data.forks))); |
| repoStats.appendChild(forksItem); |
| } |
|
|
| if (data.issues) { |
| const issuesItem = document.createElement('div'); |
| issuesItem.className = 'stat-item'; |
|
|
| const issueIcon = document.createElement('i'); |
| issueIcon.className = 'fas fa-exclamation-circle'; |
| issueIcon.setAttribute('aria-hidden', 'true'); |
| issuesItem.appendChild(issueIcon); |
| issuesItem.appendChild(document.createTextNode(String(data.issues))); |
| repoStats.appendChild(issuesItem); |
| } |
|
|
| newSite.appendChild(repoStats); |
| } |
| } else { |
| |
| const siteCardIcon = document.createElement('div'); |
| siteCardIcon.className = 'site-card-icon'; |
|
|
| const iconEl = document.createElement('i'); |
| iconEl.className = safeSiteIcon || 'fas fa-link'; |
| iconEl.setAttribute('aria-hidden', 'true'); |
|
|
| |
| siteCardIcon.appendChild(iconEl); |
|
|
| const titleEl = document.createElement('h3'); |
| titleEl.textContent = siteName; |
|
|
| const descEl = document.createElement('p'); |
| descEl.textContent = siteDescription; |
|
|
| newSite.appendChild(siteCardIcon); |
| newSite.appendChild(titleEl); |
| newSite.appendChild(descEl); |
|
|
| |
| try { |
| const cfg = |
| window.MeNav && typeof window.MeNav.getConfig === 'function' |
| ? window.MeNav.getConfig() |
| : null; |
| const iconsMode = |
| cfg && cfg.icons && cfg.icons.mode ? String(cfg.icons.mode).trim() : 'favicon'; |
| const iconsRegion = |
| cfg && cfg.icons && cfg.icons.region ? String(cfg.icons.region).trim() : 'com'; |
|
|
| const forceMode = siteForceIconMode || ''; |
| const shouldUseFavicon = forceMode ? forceMode === 'favicon' : iconsMode === 'favicon'; |
|
|
| if (shouldUseFavicon) { |
| const faviconImg = document.createElement('img'); |
| faviconImg.className = 'site-icon'; |
| faviconImg.loading = 'lazy'; |
| faviconImg.alt = siteName; |
|
|
| const fallbackToIcon = () => { |
| faviconImg.remove(); |
| iconEl.classList.add('icon-fallback'); |
| }; |
|
|
| |
| const timeoutId = setTimeout(() => { |
| fallbackToIcon(); |
| }, 5000); |
|
|
| faviconImg.onerror = () => { |
| clearTimeout(timeoutId); |
| fallbackToIcon(); |
| }; |
| faviconImg.onload = () => { |
| clearTimeout(timeoutId); |
| iconEl.remove(); |
| }; |
|
|
| if (siteFaviconUrl) { |
| faviconImg.src = siteFaviconUrl; |
| } else { |
| const urlToUse = String(data.url || '').trim(); |
| if (urlToUse) { |
| |
| const urls = []; |
| |
| const comUrl = `https://t3.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${encodeURIComponent( |
| urlToUse |
| )}&size=32&drop_404_icon=true`; |
| const cnUrl = `https://t3.gstatic.cn/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${encodeURIComponent( |
| urlToUse |
| )}&size=32&drop_404_icon=true`; |
| if (iconsRegion === 'cn') { |
| urls.push(cnUrl, comUrl); |
| } else { |
| urls.push(comUrl, cnUrl); |
| } |
|
|
| let idx = 0; |
| faviconImg.src = urls[idx]; |
|
|
| |
| const fallbackTimeoutId = setTimeout(() => { |
| idx += 1; |
| if (idx < urls.length) { |
| faviconImg.src = urls[idx]; |
| } else { |
| fallbackToIcon(); |
| } |
| }, 3000); |
|
|
| const cleanup = () => clearTimeout(fallbackTimeoutId); |
| faviconImg.onload = () => { |
| clearTimeout(timeoutId); |
| cleanup(); |
| iconEl.remove(); |
| }; |
| faviconImg.onerror = () => { |
| clearTimeout(timeoutId); |
| cleanup(); |
| idx += 1; |
| if (idx < urls.length) { |
| faviconImg.src = urls[idx]; |
| } else { |
| fallbackToIcon(); |
| } |
| }; |
| } |
| } |
|
|
| siteCardIcon.insertBefore(faviconImg, iconEl); |
| } else { |
| iconEl.classList.add('icon-fallback'); |
| } |
| } catch (e) { |
| iconEl.classList.add('icon-fallback'); |
| } |
| } |
|
|
| |
| sitesContainer.appendChild(newSite); |
|
|
| |
| const emptySites = sitesContainer.querySelector('.empty-sites'); |
| if (emptySites) { |
| emptySites.remove(); |
| } |
|
|
| |
| this.events.emit('elementAdded', { |
| id: siteName, |
| type: 'site', |
| parentId: parentId, |
| data: data, |
| }); |
|
|
| return siteName; |
| } else if (type === 'category') { |
| |
| const parent = document.querySelector(`[data-page="${parentId}"]`); |
| if (!parent) return null; |
|
|
| |
| const newCategory = document.createElement('section'); |
| newCategory.className = 'category'; |
| newCategory.setAttribute('data-type', 'category'); |
| newCategory.setAttribute('data-name', data.name || '未命名分类'); |
| if (data.icon) { |
| newCategory.setAttribute( |
| 'data-icon', |
| menavSanitizeClassList(data.icon, 'addElement(category).data-icon') |
| ); |
| } |
|
|
| |
| newCategory.setAttribute('data-level', '1'); |
|
|
| |
| const titleEl = document.createElement('h2'); |
| const iconEl = document.createElement('i'); |
| iconEl.className = menavSanitizeClassList( |
| data.icon || 'fas fa-folder', |
| 'addElement(category).icon' |
| ); |
| titleEl.appendChild(iconEl); |
| titleEl.appendChild(document.createTextNode(' ' + String(data.name || '未命名分类'))); |
|
|
| const sitesGrid = document.createElement('div'); |
| sitesGrid.className = 'sites-grid'; |
| sitesGrid.setAttribute('data-container', 'sites'); |
| const emptyEl = document.createElement('p'); |
| emptyEl.className = 'empty-sites'; |
| emptyEl.textContent = '暂无网站'; |
| sitesGrid.appendChild(emptyEl); |
|
|
| newCategory.appendChild(titleEl); |
| newCategory.appendChild(sitesGrid); |
|
|
| |
| parent.appendChild(newCategory); |
|
|
| |
| this.events.emit('elementAdded', { |
| id: data.name, |
| type: 'category', |
| data: data, |
| }); |
|
|
| return data.name; |
| } |
|
|
| return null; |
| }; |
|
|