Files
menav/templates/components/site-card.hbs
rbetree 30d50ebcd7 fix: 修复外部资源、图标模式与嵌套交互(#30)
Fixes: https://github.com/rbetree/menav/issues/30

- Font Awesome:bootcdn→Cloudflare cdnjs
- favicon:gstatic `.com` 失败自动回退 `.cn`
- `icons.mode`:修复 `site.yml` 配置未生效(提升到顶层)
- 站点级图标覆盖:支持 `faviconUrl` / `forceIconMode`(优先级:`faviconUrl` > `forceIconMode` > `icons.mode`)
- 折叠交互:恢复二级分组折叠按钮(桌面端悬停显示)
- 新标签页:递归补齐多级 `sites.external` 默认值
2026-01-02 18:09:20 +08:00

204 lines
11 KiB
Handlebars
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{{#if url}}
<a href="{{url}}" class="site-card{{#if style}} site-card-{{style}}{{/if}}"
{{#if external}}target="_blank" rel="noopener"{{/if}}
data-type="{{#if type}}{{type}}{{else}}site{{/if}}"
data-name="{{name}}"
data-url="{{url}}"
data-icon="{{#if icon}}{{icon}}{{else}}fas fa-link{{/if}}"
{{#if faviconUrl}}data-favicon-url="{{faviconUrl}}"{{/if}}
{{#if forceIconMode}}data-force-icon-mode="{{forceIconMode}}"{{/if}}
data-description="{{#if description}}{{description}}{{else}}{{extractDomain url}}{{/if}}"
{{#if publishedAt}}data-published-at="{{publishedAt}}"{{/if}}
{{#if source}}data-source="{{source}}"{{/if}}>
{{!-- articles首行图标+标题;下方“时间/来源 + 简介”全宽对齐,不被图标列缩进 --}}
{{#ifEquals type "article"}}
<div class="article-card-header">
<div class="site-card-icon" aria-hidden="true">
{{!-- 站点图标优先级faviconUrl > forceIconMode > 全局 icons.mode --}}
{{#if faviconUrl}}
<div class="icon-container">
<i class="fas fa-circle-notch fa-spin icon-placeholder" aria-hidden="true"></i>
<img
class="favicon-icon"
src="{{faviconUrl}}"
alt="{{name}} favicon"
loading="lazy"
onload="this.classList.add('loaded'); this.previousElementSibling.classList.add('hidden');"
onerror="this.classList.add('error'); this.previousElementSibling.classList.add('hidden'); this.nextElementSibling.classList.add('visible');"
/>
<i class="{{#if icon}}{{icon}}{{else}}fas fa-link{{/if}} icon-fallback" aria-hidden="true"></i>
</div>
{{else}}
{{#ifEquals forceIconMode "manual"}}
<i class="{{#if icon}}{{icon}}{{else}}fas fa-link{{/if}} site-icon" aria-hidden="true"></i>
{{else}}
{{#ifEquals forceIconMode "favicon"}}
{{#ifHttpUrl url}}
<div class="icon-container">
<i class="fas fa-circle-notch fa-spin icon-placeholder" aria-hidden="true"></i>
<img
class="favicon-icon"
src="https://t3.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url={{encodeURIComponent url}}&size=32"
alt="{{name}} favicon"
loading="lazy"
onload="this.classList.add('loaded'); this.previousElementSibling.classList.add('hidden');"
onerror="if (!this.dataset.faviconFallbackTried) { this.dataset.faviconFallbackTried = '1'; var next = this.src.replace('t3.gstatic.com', 't3.gstatic.cn'); if (next !== this.src) { this.src = next; return; } } this.classList.add('error'); this.previousElementSibling.classList.add('hidden'); this.nextElementSibling.classList.add('visible');"
/>
<i class="{{#if icon}}{{icon}}{{else}}fas fa-link{{/if}} icon-fallback" aria-hidden="true"></i>
</div>
{{else}}
<i class="{{#if icon}}{{icon}}{{else}}fas fa-link{{/if}} site-icon" aria-hidden="true"></i>
{{/ifHttpUrl}}
{{else}}
{{#ifEquals @root.icons.mode "favicon"}}
{{#ifHttpUrl url}}
<div class="icon-container">
<i class="fas fa-circle-notch fa-spin icon-placeholder" aria-hidden="true"></i>
<img
class="favicon-icon"
src="https://t3.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url={{encodeURIComponent url}}&size=32"
alt="{{name}} favicon"
loading="lazy"
onload="this.classList.add('loaded'); this.previousElementSibling.classList.add('hidden');"
onerror="if (!this.dataset.faviconFallbackTried) { this.dataset.faviconFallbackTried = '1'; var next = this.src.replace('t3.gstatic.com', 't3.gstatic.cn'); if (next !== this.src) { this.src = next; return; } } this.classList.add('error'); this.previousElementSibling.classList.add('hidden'); this.nextElementSibling.classList.add('visible');"
/>
<i class="{{#if icon}}{{icon}}{{else}}fas fa-link{{/if}} icon-fallback" aria-hidden="true"></i>
</div>
{{else}}
<i class="{{#if icon}}{{icon}}{{else}}fas fa-link{{/if}} site-icon" aria-hidden="true"></i>
{{/ifHttpUrl}}
{{else}}
<i class="{{#if icon}}{{icon}}{{else}}fas fa-link{{/if}} site-icon" aria-hidden="true"></i>
{{/ifEquals}}
{{/ifEquals}}
{{/ifEquals}}
{{/if}}
</div>
<div class="article-card-title">
<h3>{{#if name}}{{name}}{{else}}未命名站点{{/if}}</h3>
</div>
</div>
<div class="article-card-body">
{{#ifCond publishedAt '||' source}}
<div class="site-card-meta">
{{#if publishedAt}}
<span class="site-card-meta-date">{{formatDate publishedAt "YYYY-MM-DD"}}</span>
{{/if}}
{{#ifCond publishedAt '&&' source}}
<span class="site-card-meta-sep">·</span>
{{/ifCond}}
{{#if source}}
<span class="site-card-meta-source">{{source}}</span>
{{/if}}
</div>
{{/ifCond}}
<p>{{#if description}}{{description}}{{else}}{{extractDomain url}}{{/if}}</p>
</div>
{{else}}
{{!-- projects代码仓库风格卡片保留 data-* 结构,便于扩展识别与写回) --}}
{{#ifEquals style "repo"}}
<div class="repo-header">
<i class="{{#if icon}}{{icon}}{{else}}fas fa-code{{/if}} repo-icon" aria-hidden="true"></i>
<div class="repo-title">{{#if name}}{{name}}{{else}}未命名项目{{/if}}</div>
</div>
<div class="repo-desc">{{#if description}}{{description}}{{else}}{{extractDomain url}}{{/if}}</div>
{{#ifCond language '||' stars}}
<div class="repo-stats">
{{#if language}}
<div class="stat-item">
<span class="lang-dot" style="background-color: {{#if languageColor}}{{languageColor}}{{else}}#909296{{/if}};"></span>
{{language}}
</div>
{{/if}}
{{#if stars}}
<div class="stat-item">
<i class="far fa-star" aria-hidden="true"></i> {{stars}}
</div>
{{/if}}
{{#if forks}}
<div class="stat-item">
<i class="fas fa-code-branch" aria-hidden="true"></i> {{forks}}
</div>
{{/if}}
{{#if issues}}
<div class="stat-item">
<i class="fas fa-exclamation-circle" aria-hidden="true"></i> {{issues}}
</div>
{{/if}}
</div>
{{/ifCond}}
{{else}}
<div class="site-card-icon" aria-hidden="true">
{{!-- 站点图标优先级faviconUrl > forceIconMode > 全局 icons.mode --}}
{{#if faviconUrl}}
<div class="icon-container">
<i class="fas fa-circle-notch fa-spin icon-placeholder" aria-hidden="true"></i>
<img
class="favicon-icon"
src="{{faviconUrl}}"
alt="{{name}} favicon"
loading="lazy"
onload="this.classList.add('loaded'); this.previousElementSibling.classList.add('hidden');"
onerror="this.classList.add('error'); this.previousElementSibling.classList.add('hidden'); this.nextElementSibling.classList.add('visible');"
/>
<i class="{{#if icon}}{{icon}}{{else}}fas fa-link{{/if}} icon-fallback" aria-hidden="true"></i>
</div>
{{else}}
{{#ifEquals forceIconMode "manual"}}
<i class="{{#if icon}}{{icon}}{{else}}fas fa-link{{/if}} site-icon" aria-hidden="true"></i>
{{else}}
{{#ifEquals forceIconMode "favicon"}}
{{#ifHttpUrl url}}
<div class="icon-container">
<i class="fas fa-circle-notch fa-spin icon-placeholder" aria-hidden="true"></i>
<img
class="favicon-icon"
src="https://t3.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url={{encodeURIComponent url}}&size=32"
alt="{{name}} favicon"
loading="lazy"
onload="this.classList.add('loaded'); this.previousElementSibling.classList.add('hidden');"
onerror="if (!this.dataset.faviconFallbackTried) { this.dataset.faviconFallbackTried = '1'; var next = this.src.replace('t3.gstatic.com', 't3.gstatic.cn'); if (next !== this.src) { this.src = next; return; } } this.classList.add('error'); this.previousElementSibling.classList.add('hidden'); this.nextElementSibling.classList.add('visible');"
/>
<i class="{{#if icon}}{{icon}}{{else}}fas fa-link{{/if}} icon-fallback" aria-hidden="true"></i>
</div>
{{else}}
<i class="{{#if icon}}{{icon}}{{else}}fas fa-link{{/if}} site-icon" aria-hidden="true"></i>
{{/ifHttpUrl}}
{{else}}
{{#ifEquals @root.icons.mode "favicon"}}
{{#ifHttpUrl url}}
<div class="icon-container">
<i class="fas fa-circle-notch fa-spin icon-placeholder" aria-hidden="true"></i>
<img
class="favicon-icon"
src="https://t3.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url={{encodeURIComponent url}}&size=32"
alt="{{name}} favicon"
loading="lazy"
onload="this.classList.add('loaded'); this.previousElementSibling.classList.add('hidden');"
onerror="if (!this.dataset.faviconFallbackTried) { this.dataset.faviconFallbackTried = '1'; var next = this.src.replace('t3.gstatic.com', 't3.gstatic.cn'); if (next !== this.src) { this.src = next; return; } } this.classList.add('error'); this.previousElementSibling.classList.add('hidden'); this.nextElementSibling.classList.add('visible');"
/>
<i class="{{#if icon}}{{icon}}{{else}}fas fa-link{{/if}} icon-fallback" aria-hidden="true"></i>
</div>
{{else}}
<i class="{{#if icon}}{{icon}}{{else}}fas fa-link{{/if}} site-icon" aria-hidden="true"></i>
{{/ifHttpUrl}}
{{else}}
<i class="{{#if icon}}{{icon}}{{else}}fas fa-link{{/if}} site-icon" aria-hidden="true"></i>
{{/ifEquals}}
{{/ifEquals}}
{{/ifEquals}}
{{/if}}
</div>
<div class="site-card-content">
<h3>{{#if name}}{{name}}{{else}}未命名站点{{/if}}</h3>
<p>{{#if description}}{{description}}{{else}}{{extractDomain url}}{{/if}}</p>
</div>
{{/ifEquals}}
{{/ifEquals}}
</a>
{{/if}}