浏览器不支持(未启用)JavaScript,本页面的某些功能无法正常使用

Blumid:开发一款为纯粹阅读而生的 Ghost 主题

Divider Line

酝酿许久,我终于完成了这款一直想开发的博客主题——Blumid,也就是你现在所看到的模样。它基本完整实现了 Ghost 官方提供的所有功能(如搜索、会员、内容卡、原生评论等),更在设计理念和功能细节上倾注了大量心血。

Blumid 的核心是“内容优先”。在这个信息过载的时代,许多博客追求海报级的视觉冲击力,但这往往会分散读者的注意力。Blumid 选择克制与优雅,旨在通过柔和的视觉引导,为读者创造一个沉浸、愉悦的阅读环境。

如果你喜欢这款主题,欢迎你:

  • 前往 GitHub 下载并安装到自己的博客上:code-gal/Ghost-Theme-Blumid

  • 点亮一颗星 (Star)✨ 来支持这个项目。

  • 订阅我的博客,以充分体验它的所有特点,重点在精心设计的侧边栏。

设计理念

我将 Blumid 的核心风格概括为 “晶莫渐变”,旨在突出内容本身,并提供舒适的视觉体验。

  • 模块化与现代感:内容以清晰的圆角卡片形式组织,信息层级分明。整体界面简洁,减少视觉干扰,让核心内容成为焦点。

  • 柔和氛围:页面背景采用主题色的柔和渐变,导航栏、主体栏及侧边栏等容器则应用了毛玻璃效果。白天模式下呈现淡雅阴影,夜间模式下则带有细微的边框泛光,营造出轻盈、舒适的视觉层次。

  • 色彩体系:主色调参考了流行的莫兰迪色系,柔和而不张扬。背景色除了推荐方案外,还完全支持自定义。同时,主题支持根据操作系统偏好自动切换亮/暗模式,并提供手动切换选项。

  • 交互贴合习惯:布局全面响应式设计,无缝适配各种屏幕尺寸。导航栏、侧边栏、卡片等所有关键元素都经过精心设计,确保在任何设备上都有一致且流畅的交互体验。

  • 轻量与原生:坚持使用原生的 CSS 与 JavaScript,最大限度地减少对第三方框架的依赖,确保主题轻盈、高效。

  • 图标系统:使用 Handlebars Partials 嵌入内联 SVG 图标,风格统一,自适应尺寸和主题颜色。

技术规范

作为一个 Web 项目,Blumid 遵循了业界领先的开发标准,确保其稳定、高效且易于维护。

  • 100% 通过 Gscan:项目已通过 Ghost 官方的 gscan 工具全面检测,符合主题最佳实践。

  • 代码质量:代码结构清晰,注释完善,遵循 BEM 命名规范,并使用现代 CSS 技术 (Flexbox, Grid),package.json 文件也包含了完整的主题元数据。

  • 无障碍访问 (Accessibility):遵循 WCAG 标准,采用语义化 HTML 标签,为交互元素提供 aria-label,保证足够的色彩对比度并支持键盘导航,致力于提供无障碍的访问体验。

  • SEO 优化:内置了完善的 SEO 结构化数据,有助于提升网站在搜索引擎中的可见性。

  • 标准模板结构:主题遵循标准的 Ghost 模板结构,方便开发维护。关键文件及目录包括:

    • default.hbs: 基础布局模板

    • index.hbs: 首页文章列表

    • post.hbs: 文章页模板

    • page.hbs: 独立页面模板

    • partials/: 可复用的模板片段

    • locales/: 语言文件目录

    • assets/: 存放 CSS、JavaScript、字体和图片等静态资源。

核心功能

Blumid 为内容显示实现多种增强效果,并提供丰富的自定义选项,它可以适应个性化需求。

增强的内容体验

  • 图片点击放大:文章页和侧边栏中的图片均支持点击放大,方便查看原图细节。

  • 便利贴式信息卡:文章的标签、日期等元信息会以别致的便利贴样式展示,并集成了一键分享功能。

  • 代码高亮:使用 Prism.js 实现代码高亮,支持显示语言类型和一键复制,并能自适应日夜模式。

  • Mermaid 图表:原生支持 Mermaid 语法,只需在 Markdown 或 HTML 中标注语言类型,即可将代码块渲染为流程图、时序图等。

  • LaTeX 公式:通过 KaTeX 解析,支持在文章页中渲染数学公式。为获得最佳效果,建议将公式写入 mathlatex 类型的代码块中。

  • 优化的内容卡:表格、引用以及所有 Ghost 官方内容卡都经过设计,确保在各种设备上都有美观且响应式的显示效果。

丰富的自定义选项

你可以在 Ghost 后台的 Design & branding 中轻松完成所有设置,或通过 Code injection 实现更高级的定制。

1. 全屏首页

可选的沉浸式首页,可用于展示精美的背景图与网站元信息,适合需要内容预加载或希望第一时间展示关键信息的网站。用户可通过滚动或点击箭头平滑过渡到文章列表。

2. 导航

头部和页脚导航栏分别展示Ghost 后台设置的主要和次要的导航条目,头部导航栏滚动自动显隐,小屏幕下自动折叠,小图标按钮切换侧边栏显示、颜色模式、搜索和会员。

3. 侧边栏收纳

Page页是没有侧边栏的,你可以选择完全关闭,打造极简的阅读体验。如果打开,它会智能响应不同设备,交互方式清晰自然:

  • 手机/平板:从屏幕左侧向右滑动可呼出侧边栏,点击内容区域外即可隐藏,避免与阅读手势冲突。

  • 桌面端:通过点击导航栏的图标来显示或隐藏。

4. 多功能侧边栏内容

在文章页,侧边栏会自动生成文章大纲。在列表页(首页、标签页等),你可以选择展示三种不同内容:

  • 网站信息 (Site_Info):默认模式,展示网站 Logo、作者信息和标签云。

  • RSS Feed:订阅并展示外部 RSS 源,非常适合聚合你在其他平台(如 Memos、Blinko、Mastodon)发布的内容。

  • 代码注入 (Code_Injection):通过注入自定义脚本,嵌入聊天框、个人状态等独特模块。

  • 会员专属内容:你可以将 RSS Feed 或代码注入的侧边栏内容设置为“仅会员可见”,为订阅用户提供专属价值。

5. 自定义渐变背景

背景颜色支持完全自定义,你可以随心所欲地调整日间与夜间模式下的顶部和底部色彩。

推荐颜色配置

  • 日间#A0C4FF (天空蓝), #FFFFE0 (阳光黄), #E4F9E0 (青草绿), #FFFFFF (纯白)

  • 夜间#000000 (星空黑), #2C003E (霓虹紫), #C76D00 (落日橙), #7D7D7D (纯灰)

6. 自定义字体与笔头

主题内置了优雅的字体回退方案,并允许你通过后台或代码注入引入自定义字体(如霞鹜文楷)。

文章分隔线上的“笔头”图标也支持替换(支持 SVG 和 PNG),增添个性化细节。

7. 多语言支持

主题内置中、英双语,并会根据 Ghost 后台的语言设置自动切换。你也可以轻松在 locales 文件夹中添加更多语言的翻译文件。

进阶功能详解

关于侧边栏 RSS 的 CORS 问题

由于浏览器同源策略,直接在前端请求第三方 RSS 源可能会遇到 CORS 限制。解决方案如下:

  1. 控制自己的源:如果 RSS 源站属于你,可以在服务器响应中添加 Access-Control-Allow-Origin 等头部。

  2. 使用 CORS 代理:对于无法控制的第三方源,可以使用 CORS 代理服务。其原理是让代理服务器去请求 RSS 源,然后将结果返回给你的博客,从而绕过浏览器限制。

一些公共 CORS 代理服务对比:

代理名称 URL 工作方式 API 密钥 速率限制/备注
allorigins.win https://api.allorigins.win /get?url={TARGET_URL} 不需要 免费开源,速率限制未明确说明。
CORS.SH https://proxy.cors.sh /{TARGET_URL} 需要 提供免费(限开源项目)和付费套餐。
cors-anywhere https://cors-anywhere.herokuapp.com /{TARGET_URL} 不需要 仅为演示服务器,不稳定,不建议生产使用。
Cloudflare Worker https://test.cors.workers.dev /?url={TARGET_URL} (示例,具体取决于worker实现) 不需要 Cloudflare Worker的演示实例,用于测试。可以自建。

使用示例:若你的 RSS 源是 https://example.com/rss,你可以在后台设置为 https://api.allorigins.win/get?url=https://example.com/rss

关于侧边栏代码注入

此功能为你提供了一个锚点元素 <div id="initChatBox" class="sidebar-section"></div>,你可以通过它在侧边栏嵌入任何动态内容,例如微博时间线或一个独立的聊天盒子。

我特别推荐结合 Cactus Comments 使用。如果你部署了自己的 Matrix 服务,它可以作为一个功能强大的网站聊天室,甚至是一个独立的短内容发布渠道,与本主题的会员系统和样式完美融合。参考教程:Matrix 评论系统:我为 Cactus Comments 开发了些新功能

在这个主题上,有一些特别的推荐设置:

<link rel="stylesheet" href="https://.../cactus-style.css" type="text/css">
<script type="text/javascript" src="https://.../cactus.js"></script>
<script type="text/javascript" src="https://.../cactus-lang.js"></script>
<script>
document.addEventListener('DOMContentLoaded', async function() {
    // 1. 定义获取Ghost会员名的函数
    async function getGhostMemberName() {
        try {
            // 尝试访问Ghost会员API端点
            const response = await fetch('/members/api/member/');
            if (response.status === 204) {
                // 204 No Content - 表示会员未登录
                return null;
            }
            if (response.ok) {
                const member = await response.json();
                if (member && member.name && member.name.trim() !== '') {
                    // console.log('Ghost Member: Name found -', member.name);
                    return member.name;
                } else if (member && member.email) {
                    // 如果没有名字,可以考虑使用邮箱前缀作为备选
                    const emailUsername = member.email.split('@')[0];
                    return emailUsername;
                }
                console.log('Ghost Member: Logged in, but no usable name or email found.');
                return null;
            } else {
                console.error('Ghost Member: Error fetching member data -', response.status);
                return null;
            }
        } catch (error) {
            console.error('Ghost Member: Exception fetching member data -', error);
            return null;
        }
    }

    // 2. 尝试获取Ghost会员名
    const ghostUserName = await getGhostMemberName();

    // 3. 初始化聊天盒子 (Cactus Comments)
    const chatBoxContainer = document.getElementById("initChatBox");
    if (chatBoxContainer) {
        // 确保 initComments 函数已在全局作用域定义或通过其他方式引入
        if (typeof initComments === 'function') {
            initComments({
                node: document.getElementById("initChatBox"),
                defaultHomeserverUrl: "https://matrix.xxx.com:8448",
                serverName: "xxx.com",
                siteName: "xxx",
                loginEnabled: true,
                guestPostingEnabled: true,
                commentEnabled: true,
                updateInterval: 60,
                pageSize:10,
                commentSectionId: "blog",
                isAuthenticated: true,
                translationsData: cactusTranslations,
                language: "cn"
            });

            // 4. 如果获取到了Ghost会员名,则尝试填充到聊天输入框
            if (ghostUserName) {
                let attempts = 0;
                const maxAttempts = 15; // 尝试次数 (15 * 500ms = 7.5秒)
                const interval = 500; // 尝试间隔 (毫秒)

                function attemptToFillUsername() {
                    // 根据您提供的HTML结构定位输入框
                    const nameInput = document.querySelector(".cactus-editor-name input[type='text']");

                    if (nameInput) {
                        nameInput.value = ghostUserName;
                        // 触发 'input' 事件,以便聊天客户端可能依赖的事件监听器能够感知到值的变化
                        nameInput.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));
                        // 有些组件可能监听 'change' 事件
                        nameInput.dispatchEvent(new Event('change', { bubbles: true, cancelable: true }));
                    } else if (attempts < maxAttempts) {
                        attempts++;
                        setTimeout(attemptToFillUsername, interval);
                    } else {
                        console.warn('ChatBox: Username input field not found after multiple attempts. Could not prefill with Ghost member name.');
                    }
                }
                attemptToFillUsername(); // 开始尝试填充
            }
        } else {
            console.error('Error: initComments function is not defined. Cannot initialize chatbox.');
        }
    }
});
</script>
<style>
    .cactus-container {
        flex-direction: column-reverse;
    }
    .cactus-comments-container {
        flex-direction: column-reverse;
    }
        .cactus-comments-list {
        flex-direction: column-reverse;
    }
    .cactus-comment-avatar{
        width: 25px !important;
    }
    .cactus-comment-avatar-placeholder,
    .cactus-comment-avatar>img {
        width: 25px !important;
        height: 25px !important;
    }
  .cactus-login-form-wrapper {
      position: sticky;
  }
  .cactus-editor-name {
      display:none;
  }
  .cactus-comment{
      gap: 0.5em;
  }
</style>
Divider Line
标签: Ghost 砖工技术
作者: 宁迦
日期:2025年06月09日

评论