· 4,194 chars · 5 min Updated

博客新文章通知与历史修订功能实现

基于 Astro + IndexedDB + diff 库实现的文章更新通知系统,支持 RSS 差异比较、文章内变更高亮显示

AI
AI 摘要
AI Generated
博客新文章通知与历史修订功能实现 ## 前言 在维护博客的过程中,经常需要更新已发布的文章。如何让读者知道文章有更新?如何展示具体变更内容?本文介绍我基于 Astro 框架实现的新文章通知与历史修订功能。
本摘要由 AI 生成,仅供参考,内容准确性请以原文为准。

前言

在维护博客的过程中,经常需要更新已发布的文章。如何让读者知道文章有更新?如何展示具体变更内容?本文将介绍我基于 Astro 框架实现的新文章通知与历史修订功能。

功能概述

实现的功能包括:

  1. 新文章通知 - 右下角通知面板,显示新发布的文章和更新的文章
  2. 文章内差异高亮 - 在文章页面显示具体变更(新增/删除)
  3. 跳转到更新处 - 一键跳转到文章变更位置
  4. 文章时效性提示 - 显示文章发布/更新时间

技术栈

  • Astro - 静态站点生成器
  • IndexedDB - 浏览器本地数据库存储 RSS 数据
  • diff - JavaScript 差异比较库
  • RSS - 文章数据源

架构设计

数据流向

RSS 数据

NewPostNotification 获取并存储到 IndexedDB

比较新旧 RSS 差异(diff)

存储差异数据到 localStorage

PostDiffInArticle 读取并渲染高亮

组件分工

组件职责
NewPostNotification全局通知组件,RSS 获取、差异计算、IndexedDB 存储
PostDiffInArticle文章内差异展示,DOM 操作渲染高亮
PostContentHighlighter更新提示 Toast,跳转功能
PostUpdateNotice文章时效性提示

核心实现

1. RSS 存储与差异比较

使用 IndexedDB 存储 RSS 数据,避免重复获取:

const DB_NAME = 'blog-rss-store-v2';
const STORE_OLD = 'posts';
const STORE_NEW = 'posts_new';

function openDB() {
    return new Promise((resolve, reject) => {
        const request = indexedDB.open(DB_NAME, 1);
        request.onupgradeneeded = (event) => {
            const db = event.target.result;
            db.createObjectStore(STORE_OLD, { keyPath: 'id' });
            db.createObjectStore(STORE_NEW, { keyPath: 'id' });
        };
        // ...
    });
}

使用 diff 库比较文章内容:

import * as Diff from 'diff';

function computeDiff(oldText, newText) {
    const diffs = Diff.diffLines(oldText, newText);
    return diffs.filter(part => part.added || part.removed);
}

2. 文章内差异高亮

差异数据格式:

{
    title: "文章标题",
    isUpdated: true,
    diff: {
        content: [
            { value: "旧内容", removed: true },
            { value: "新内容", added: true }
        ]
    }
}

渲染逻辑:

// 找到匹配的 DOM 元素并添加样式
const target = findBlockByText(container, addRow.text);
if (target) {
    target.classList.add("post-inline-diff-add-target");
    target.style.textDecorationLine = "underline";
    target.style.textDecorationColor = "#4ade80";
} else {
    // 创建新元素显示新增内容
    const node = createAdditionNode(text);
    container.appendChild(node);
}

3. 样式设计

/* 新增内容 - 绿色下划线 */
.post-inline-diff-add-target {
    text-decoration-line: underline !important;
    text-decoration-thickness: 2px !important;
    text-decoration-color: #4ade80 !important;
}

/* 删除内容 - 红色删除线 */
.post-inline-diff-del-target {
    color: #f87171 !important;
    text-decoration-line: line-through !important;
    text-decoration-color: #f87171 !important;
}

4. 触发条件

Diff 样式在以下情况自动显示:

  1. 从网站内部页面点击文章(通过 document.referrer 检测)
  2. URL 带 ?diff=1 参数
  3. URL 带 #post-diff 锚点
  4. localStorage 中有通知状态(从通知面板点击)
  5. 调试模式 ?__diff_debug=1
const isFromInternal = document.referrer?.startsWith(window.location.origin);
const hasNotificationState = !!localStorage.getItem(NOTIFICATION_STATE_KEY);
const shouldAutoShow = matched && (isFromInternal || hasNotificationState);
const shouldApply = isDebug || diffParam || hasDiffHash || shouldAutoShow;

使用方式

对于读者

  1. 查看通知 - 访问博客时,右下角通知面板自动显示新文章和更新文章
  2. 查看差异 - 点击更新文章,自动显示变更高亮(红色删除/绿色新增)
  3. 跳转定位 - 点击”跳转到更新处”按钮,快速定位到变更位置

对于开发者

调试模式:

http://localhost:4321/blog/article-slug?__diff_debug=1

强制显示差异:

http://localhost:4321/blog/article-slug?diff=1#post-diff

清除 IndexedDB(调试用途):

http://localhost:4321/?__diff_debug_cleanup=1

实现细节

IndexedDB 版本管理

当数据库结构变化时,需要升级版本。为避免版本冲突,生产环境使用了新的数据库名称:

const DB_NAME = 'blog-rss-store-v2'; // 使用 v2 避免与旧版本冲突
const DB_VERSION = 1;

路径匹配

处理不同部署路径的情况:

function stripBasePath(pathname) {
    const base = normalizePathname(BASE_URL);
    const p = normalizePathname(pathname);
    if (p.startsWith(`${base}/`)) return p.slice(base.length) || '/';
    return p;
}

RSS 路径适配

生产环境可能使用不同的 basePath,需要尝试多个 RSS 路径:

const possiblePaths = [
    basePath ? `${basePath}/rss.xml` : '/rss.xml',
    '/rss.xml',
    './rss.xml',
    'rss.xml'
];

图片变更处理

图片变更通过 outline 样式标记:

.post-inline-diff-add-target-img {
    outline: 2px solid #4ade80;
    outline-offset: 2px;
}

.post-inline-diff-del-target-img {
    outline: 2px solid #f87171;
    opacity: 0.7;
}

遇到的问题与解决方案

1. 子模块部署问题

问题:Cloudflare Pages 部署时无法克隆 git 子模块

解决:移除 .gitmodules 文件,将 Blogs_workerfuwari 作为普通目录保留在本地

2. IndexedDB 版本冲突

问题:生产环境数据库版本与本地不一致,导致 VersionError

解决:修改数据库名称为 blog-rss-store-v2,重新开始

3. 变量重复定义

问题NOTIFICATION_STATE_KEY 在组件顶部和函数内重复定义,导致构建后变量提升问题

解决:移除函数内的重复定义

4. 日期字段不匹配

问题:友链朋友圈 API 返回的日期字段是 created 而不是 date

解决:统一使用 created 字段

5. Referrer 不可靠

问题:生产环境 document.referrer 可能为空

解决:同时检查 localStorage 中是否有通知状态

总结

通过 RSS + IndexedDB + diff 的组合,实现了完整的新文章通知和历史修订功能。读者可以:

  • 及时了解博客更新
  • 直观查看文章变更
  • 快速定位更新位置

这套方案不依赖后端,完全在浏览器端实现,适合静态博客部署。

参考

内容已更新

检测到文章内容有变化,已为您高亮差异部分。

这篇文章是否对你有帮助?

发现错误或想要改进这篇文章?

在 GitHub 上编辑此页

文章修订历史 (1 次)

查看变更记录
2026年3月13日 0d76044

feat(文章差异): 实现文章更新差异高亮与跳转功能

博客新文章通知与历史修订功能实现
作者
异飨客
发布于
许可协议
CC BY-NC-SA 4.0

评论区

文章更新