From 065c183170e67c0fcde19e4f5c5fec239aff9bc5 Mon Sep 17 00:00:00 2001 From: HowieHz Date: Tue, 24 Mar 2026 21:17:36 +0800 Subject: [PATCH 01/19] feat: Add comprehensive SEO processors and README updates --- README.md | 40 +- .../timefactor/process/TimeFactorProcess.java | 874 +++++++++++++++--- 2 files changed, 808 insertions(+), 106 deletions(-) diff --git a/README.md b/README.md index aa641a1..9a00744 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,8 @@ - **演示站点**:[https://www.lik.cc/](https://www.lik.cc/) - **文档**:[https://docs.lik.cc/](https://docs.lik.cc/) -- **QQ 交流群**:[![QQ群](https://www.lik.cc/upload/iShot_2025-03-03_16.03.00.png)](https://www.lik.cc/upload/iShot_2025-03-03_16.03.00.png) +- **QQ 交流群 + **:[![QQ群](https://www.lik.cc/upload/iShot_2025-03-03_16.03.00.png)](https://www.lik.cc/upload/iShot_2025-03-03_16.03.00.png) ## 功能特性 @@ -17,45 +18,80 @@ ![结构化数据注入效果示例](https://www.lik.cc/upload/Google%20Chrome%202025-06-28%2011.51.07.png) ### 🎯 智能 SEO 注入 + - **多平台支持**:支持 Google、百度、字节、OG 等主流搜索引擎的结构化数据格式 - **动态内容**:根据页面类型和内容自动填充标题、描述、作者、标签等字段 ### 🔧 配置灵活 + - 支持启用/禁用搜索引擎优化功能 - 可配置默认封面图片 - 自动获取站点信息(标题、Logo、关键词等) ### 🛡️ 性能优化 + - 普通用户访问时零性能影响 - 结构化数据格式紧凑,无多余注释 - 异常处理完善,保证系统稳定性 ## 支持注入的页面类型 -- [文章详情页](https://docs.halo.run/developer-guide/theme/template-variables/post) +> **数据来源说明**:「站点信息」指使用 Halo 后台设置的站点标题、Logo、SEO 关键词和描述等全局配置来生成 SEO +> 标签,适用于没有具体内容实体的列表/聚合页面;其他数据来源(如 Post、Category 等)表示通过 Halo Extension API +> 获取对应的强类型对象,从中提取标题、摘要、封面、作者、发布时间等精确字段。 + +### Halo 内置页面 + +| 页面类型 | 模板 ID | 数据来源 | 文档 | +|-------|--------------|-------------------|-----------------------------------------------------------------------------------| +| 首页 | `index` | 站点信息 | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/index_) | +| 文章详情页 | `post` | Post + User + Tag | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/post) | +| 独立页面 | `page` | SinglePage + User | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/page) | +| 分类列表页 | `categories` | 站点信息 | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/categories) | +| 分类详情页 | `category` | Category | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/category) | +| 标签列表页 | `tags` | 站点信息 | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/tags) | +| 标签详情页 | `tag` | Tag | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/tag) | +| 归档页 | `archives` | 站点信息 | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/archives) | +| 作者页 | `author` | User | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/author) | + +### 第三方插件页面 + +| 页面类型 | 模板 ID | 数据来源 | 插件 | +|------|-----------|--------------------|-----------------------------------------------------------------------------------| +| 瞬间列表 | `moments` | 站点信息 | [plugin-moments](https://github.com/halo-sigs/plugin-moments) | +| 瞬间详情 | `moment` | 站点信息 | [plugin-moments](https://github.com/halo-sigs/plugin-moments) | +| 图库 | `photos` | 站点信息 | [plugin-photos](https://github.com/halo-sigs/plugin-photos) | +| 朋友圈 | `friends` | 站点信息 | [plugin-friends-new](https://github.com/chengzhongxue/plugin-friends-new) | +| 豆瓣 | `douban` | 站点信息 + 路由 title 变量 | [plugin-douban](https://github.com/chengzhongxue/plugin-douban) | +| 番剧 | `bangumi` | 站点信息 | [halo-plugin-bangumi-data](https://github.com/ShiinaKin/halo-plugin-bangumi-data) | ## 支持注入的数据类型 ### Link 标签 + - canonical 链接 - alternate 链接 ### Open Graph (OG) + - 标题、描述、封面图 - 作者、标签、发布时间 - 站点信息 ### 百度结构化数据 + - 文章标题、摘要、封面 - 作者、分类、发布时间 - 站点名称、Logo ### 字节跳动结构化数据 + - 内容标题、描述、图片 - 作者信息、发布时间 - 站点标识 ### Google JSON-LD (schema.org) + - 文章结构化数据 - 作者信息 - 发布时间(带时区) diff --git a/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java b/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java index 52b98f2..ad4d147 100644 --- a/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java +++ b/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java @@ -1,6 +1,7 @@ package cc.lik.timefactor.process; import cc.lik.timefactor.service.SettingConfigGetter; +import java.time.Instant; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.Arrays; @@ -18,7 +19,9 @@ import org.unbescape.json.JsonEscape; import reactor.core.publisher.Mono; import run.halo.app.core.extension.User; +import run.halo.app.core.extension.content.Category; import run.halo.app.core.extension.content.Post; +import run.halo.app.core.extension.content.SinglePage; import run.halo.app.core.extension.content.Tag; import run.halo.app.extension.ListOptions; import run.halo.app.extension.ReactiveExtensionClient; @@ -30,25 +33,38 @@ import run.halo.app.theme.dialect.TemplateHeadProcessor; import run.halo.app.theme.router.ModelConst; +/** + * 模板 ID 枚举,映射 Halo 路由系统中的 {@code _templateId} 到具体页面类型。 + * + *

参考 Halo 内置模板枚举: + * + * DefaultTemplateEnum.java + */ @Getter enum TemplateEnum { - // 内置模板 ID 参考 https://github.com/halo-dev/halo/blob/main/application/src/main/java/run/halo/app/theme/DefaultTemplateEnum.java + // ---- 内置模板 ID ---- + // 参考 https://github.com/halo-dev/halo/blob/main/application/src/main/java/run/halo/app/theme/DefaultTemplateEnum.java INDEX("index"), CATEGORIES("categories"), CATEGORY("category"), ARCHIVES("archives"), POST("post"), TAG("tag"), TAGS("tags"), SINGLE_PAGE("page"), AUTHOR("author"), + + // ---- 第三方插件适配 ---- // 适配瞬间插件 https://github.com/halo-sigs/plugin-moments MOMENTS("moments"), // 瞬间列表,路径 /moments - MOMENT("moment"), // 瞬间详情页,路径 /moments/{slug} + MOMENT("moment"), // 瞬间详情页,路径 /moments/{slug} // 适配图库管理插件 https://github.com/halo-sigs/plugin-photos - PHOTOS("photos"), // 路径 /photos + PHOTOS("photos"), // 路径 /photos // 适配朋友圈插件 https://github.com/chengzhongxue/plugin-friends-new - FRIENDS("friends"), // 路径 /friends + FRIENDS("friends"), // 路径 /friends // 适配豆瓣插件 https://github.com/chengzhongxue/plugin-douban - DOUBAN("douban"), // 路径 /douban + DOUBAN("douban"), // 路径 /douban // 适配 BangumiData 插件 https://github.com/ShiinaKin/halo-plugin-bangumi-data - BANGUMI("bangumi"), // 路径 /bangumi - // 无法适配足迹插件 https://github.com/acanyo/halo-plugin-footprint ,值为 null - // 无法适配链接管理插件 https://github.com/halo-sigs/plugin-links ,值为 null - // 无法适配追番插件 https://github.com/Roozenlz/plugin-bilibili-bangumi ,值为 null + BANGUMI("bangumi"), // 路径 /bangumi + + // ---- 无法适配的插件(_templateId 为 null,无法识别) ---- + // 足迹插件 https://github.com/acanyo/halo-plugin-footprint + // 链接管理插件 https://github.com/halo-sigs/plugin-links + // 追番插件 https://github.com/Roozenlz/plugin-bilibili-bangumi + // 未知模板 UNKNOWN("unknown"); @@ -64,120 +80,460 @@ static TemplateEnum fromTemplateId(String templateId) { } } +/** + * 时间因子 SEO 处理器,为 Halo 主题页面的 {@code } 注入结构化 SEO 数据。 + * + *

核心设计思路: + *

    + *
  1. 模板分发:根据 {@code _templateId} 上下文变量分发到对应页面处理器
  2. + *
  3. 官方详情页使用强类型:POST、SINGLE_PAGE、CATEGORY、TAG、AUTHOR 通过 + * {@link ReactiveExtensionClient} 获取 Halo Extension 对象(Post、SinglePage、 + * Category、Tag、User),完全避免反射,保证类型安全和编译期检查
  4. + *
  5. 列表页使用站点信息:INDEX、CATEGORIES、TAGS、ARCHIVES 等列表页没有 + * 特定实体对象,使用站点级信息(标题、描述、Logo)构建 SEO 数据
  6. + *
  7. 第三方插件降级处理:由于第三方插件的 Vo 类型不在编译期 classpath 中, + * 第三方插件页面(MOMENTS、PHOTOS、FRIENDS、DOUBAN、BANGUMI)统一使用 + * 站点级信息 + 页面语义标题构建,部分可利用的上下文变量(如豆瓣的 title)也会被读取
  8. + *
  9. 统一输出管道:所有页面最终通过 {@link #generateSeoTags} 输出, + * 保证标签格式一致,配置项统一生效
  10. + *
+ * + * @see TemplateHeadProcessor + * @see Halo 模板变量文档 + */ @Component @RequiredArgsConstructor @Slf4j public class TimeFactorProcess implements TemplateHeadProcessor { + + /** + * 百度时间因子格式:不含时区偏移,如 {@code 2024-01-15T10:30:00} + */ private static final DateTimeFormatter BAIDU_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"); + + /** + * Google/Schema.org 时间格式:含时区偏移,如 {@code 2024-01-15T10:30:00+08:00} + */ private static final DateTimeFormatter GOOGLE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXXX"); + private final ReactiveExtensionClient client; private final SettingConfigGetter settingConfigGetter; private final ExternalLinkProcessor externalLinkProcessor; private final SystemInfoGetter systemInfoGetter; + // ======================== 入口方法 ======================== + + /** + * Halo 模板头处理器入口,根据模板类型分发到对应的 SEO 处理器。 + * + *

从 Thymeleaf 上下文读取 {@code _templateId} 变量(由 Halo 路由系统注入), + * 映射为 {@link TemplateEnum} 后分发到对应处理方法。未知模板返回空 Mono, + * 不影响页面渲染。全局 onErrorResume 兜底,确保 SEO 注入失败不会影响页面加载。 + * + * @param context Thymeleaf 模板上下文,包含路由注入的模板变量 + * @param model 待输出的 HTML 模型,SEO 标签将追加到此模型 + * @param handler 结构处理器(本插件未使用) + */ @Override public Mono process(ITemplateContext context, IModel model, IElementModelStructureHandler handler) { - var templateId = Optional.ofNullable(context.getVariable(ModelConst.TEMPLATE_ID)).map(Object::toString) .orElse(null); var template = TemplateEnum.fromTemplateId(templateId); - log.debug("Processing SEO for templateId: {}", templateId); - return switch (template) { - case INDEX -> processIndexSeoData(); + // 按模板类型分发,全局捕获异常避免影响页面渲染 + Mono result = switch (template) { + // ---- 官方页面 ---- + case INDEX -> processIndexSeoData(context, model); case POST -> processPostSeoData(context, model); - case CATEGORIES -> processCategoriesSeoData(); - case CATEGORY -> processCategorySeoData(); - case ARCHIVES -> processArchivesSeoData(); - case TAGS -> processTagsSeoData(); - case TAG -> processTagSeoData(); - case SINGLE_PAGE -> processSinglePageSeoData(); - case AUTHOR -> processAuthorSeoData(); - case MOMENTS -> processMomentsSeoData(); - case MOMENT -> processMomentSeoData(); - case PHOTOS -> processPhotosSeoData(); - case FRIENDS -> processFriendsSeoData(); - case DOUBAN -> processDoubanSeoData(); - case BANGUMI -> processBangumiSeoData(); + case SINGLE_PAGE -> processSinglePageSeoData(context, model); + case CATEGORIES -> processCategoriesSeoData(context, model); + case CATEGORY -> processCategorySeoData(context, model); + case ARCHIVES -> processArchivesSeoData(context, model); + case TAGS -> processTagsSeoData(context, model); + case TAG -> processTagSeoData(context, model); + case AUTHOR -> processAuthorSeoData(context, model); + // ---- 第三方插件页面 ---- + case MOMENTS -> processMomentsSeoData(context, model); + case MOMENT -> processMomentSeoData(context, model); + case PHOTOS -> processPhotosSeoData(context, model); + case FRIENDS -> processFriendsSeoData(context, model); + case DOUBAN -> processDoubanSeoData(context, model); + case BANGUMI -> processBangumiSeoData(context, model); case UNKNOWN -> Mono.empty(); }; - } - private Mono processIndexSeoData() { - return Mono.empty(); + return result.onErrorResume(error -> { + log.warn("Failed to process SEO for templateId: {}", templateId, error); + return Mono.empty(); + }); } - private Mono processPostSeoData(ITemplateContext context, IModel model) { - var modelFactory = context.getModelFactory(); - var postName = Optional.ofNullable(context.getVariable("name")).map(Object::toString) - .filter(name -> !name.isEmpty()).orElse(null); + // ======================== 列表页 SEO 处理器 ======================== + // 设计思路:列表页没有具体的内容实体对象,使用站点级信息 + 页面语义标题构建 SEO 数据。 + // 所有列表页共享 buildListPageSeoData 方法,仅传入不同的标题、描述和路径。 - if (postName == null) { - return Mono.empty(); - } + /** + * 首页 SEO 注入。 + * + *

首页是站点的入口页面,使用站点标题和 SEO 描述构建。 + * 模板变量:{@code posts} (UrlContextListResult<ListedPostVo>) + * + * @see + * 首页模板变量 + */ + private Mono processIndexSeoData(ITemplateContext context, IModel model) { + return buildListPageSeoData(context, model, "首页", "", "/"); + } - return client.fetch(Post.class, postName).flatMap(post -> buildSeoDataForPost(post).flatMap( - seoData -> generateSeoTags(seoData, model, modelFactory))); + /** + * 分类列表页 SEO 注入。 + * + *

展示所有分类的导航页面。 + * 模板变量:{@code categories} (List<CategoryTreeVo>) + * + * @see + * 分类列表模板变量 + */ + private Mono processCategoriesSeoData(ITemplateContext context, IModel model) { + return buildListPageSeoData(context, model, "分类", "分类导航页面", "/categories"); } - private Mono processCategoriesSeoData() { - return Mono.empty(); + /** + * 归档页 SEO 注入。 + * + *

按时间归档的文章列表页面。 + * 模板变量:{@code archives} (UrlContextListResult<PostArchiveVo>) + * + * @see + * 归档模板变量 + */ + private Mono processArchivesSeoData(ITemplateContext context, IModel model) { + return buildListPageSeoData(context, model, "归档", "内容归档页面", "/archives"); } - private Mono processCategorySeoData() { - return Mono.empty(); + /** + * 标签列表页 SEO 注入。 + * + *

展示所有标签的导航页面。 + * 模板变量:{@code tags} (List<TagVo>) + * + * @see + * 标签列表模板变量 + */ + private Mono processTagsSeoData(ITemplateContext context, IModel model) { + return buildListPageSeoData(context, model, "标签", "标签导航页面", "/tags"); } - private Mono processArchivesSeoData() { - return Mono.empty(); + /** + * 瞬间列表页 SEO 注入(第三方插件 plugin-moments)。 + * + *

瞬间插件的列表页面,使用站点级信息构建。 + * MomentVo 结构:spec.content.html, spec.releaseTime, spec.owner, owner.displayName + * 由于 MomentVo 不在编译期 classpath,此处使用站点级信息降级处理。 + * + * @see plugin-moments + */ + private Mono processMomentsSeoData(ITemplateContext context, IModel model) { + return buildListPageSeoData(context, model, "瞬间", "瞬间列表页面", "/moments"); } - private Mono processTagsSeoData() { - return Mono.empty(); + /** + * 瞬间详情页 SEO 注入(第三方插件 plugin-moments)。 + * + *

由于 MomentVo 类型不在编译期 classpath 中,无法强类型读取。 + * 瞬间详情页通常为短内容社交帖子,站点级 SEO 已满足基本需求。 + * 如需更精确的 SEO 数据,可在未来版本通过 Finder API 增强。 + */ + private Mono processMomentSeoData(ITemplateContext context, IModel model) { + return buildListPageSeoData(context, model, "瞬间", "瞬间详情页面", "/moments"); } - private Mono processTagSeoData() { - return Mono.empty(); + /** + * 图库页 SEO 注入(第三方插件 plugin-photos)。 + * + *

图库插件的列表页面。PhotoVo 结构:spec.displayName, spec.description, + * spec.url, spec.cover。由于不在 classpath,使用站点级信息降级。 + * + * @see plugin-photos + */ + private Mono processPhotosSeoData(ITemplateContext context, IModel model) { + return buildListPageSeoData(context, model, "图库", "图库页面", "/photos"); } - private Mono processSinglePageSeoData() { - return Mono.empty(); + /** + * 朋友圈页 SEO 注入(第三方插件 plugin-friends-new)。 + * + *

朋友圈插件的列表页面。FriendPostVo 结构:spec.author, spec.logo, + * spec.title, spec.postLink, spec.description, spec.pubDate。 + * 由于不在 classpath,使用站点级信息降级。 + * + * @see plugin-friends-new + */ + private Mono processFriendsSeoData(ITemplateContext context, IModel model) { + return buildListPageSeoData(context, model, "朋友圈", "朋友圈页面", "/friends"); } - private Mono processAuthorSeoData() { - return Mono.empty(); + /** + * 豆瓣页 SEO 注入(第三方插件 plugin-douban)。 + * + *

豆瓣插件路由在上下文中设置了 {@code title} 变量 + * (由 {@code DoubanRouter.getDoubanTitle()} 生成),优先使用该变量作为页面标题。 + * DoubanMovieVo 结构:spec.name, spec.poster, spec.link, spec.score, spec.year, + * faves.createTime, faves.remark。 + * + * @see plugin-douban + */ + private Mono processDoubanSeoData(ITemplateContext context, IModel model) { + // 豆瓣路由设置了 title 上下文变量(DoubanRouter.getDoubanTitle),优先使用 + var doubanTitle = Optional.ofNullable(context.getVariable("title")).map(Object::toString) + .filter(s -> !s.isBlank()).orElse("豆瓣"); + return buildListPageSeoData(context, model, doubanTitle, "豆瓣内容页面", "/douban"); } - private Mono processMomentsSeoData() { - return Mono.empty(); + /** + * 番剧页 SEO 注入(第三方插件 halo-plugin-bangumi-data)。 + * + *

番剧插件通过 {@code bangumiDataFinder} 提供数据。 + * BangumiUserData(Kotlin 数据类)结构:spec.nickname, spec.avatar, spec.sign, + * spec.*CollectionJson(各类收藏的 JSON 字符串)。 + * 由于数据模型为 Kotlin 类且不在 classpath,使用站点级信息降级。 + * + * @see + * halo-plugin-bangumi-data + */ + private Mono processBangumiSeoData(ITemplateContext context, IModel model) { + return buildListPageSeoData(context, model, "番剧", "番剧数据页面", "/bangumi"); } - private Mono processMomentSeoData() { - return Mono.empty(); + // ======================== 详情页 SEO 处理器 ======================== + // 设计思路:详情页有具体的内容实体,通过上下文变量 name(Halo 路由系统注入的 + // metadata.name)获取实体标识,再用 ReactiveExtensionClient 获取强类型 Extension + // 对象,完全避免反射。这与文章详情页(POST)的实现方式一致。 + + /** + * 文章详情页 SEO 注入。 + * + *

通过上下文变量 {@code name}(文章的 metadata.name)获取强类型 + * {@link Post} 对象,提取标题、摘要、封面、作者、标签、发布/更新时间等完整 SEO 信息。 + * 这是插件最核心的 SEO 注入场景,字段精度最高。 + * + *

模板变量:{@code post} (PostVo), {@code name} (String) + *

数据来源:Post.spec.title, Post.status.excerpt, Post.spec.cover, + * Post.status.permalink, Post.spec.owner → User.spec.displayName, + * Post.spec.tags → Tag.spec.displayName, Post.spec.publishTime, + * Post.status.lastModifyTime + * + * @see 文章模板变量 + */ + private Mono processPostSeoData(ITemplateContext context, IModel model) { + var modelFactory = context.getModelFactory(); + // Halo 路由在上下文中设置 name = post.metadata.name + var postName = Optional.ofNullable(context.getVariable("name")).map(Object::toString) + .filter(name -> !name.isEmpty()).orElse(null); + if (postName == null) { + return Mono.empty(); + } + return client.fetch(Post.class, postName).flatMap(post -> buildSeoDataForPost(post).flatMap( + seoData -> generateSeoTags(seoData, model, modelFactory))); } - private Mono processPhotosSeoData() { - return Mono.empty(); + /** + * 分类详情页 SEO 注入。 + * + *

通过上下文变量 {@code name}(分类的 metadata.name)获取强类型 + * {@link Category} 对象。分类拥有 displayName、description、cover 等字段, + * 但没有独立的作者和发布时间。 + * + *

模板变量:{@code category} (CategoryVo), {@code posts} (UrlContextListResult), + * {@code name} (String) + *

数据来源:Category.spec.displayName, Category.spec.description, + * Category.spec.cover, Category.status.permalink + * + * @see + * 分类详情模板变量 + */ + private Mono processCategorySeoData(ITemplateContext context, IModel model) { + var modelFactory = context.getModelFactory(); + var categoryName = Optional.ofNullable(context.getVariable("name")).map(Object::toString) + .filter(s -> !s.isEmpty()).orElse(null); + if (categoryName == null) { + return Mono.empty(); + } + return client.fetch(Category.class, categoryName).flatMap( + category -> buildSeoDataForCategory(category).flatMap( + seoData -> generateSeoTags(seoData, model, modelFactory))); } - private Mono processFriendsSeoData() { - return Mono.empty(); + /** + * 标签详情页 SEO 注入。 + * + *

通过上下文变量 {@code name}(标签的 metadata.name)获取强类型 + * {@link Tag} 对象。注意:Tag 没有 description 字段,使用 + * "标签: displayName" 作为描述。 + * + *

模板变量:{@code tag} (TagVo), {@code posts} (UrlContextListResult), + * {@code name} (String) + *

数据来源:Tag.spec.displayName, Tag.spec.cover, Tag.status.permalink + * + * @see + * 标签详情模板变量 + */ + private Mono processTagSeoData(ITemplateContext context, IModel model) { + var modelFactory = context.getModelFactory(); + var tagName = Optional.ofNullable(context.getVariable("name")).map(Object::toString) + .filter(s -> !s.isEmpty()).orElse(null); + if (tagName == null) { + return Mono.empty(); + } + return client.fetch(Tag.class, tagName).flatMap(tag -> buildSeoDataForTag(tag).flatMap( + seoData -> generateSeoTags(seoData, model, modelFactory))); } - private Mono processDoubanSeoData() { - return Mono.empty(); + /** + * 独立页面 SEO 注入。 + * + *

独立页面与文章结构类似,拥有标题、摘要、封面、发布时间等字段, + * 通过 {@code name} 上下文变量获取 {@link SinglePage} 对象。 + * 与文章的区别:独立页面没有分类和标签,关键词使用站点级 SEO 关键词。 + * + *

模板变量:{@code singlePage} (SinglePageVo), {@code name} (String) + *

数据来源:SinglePage.spec.title, SinglePage.spec.cover, + * SinglePage.spec.publishTime, SinglePage.spec.owner → User.spec.displayName, + * SinglePage.status.permalink, SinglePage.status.excerpt, + * SinglePage.status.lastModifyTime(SinglePageStatus 继承 PostStatus) + * + * @see + * 独立页面模板变量 + */ + private Mono processSinglePageSeoData(ITemplateContext context, IModel model) { + var modelFactory = context.getModelFactory(); + var pageName = Optional.ofNullable(context.getVariable("name")).map(Object::toString) + .filter(s -> !s.isEmpty()).orElse(null); + if (pageName == null) { + return Mono.empty(); + } + return client.fetch(SinglePage.class, pageName).flatMap( + page -> buildSeoDataForSinglePage(page).flatMap( + seoData -> generateSeoTags(seoData, model, modelFactory))); } - private Mono processBangumiSeoData() { - return Mono.empty(); + /** + * 作者页 SEO 注入。 + * + *

通过上下文变量 {@code name}(用户的 metadata.name)获取强类型 + * {@link User} 对象。注意:User 没有 status.permalink 字段, + * URL 通过 "/authors/" + metadata.name 手动构造。 + * + *

模板变量:{@code author} (UserVo), {@code posts} (UrlContextListResult), + * {@code name} (String) + *

数据来源:User.spec.displayName, User.spec.bio, User.spec.avatar + * + * @see + * 作者模板变量 + */ + private Mono processAuthorSeoData(ITemplateContext context, IModel model) { + var modelFactory = context.getModelFactory(); + var userName = Optional.ofNullable(context.getVariable("name")).map(Object::toString) + .filter(s -> !s.isEmpty()).orElse(null); + if (userName == null) { + return Mono.empty(); + } + return client.fetch(User.class, userName).flatMap( + user -> buildSeoDataForAuthor(user).flatMap( + seoData -> generateSeoTags(seoData, model, modelFactory))); } + // ======================== SEO 数据构建方法 ======================== + + /** + * 列表页通用 SEO 数据构建。 + * + *

设计思路:列表页(首页、分类列表、标签列表、归档页、第三方插件列表页) + * 没有具体的内容实体对象,使用站点级信息构建 SEO 数据。 + *

+ * + * @param context Thymeleaf 模板上下文 + * @param model HTML 输出模型 + * @param pageTitle 页面语义标题,如 "首页"、"分类"、"标签" + * @param fallbackDesc 页面描述,为空时回退到站点 SEO 描述 + * @param pagePath 页面路径,如 "/"、"/categories"、"/tags" + */ + private Mono buildListPageSeoData(ITemplateContext context, IModel model, + String pageTitle, String fallbackDesc, String pagePath) { + var modelFactory = context.getModelFactory(); + + return Mono.zip(settingConfigGetter.getBasicConfig(), systemInfoGetter.get()) + .flatMap(tuple -> { + var config = tuple.getT1(); + var systemInfo = tuple.getT2(); + + var siteName = safeString(systemInfo.getTitle()); + var siteLogo = externalLinkProcessor.processLink(safeString(systemInfo.getLogo())); + + // 标题格式:pageTitle - siteName + var title = siteName.isBlank() ? pageTitle : pageTitle + " - " + siteName; + + // 描述优先级:传入描述 > 站点 SEO 描述 > 页面标题 + var siteDesc = + Optional.ofNullable(systemInfo.getSeo()).map(SystemInfo.SeoProp::getDescription) + .orElse(""); + var description = firstNonBlank(fallbackDesc, siteDesc, pageTitle); + + // 通过 ExternalLinkProcessor 将相对路径转为完整的外部 URL + var pageUrl = externalLinkProcessor.processLink(pagePath); + var coverUrl = externalLinkProcessor.processLink( + firstNonBlank(config.getDefaultImage(), systemInfo.getLogo())); + var keywords = + Optional.ofNullable(systemInfo.getSeo()).map(SystemInfo.SeoProp::getKeywords) + .orElse(""); + + // 列表页没有具体的发布/更新时间和作者信息,相关字段留空 + var seoData = + new SeoData(title, description, coverUrl, pageUrl, siteName, "", "", "", "", + siteName, siteLogo, keywords); + + return generateSeoTags(seoData, model, modelFactory); + }).onErrorResume(error -> { + log.warn("Failed to build list page SEO for: {}", pageTitle, error); + return Mono.empty(); + }); + } + + /** + * 构建文章详情页 SEO 数据。 + * + *

从 {@link Post} 强类型对象提取完整的 SEO 信息。 + * 并行获取四个数据源:文章所有者(User)、文章标签(Tag)、插件配置、系统信息。 + * + *

字段映射: + *

+ * + * @param post 文章 Extension 对象 + * @return 包含完整 SEO 数据的 Mono + */ private Mono buildSeoDataForPost(Post post) { + // 并行获取:文章所有者、文章标签、插件配置、系统信息 return Mono.zip(client.fetch(User.class, post.getSpec().getOwner()), findTagForPost(post), settingConfigGetter.getBasicConfig(), systemInfoGetter.get()).map(tuple -> { var user = tuple.getT1(); @@ -185,12 +541,14 @@ private Mono buildSeoDataForPost(Post post) { var config = tuple.getT3(); var systemInfo = tuple.getT4(); + // 作者名:优先 User.spec.displayName,回退到 owner 标识符 var author = Optional.of(user).map(User::getSpec).map(User.UserSpec::getDisplayName) .orElse(post.getSpec().getOwner()); var postUrl = externalLinkProcessor.processLink(post.getStatus().getPermalink()); var title = post.getSpec().getTitle(); var description = post.getStatus().getExcerpt(); + // 封面:优先文章封面,回退到插件默认封面 var coverUrl = externalLinkProcessor.processLink( Optional.ofNullable(post.getSpec().getCover()).filter(cover -> !cover.isBlank()) .orElse(config.getDefaultImage())); @@ -199,29 +557,234 @@ private Mono buildSeoDataForPost(Post post) { var updateInstant = post.getStatus().getLastModifyTime(); var zoneId = ZoneId.systemDefault(); - var baiduPubDate = formatDateTime(publishInstant, BAIDU_FORMATTER, zoneId); - var baiduUpdDate = formatDateTime(updateInstant, BAIDU_FORMATTER, zoneId); - var googlePubDate = formatDateTime(publishInstant, GOOGLE_FORMATTER, zoneId); - var googleUpdDate = formatDateTime(updateInstant, GOOGLE_FORMATTER, zoneId); - var siteName = systemInfo.getTitle(); var siteLogo = externalLinkProcessor.processLink(systemInfo.getLogo()); var siteKeywords = Optional.ofNullable(systemInfo.getSeo()).map(SystemInfo.SeoProp::getKeywords) .orElse(""); - + // 关键词:优先文章标签,回退到站点 SEO 关键词 var finalKeywords = keywords.isBlank() ? siteKeywords : keywords; - return new SeoData(title, description, coverUrl, postUrl, author, baiduPubDate, - baiduUpdDate, googlePubDate, googleUpdDate, siteName, siteLogo, finalKeywords); + return new SeoData(title, description, coverUrl, postUrl, author, + formatDateTime(publishInstant, BAIDU_FORMATTER, zoneId), + formatDateTime(updateInstant, BAIDU_FORMATTER, zoneId), + formatDateTime(publishInstant, GOOGLE_FORMATTER, zoneId), + formatDateTime(updateInstant, GOOGLE_FORMATTER, zoneId), siteName, siteLogo, + finalKeywords); }); } + /** + * 构建独立页面 SEO 数据。 + * + *

结构类似文章,从 {@link SinglePage} 提取 SEO 信息。 + * 与文章的区别:独立页面没有分类和标签,关键词使用站点级 SEO 关键词。 + * SinglePageStatus 继承 PostStatus,共享 getPermalink、getExcerpt、 + * getLastModifyTime 方法。 + * + *

字段映射: + *

    + *
  • 标题 ← {@code singlePage.spec.title}
  • + *
  • 摘要 ← {@code singlePage.status.excerpt}
  • + *
  • 封面 ← {@code singlePage.spec.cover},回退到插件默认封面
  • + *
  • URL ← {@code singlePage.status.permalink}
  • + *
  • 作者 ← 通过 {@code singlePage.spec.owner} 查询 User
  • + *
  • 日期 ← {@code spec.publishTime}, {@code status.lastModifyTime}
  • + *
  • 关键词 ← 站点 SEO 关键词(独立页面无标签)
  • + *
+ * + * @param page 独立页面 Extension 对象 + * @return 包含 SEO 数据的 Mono + */ + private Mono buildSeoDataForSinglePage(SinglePage page) { + // 并行获取:页面所有者、插件配置、系统信息 + return Mono.zip(client.fetch(User.class, page.getSpec().getOwner()), + settingConfigGetter.getBasicConfig(), systemInfoGetter.get()).map(tuple -> { + var user = tuple.getT1(); + var config = tuple.getT2(); + var systemInfo = tuple.getT3(); + + var author = Optional.of(user).map(User::getSpec).map(User.UserSpec::getDisplayName) + .orElse(page.getSpec().getOwner()); + + var pageUrl = externalLinkProcessor.processLink(page.getStatus().getPermalink()); + var title = page.getSpec().getTitle(); + var description = page.getStatus().getExcerpt(); + var coverUrl = externalLinkProcessor.processLink( + Optional.ofNullable(page.getSpec().getCover()).filter(cover -> !cover.isBlank()) + .orElse(config.getDefaultImage())); + + var publishInstant = page.getSpec().getPublishTime(); + var updateInstant = page.getStatus().getLastModifyTime(); + var zoneId = ZoneId.systemDefault(); + + var siteName = systemInfo.getTitle(); + var siteLogo = externalLinkProcessor.processLink(systemInfo.getLogo()); + var keywords = + Optional.ofNullable(systemInfo.getSeo()).map(SystemInfo.SeoProp::getKeywords) + .orElse(""); + + return new SeoData(title, description, coverUrl, pageUrl, author, + formatDateTime(publishInstant, BAIDU_FORMATTER, zoneId), + formatDateTime(updateInstant, BAIDU_FORMATTER, zoneId), + formatDateTime(publishInstant, GOOGLE_FORMATTER, zoneId), + formatDateTime(updateInstant, GOOGLE_FORMATTER, zoneId), siteName, siteLogo, + keywords); + }); + } + + /** + * 构建分类详情页 SEO 数据。 + * + *

从 {@link Category} 提取分类级 SEO 信息。分类没有独立的作者和发布时间, + * 作者字段使用站点名称,日期字段留空。 + * + *

字段映射: + *

    + *
  • 标题 ← {@code category.spec.displayName} + " - " + 站点名称
  • + *
  • 描述 ← {@code category.spec.description},回退到 "分类: displayName"
  • + *
  • 封面 ← {@code category.spec.cover},回退到默认封面/站点 Logo
  • + *
  • URL ← {@code category.status.permalink}
  • + *
  • 关键词 ← 分类显示名
  • + *
+ * + * @param category 分类 Extension 对象 + * @return 包含 SEO 数据的 Mono + */ + private Mono buildSeoDataForCategory(Category category) { + return Mono.zip(settingConfigGetter.getBasicConfig(), systemInfoGetter.get()).map(tuple -> { + var config = tuple.getT1(); + var systemInfo = tuple.getT2(); + + var displayName = safeString(category.getSpec().getDisplayName()); + var siteName = safeString(systemInfo.getTitle()); + var title = siteName.isBlank() ? displayName : displayName + " - " + siteName; + + // 分类有 description 字段,回退到 "分类: displayName" + var description = + firstNonBlank(category.getSpec().getDescription(), "分类: " + displayName); + + var pageUrl = externalLinkProcessor.processLink(category.getStatus().getPermalink()); + var coverUrl = externalLinkProcessor.processLink( + firstNonBlank(category.getSpec().getCover(), config.getDefaultImage(), + systemInfo.getLogo())); + var siteLogo = externalLinkProcessor.processLink(safeString(systemInfo.getLogo())); + + // 分类没有独立的作者和发布时间,作者使用站点名称,日期留空 + return new SeoData(title, description, coverUrl, pageUrl, siteName, "", "", "", "", + siteName, siteLogo, displayName); + }); + } + + /** + * 构建标签详情页 SEO 数据。 + * + *

从 {@link Tag} 提取标签级 SEO 信息。标签没有 description 字段, + * 描述使用 "标签: displayName" 构造。标签也没有独立的作者和发布时间。 + * + *

字段映射: + *

    + *
  • 标题 ← {@code tag.spec.displayName} + " - " + 站点名称
  • + *
  • 描述 ← "标签: " + displayName(Tag 无 description 字段)
  • + *
  • 封面 ← {@code tag.spec.cover},回退到默认封面/站点 Logo
  • + *
  • URL ← {@code tag.status.permalink}
  • + *
  • 关键词 ← 标签显示名
  • + *
+ * + * @param tag 标签 Extension 对象 + * @return 包含 SEO 数据的 Mono + */ + private Mono buildSeoDataForTag(Tag tag) { + return Mono.zip(settingConfigGetter.getBasicConfig(), systemInfoGetter.get()).map(tuple -> { + var config = tuple.getT1(); + var systemInfo = tuple.getT2(); + + var displayName = safeString(tag.getSpec().getDisplayName()); + var siteName = safeString(systemInfo.getTitle()); + var title = siteName.isBlank() ? displayName : displayName + " - " + siteName; + // Tag 没有 description 字段,使用语义描述 + var description = "标签: " + displayName; + + var pageUrl = externalLinkProcessor.processLink(tag.getStatus().getPermalink()); + var coverUrl = externalLinkProcessor.processLink( + firstNonBlank(tag.getSpec().getCover(), config.getDefaultImage(), + systemInfo.getLogo())); + var siteLogo = externalLinkProcessor.processLink(safeString(systemInfo.getLogo())); + + // 标签没有独立的作者和发布时间 + return new SeoData(title, description, coverUrl, pageUrl, siteName, "", "", "", "", + siteName, siteLogo, displayName); + }); + } + + /** + * 构建作者页 SEO 数据。 + * + *

从 {@link User} 提取作者级 SEO 信息。注意 User 没有 + * {@code status.permalink} 字段,URL 通过 "/authors/" + metadata.name 手动构造。 + * + *

字段映射: + *

    + *
  • 标题 ← {@code user.spec.displayName} + " - " + 站点名称
  • + *
  • 描述 ← {@code user.spec.bio},回退到 "作者: displayName"
  • + *
  • 封面 ← {@code user.spec.avatar},回退到默认封面/站点 Logo
  • + *
  • URL ← "/authors/" + {@code user.metadata.name}
  • + *
  • 作者 ← {@code user.spec.displayName}
  • + *
  • 关键词 ← 作者显示名
  • + *
+ * + * @param user 用户 Extension 对象 + * @return 包含 SEO 数据的 Mono + */ + private Mono buildSeoDataForAuthor(User user) { + return Mono.zip(settingConfigGetter.getBasicConfig(), systemInfoGetter.get()).map(tuple -> { + var config = tuple.getT1(); + var systemInfo = tuple.getT2(); + + var displayName = safeString(user.getSpec().getDisplayName()); + var siteName = safeString(systemInfo.getTitle()); + var title = siteName.isBlank() ? displayName : displayName + " - " + siteName; + var description = firstNonBlank(user.getSpec().getBio(), "作者: " + displayName); + + // User 没有 status.permalink,手动构造作者归档页 URL + var pageUrl = + externalLinkProcessor.processLink("/authors/" + user.getMetadata().getName()); + var coverUrl = externalLinkProcessor.processLink( + firstNonBlank(user.getSpec().getAvatar(), config.getDefaultImage(), + systemInfo.getLogo())); + var siteLogo = externalLinkProcessor.processLink(safeString(systemInfo.getLogo())); + + // 作者页的 author 字段即为用户自身 + return new SeoData(title, description, coverUrl, pageUrl, displayName, "", "", "", "", + siteName, siteLogo, displayName); + }); + } + + // ======================== SEO 标签生成管道 ======================== + + /** + * 根据 SEO 数据和插件配置生成完整的 HTML meta/script 标签。 + * + *

所有页面类型的 SEO 数据最终汇入此方法,按插件设置依次生成: + *

    + *
  1. canonical 链接标签(帮助搜索引擎识别内容唯一地址)
  2. + *
  3. alternate 链接标签(多语言/多版本支持)
  4. + *
  5. Open Graph (OG) meta 标签(主流社交平台和搜索引擎)
  6. + *
  7. 字节跳动 (Bytedance) meta 标签(头条系搜索引擎)
  8. + *
  9. 百度时间因子 script 标签(百度搜索引擎)
  10. + *
  11. Schema.org JSON-LD 结构化数据(Google、Bing)
  12. + *
  13. Twitter Cards meta 标签(Twitter/X 平台)
  14. + *
+ * + * @param seoData SEO 数据记录 + * @param model HTML 输出模型 + * @param modelFactory Thymeleaf 模型工厂,用于创建 HTML 文本节点 + */ private Mono generateSeoTags(SeoData seoData, IModel model, IModelFactory modelFactory) { return settingConfigGetter.getBasicConfig().doOnNext(config -> { var sb = new StringBuilder(); - // 使用if-else简化配置检查 + // canonical 和 alternate 链接 if (config.isEnableCanonicalLink()) { sb.append("\n"); @@ -237,20 +800,24 @@ private Mono generateSeoTags(SeoData seoData, IModel model, IModelFactory } } + // OG meta 标签 if (config.isEnableOGTimeFactor()) { sb.append(genOGMeta(seoData)); } + // 字节跳动 meta 标签 if (config.isEnableMetaTimeFactor()) { sb.append(genBytedanceMeta(seoData.baiduPubDate(), seoData.baiduUpdDate())); } + // 百度时间因子 if (config.isEnableBaiduTimeFactor()) { sb.append(genBaiduScript(seoData.title(), seoData.postUrl(), seoData.baiduPubDate(), seoData.baiduUpdDate())); } + // Schema.org JSON-LD if (config.isEnableStructuredData()) { sb.append(genSchemaOrgScript(seoData)); } - + // Twitter Cards if (config.isEnableTwitterCards()) { sb.append(genTwitterCards(seoData, config.getTwitterCardsType(), config.getTwitterSiteUsername(), config.getTwitterSiteUserId(), @@ -261,28 +828,16 @@ private Mono generateSeoTags(SeoData seoData, IModel model, IModelFactory }).then(); } - private Mono findTagForPost(Post post) { - var tagNames = post.getSpec().getTags(); - if (tagNames == null || tagNames.isEmpty()) { - return Mono.just(""); - } - - var listOptions = new ListOptions(); - listOptions.setFieldSelector( - FieldSelector.of(Queries.in("metadata.name", tagNames.toArray(new String[0])))); - - return client.listAll(Tag.class, listOptions, Sort.by(Sort.Order.asc("metadata.name"))).map( - tag -> Optional.ofNullable(tag.getSpec().getDisplayName()) - .orElse(tag.getMetadata().getName())).collectList() - .map(list -> String.join(",", list)).onErrorReturn("").defaultIfEmpty(""); - } - - private String formatDateTime(java.time.Instant instant, DateTimeFormatter formatter, - ZoneId zoneId) { - return Optional.ofNullable(instant).map(inst -> inst.atZone(zoneId).format(formatter)) - .orElse(""); - } + // ======================== 标签生成方法 ======================== + /** + * 生成 Open Graph meta 标签。 + * + *

OG 协议被 Facebook、LinkedIn、微信等主流社交平台支持, + * 也被 Google、Bing 等搜索引擎用于增强搜索结果展示。 + * + * @see The Open Graph protocol + */ private String genOGMeta(SeoData seoData) { return """ @@ -299,6 +854,11 @@ private String genOGMeta(SeoData seoData) { HtmlEscape.escapeHtml5(seoData.author())); } + /** + * 生成字节跳动时间因子 meta 标签。 + * + *

用于今日头条、抖音搜索等字节系产品的 SEO 优化。 + */ private String genBytedanceMeta(String publishDate, String updateDate) { return """ @@ -306,6 +866,14 @@ private String genBytedanceMeta(String publishDate, String updateDate) { """.formatted(publishDate, updateDate); } + /** + * 生成百度时间因子 JSON-LD script 标签。 + * + *

使用百度专用的 cambrian.jsonld 上下文,帮助百度搜索引擎 + * 识别内容的发布和更新时间。 + * + * @see 百度时间因子文档 + */ private String genBaiduScript(String title, String url, String publishDate, String updateDate) { return """ - """.formatted(url, JsonEscape.escapeJson(title), publishDate, updateDate); + // 百度时间因子的 pubDate 和 upDate 为核心字段,全部为空时跳过整个 script 标签 + if (publishDate.isBlank() && updateDate.isBlank()) { + return ""; + } + var sb = new StringBuilder(); + sb.append("\n"); + return sb.toString(); } /** @@ -897,41 +923,52 @@ private String genBaiduScript(String title, String url, String publishDate, Stri * @see Schema.org BlogPosting */ private String genSchemaOrgScript(SeoData seoData) { - return """ - - """.formatted(seoData.postUrl(), JsonEscape.escapeJson(seoData.title()), - JsonEscape.escapeJson(seoData.description()), seoData.googlePubDate(), - seoData.googleUpdDate(), JsonEscape.escapeJson(seoData.author()), - JsonEscape.escapeJson(seoData.siteName()), seoData.siteLogo(), seoData.coverUrl(), - seoData.postUrl(), JsonEscape.escapeJson(seoData.keywords())); + // 根据 pageType 推导 Schema.org @type:article→BlogPosting, profile→ProfilePage, 其他→WebPage + var schemaType = switch (seoData.pageType()) { + case "article" -> "BlogPosting"; + case "profile" -> "ProfilePage"; + default -> "WebPage"; + }; + // 使用 StringBuilder 动态构建 JSON-LD,日期字段为空时省略,避免无效结构化数据 + var sb = new StringBuilder(); + sb.append("\n"); + return sb.toString(); } /** @@ -1068,10 +1105,12 @@ private String firstNonBlank(String... values) { * @param siteName 站点名称 * @param siteLogo 站点 Logo URL * @param keywords 关键词 + * @param pageType 页面语义类型,用于推导 og:type 和 Schema.org @type。 + * 取值:website(列表/聚合页)、article(内容详情页)、profile(作者页) */ private record SeoData(String title, String description, String coverUrl, String postUrl, String author, String baiduPubDate, String baiduUpdDate, String googlePubDate, String googleUpdDate, String siteName, - String siteLogo, String keywords) { + String siteLogo, String keywords, String pageType) { } } From 909f683c630ba445a9687658b47ea6e1c0ee2653 Mon Sep 17 00:00:00 2001 From: HowieHz Date: Tue, 24 Mar 2026 22:32:11 +0800 Subject: [PATCH 03/19] Null-safe SEO tags, permalink and escaping --- .../timefactor/process/TimeFactorProcess.java | 332 ++++++++++-------- 1 file changed, 191 insertions(+), 141 deletions(-) diff --git a/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java b/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java index 11522b0..14e81e0 100644 --- a/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java +++ b/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java @@ -1,6 +1,11 @@ package cc.lik.timefactor.process; import cc.lik.timefactor.service.SettingConfigGetter; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.Arrays; +import java.util.Optional; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -27,11 +32,6 @@ import run.halo.app.infra.SystemInfoGetter; import run.halo.app.theme.dialect.TemplateHeadProcessor; import run.halo.app.theme.router.ModelConst; -import java.time.Instant; -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; -import java.util.Arrays; -import java.util.Optional; /** * 模板 ID 枚举,映射 Halo 路由系统中的 {@code _templateId} 到具体页面类型。 @@ -478,30 +478,30 @@ private Mono buildListPageSeoData(ITemplateContext context, IModel model, var config = tuple.getT1(); var systemInfo = tuple.getT2(); - var siteName = safeString(systemInfo.getTitle()); - var siteLogo = externalLinkProcessor.processLink(safeString(systemInfo.getLogo())); + var siteName = systemInfo.getTitle(); + var siteLogo = processPermalink(systemInfo.getLogo()); // 标题格式:pageTitle - siteName - var title = siteName.isBlank() ? pageTitle : pageTitle + " - " + siteName; + var title = hasValue(siteName) ? pageTitle + " - " + siteName : pageTitle; // 描述优先级:传入描述 > 站点 SEO 描述 > 页面标题 var siteDesc = Optional.ofNullable(systemInfo.getSeo()).map(SystemInfo.SeoProp::getDescription) - .orElse(""); + .orElse(null); var description = firstNonBlank(fallbackDesc, siteDesc, pageTitle); // 通过 ExternalLinkProcessor 将相对路径转为完整的外部 URL var pageUrl = externalLinkProcessor.processLink(pagePath); - var coverUrl = externalLinkProcessor.processLink( - firstNonBlank(config.getDefaultImage(), systemInfo.getLogo())); + var coverUrl = + processPermalink(firstNonBlank(config.getDefaultImage(), systemInfo.getLogo())); var keywords = Optional.ofNullable(systemInfo.getSeo()).map(SystemInfo.SeoProp::getKeywords) - .orElse(""); + .orElse(null); // 列表页没有具体的发布/更新时间和作者信息,相关字段留空;pageType 为 website var seoData = - new SeoData(title, description, coverUrl, pageUrl, siteName, "", "", "", "", - siteName, siteLogo, keywords, "website"); + new SeoData(title, description, coverUrl, pageUrl, siteName, null, null, null, + null, siteName, siteLogo, keywords, "website"); return generateSeoTags(seoData, model, modelFactory); }).onErrorResume(error -> { @@ -545,25 +545,26 @@ private Mono buildSeoDataForPost(Post post) { var author = Optional.of(user).map(User::getSpec).map(User.UserSpec::getDisplayName) .orElse(post.getSpec().getOwner()); - var postUrl = externalLinkProcessor.processLink(post.getStatus().getPermalink()); + // status 字段在 Reconciler 首次处理前为 null,使用 getStatusOrDefault() 安全获取 + var status = post.getStatusOrDefault(); + var postUrl = processPermalink(status.getPermalink()); var title = post.getSpec().getTitle(); - var description = post.getStatus().getExcerpt(); + var description = status.getExcerpt(); // 封面:优先文章封面,回退到插件默认封面 - var coverUrl = externalLinkProcessor.processLink( - Optional.ofNullable(post.getSpec().getCover()).filter(cover -> !cover.isBlank()) - .orElse(config.getDefaultImage())); + var coverUrl = processPermalink( + firstNonBlank(post.getSpec().getCover(), config.getDefaultImage())); var publishInstant = post.getSpec().getPublishTime(); - var updateInstant = post.getStatus().getLastModifyTime(); + var updateInstant = status.getLastModifyTime(); var zoneId = ZoneId.systemDefault(); var siteName = systemInfo.getTitle(); - var siteLogo = externalLinkProcessor.processLink(systemInfo.getLogo()); + var siteLogo = processPermalink(systemInfo.getLogo()); var siteKeywords = Optional.ofNullable(systemInfo.getSeo()).map(SystemInfo.SeoProp::getKeywords) - .orElse(""); + .orElse(null); // 关键词:优先文章标签,回退到站点 SEO 关键词 - var finalKeywords = keywords.isBlank() ? siteKeywords : keywords; + var finalKeywords = hasValue(keywords) ? keywords : siteKeywords; return new SeoData(title, description, coverUrl, postUrl, author, formatDateTime(publishInstant, BAIDU_FORMATTER, zoneId), @@ -607,22 +608,23 @@ private Mono buildSeoDataForSinglePage(SinglePage page) { var author = Optional.of(user).map(User::getSpec).map(User.UserSpec::getDisplayName) .orElse(page.getSpec().getOwner()); - var pageUrl = externalLinkProcessor.processLink(page.getStatus().getPermalink()); + // status 字段在 Reconciler 首次处理前为 null,使用 getStatusOrDefault() 安全获取 + var status = page.getStatusOrDefault(); + var pageUrl = processPermalink(status.getPermalink()); var title = page.getSpec().getTitle(); - var description = page.getStatus().getExcerpt(); - var coverUrl = externalLinkProcessor.processLink( - Optional.ofNullable(page.getSpec().getCover()).filter(cover -> !cover.isBlank()) - .orElse(config.getDefaultImage())); + var description = status.getExcerpt(); + var coverUrl = processPermalink( + firstNonBlank(page.getSpec().getCover(), config.getDefaultImage())); var publishInstant = page.getSpec().getPublishTime(); - var updateInstant = page.getStatus().getLastModifyTime(); + var updateInstant = status.getLastModifyTime(); var zoneId = ZoneId.systemDefault(); var siteName = systemInfo.getTitle(); - var siteLogo = externalLinkProcessor.processLink(systemInfo.getLogo()); + var siteLogo = processPermalink(systemInfo.getLogo()); var keywords = Optional.ofNullable(systemInfo.getSeo()).map(SystemInfo.SeoProp::getKeywords) - .orElse(""); + .orElse(null); return new SeoData(title, description, coverUrl, pageUrl, author, formatDateTime(publishInstant, BAIDU_FORMATTER, zoneId), @@ -656,23 +658,24 @@ private Mono buildSeoDataForCategory(Category category) { var config = tuple.getT1(); var systemInfo = tuple.getT2(); - var displayName = safeString(category.getSpec().getDisplayName()); - var siteName = safeString(systemInfo.getTitle()); - var title = siteName.isBlank() ? displayName : displayName + " - " + siteName; + var displayName = category.getSpec().getDisplayName(); + var siteName = systemInfo.getTitle(); + var title = hasValue(siteName) ? displayName + " - " + siteName : displayName; // 分类有 description 字段,回退到 "分类: displayName" var description = firstNonBlank(category.getSpec().getDescription(), "分类: " + displayName); - var pageUrl = externalLinkProcessor.processLink(category.getStatus().getPermalink()); - var coverUrl = externalLinkProcessor.processLink( + // status 字段在 Reconciler 首次处理前为 null,使用 getStatusOrDefault() 安全获取 + var pageUrl = processPermalink(category.getStatusOrDefault().getPermalink()); + var coverUrl = processPermalink( firstNonBlank(category.getSpec().getCover(), config.getDefaultImage(), systemInfo.getLogo())); - var siteLogo = externalLinkProcessor.processLink(safeString(systemInfo.getLogo())); + var siteLogo = processPermalink(systemInfo.getLogo()); // 分类没有独立的作者和发布时间,作者使用站点名称,日期留空;pageType 为 website - return new SeoData(title, description, coverUrl, pageUrl, siteName, "", "", "", "", - siteName, siteLogo, displayName, "website"); + return new SeoData(title, description, coverUrl, pageUrl, siteName, null, null, null, + null, siteName, siteLogo, displayName, "website"); }); } @@ -699,21 +702,22 @@ private Mono buildSeoDataForTag(Tag tag) { var config = tuple.getT1(); var systemInfo = tuple.getT2(); - var displayName = safeString(tag.getSpec().getDisplayName()); - var siteName = safeString(systemInfo.getTitle()); - var title = siteName.isBlank() ? displayName : displayName + " - " + siteName; + var displayName = tag.getSpec().getDisplayName(); + var siteName = systemInfo.getTitle(); + var title = hasValue(siteName) ? displayName + " - " + siteName : displayName; // Tag 没有 description 字段,使用语义描述 var description = "标签: " + displayName; - var pageUrl = externalLinkProcessor.processLink(tag.getStatus().getPermalink()); - var coverUrl = externalLinkProcessor.processLink( + // status 字段在 Reconciler 首次处理前为 null,使用 getStatusOrDefault() 安全获取 + var pageUrl = processPermalink(tag.getStatusOrDefault().getPermalink()); + var coverUrl = processPermalink( firstNonBlank(tag.getSpec().getCover(), config.getDefaultImage(), systemInfo.getLogo())); - var siteLogo = externalLinkProcessor.processLink(safeString(systemInfo.getLogo())); + var siteLogo = processPermalink(systemInfo.getLogo()); // 标签没有独立的作者和发布时间;pageType 为 website - return new SeoData(title, description, coverUrl, pageUrl, siteName, "", "", "", "", - siteName, siteLogo, displayName, "website"); + return new SeoData(title, description, coverUrl, pageUrl, siteName, null, null, null, + null, siteName, siteLogo, displayName, "website"); }); } @@ -741,22 +745,22 @@ private Mono buildSeoDataForAuthor(User user) { var config = tuple.getT1(); var systemInfo = tuple.getT2(); - var displayName = safeString(user.getSpec().getDisplayName()); - var siteName = safeString(systemInfo.getTitle()); - var title = siteName.isBlank() ? displayName : displayName + " - " + siteName; + var displayName = user.getSpec().getDisplayName(); + var siteName = systemInfo.getTitle(); + var title = hasValue(siteName) ? displayName + " - " + siteName : displayName; var description = firstNonBlank(user.getSpec().getBio(), "作者: " + displayName); // User 没有 status.permalink,手动构造作者归档页 URL var pageUrl = externalLinkProcessor.processLink("/authors/" + user.getMetadata().getName()); - var coverUrl = externalLinkProcessor.processLink( + var coverUrl = processPermalink( firstNonBlank(user.getSpec().getAvatar(), config.getDefaultImage(), systemInfo.getLogo())); - var siteLogo = externalLinkProcessor.processLink(safeString(systemInfo.getLogo())); + var siteLogo = processPermalink(systemInfo.getLogo()); // 作者页的 author 字段即为用户自身;pageType 为 profile - return new SeoData(title, description, coverUrl, pageUrl, displayName, "", "", "", "", - siteName, siteLogo, displayName, "profile"); + return new SeoData(title, description, coverUrl, pageUrl, displayName, null, null, null, + null, siteName, siteLogo, displayName, "profile"); }); } @@ -784,18 +788,25 @@ private Mono generateSeoTags(SeoData seoData, IModel model, IModelFactory return settingConfigGetter.getBasicConfig().doOnNext(config -> { var sb = new StringBuilder(); - // canonical 和 alternate 链接 - if (config.isEnableCanonicalLink()) { + // canonical 和 alternate 链接(pageUrl 为空时跳过,无有效 URL 的页面不输出 canonical) + if (config.isEnableCanonicalLink() && hasValue(seoData.pageUrl())) { sb.append("\n"); + .append(HtmlEscape.escapeHtml5(seoData.pageUrl())).append("\" />\n"); if (config.isEnableAlternateLink()) { - for (var altLink : config.getAlternateLinks()) { - sb.append("\n"); + var alternateLinks = config.getAlternateLinks(); + if (alternateLinks == null) { + log.warn( + "Alternate link is enabled but alternateLinks list is null, skipping " + + "alternate tags"); + } else { + for (var altLink : alternateLinks) { + sb.append("\n"); + } } } } @@ -810,7 +821,7 @@ private Mono generateSeoTags(SeoData seoData, IModel model, IModelFactory } // 百度时间因子 if (config.isEnableBaiduTimeFactor()) { - sb.append(genBaiduScript(seoData.title(), seoData.postUrl(), seoData.baiduPubDate(), + sb.append(genBaiduScript(seoData.title(), seoData.pageUrl(), seoData.baiduPubDate(), seoData.baiduUpdDate())); } // Schema.org JSON-LD @@ -839,27 +850,35 @@ private Mono generateSeoTags(SeoData seoData, IModel model, IModelFactory * @see The Open Graph protocol */ private String genOGMeta(SeoData seoData) { + // 所有字段按需输出:null 或空白时跳过对应标签,避免生成无意义的空标签 var sb = new StringBuilder(); sb.append("\n"); - sb.append("\n"); - sb.append("\n"); - sb.append("\n"); - sb.append("\n"); - // 日期和作者字段仅在非空时输出,避免生成无意义的空标签 - if (!seoData.baiduPubDate().isBlank()) { + if (hasValue(seoData.title())) { + sb.append("\n"); + } + if (hasValue(seoData.description())) { + sb.append("\n"); + } + if (hasValue(seoData.coverUrl())) { + sb.append("\n"); + } + if (hasValue(seoData.pageUrl())) { + sb.append("\n"); + } + if (hasValue(seoData.baiduPubDate())) { sb.append("\n"); + .append(HtmlEscape.escapeHtml5(seoData.baiduPubDate())).append("\" />\n"); } - if (!seoData.baiduUpdDate().isBlank()) { + if (hasValue(seoData.baiduUpdDate())) { sb.append("\n"); + .append(HtmlEscape.escapeHtml5(seoData.baiduUpdDate())).append("\" />\n"); } - if (!seoData.author().isBlank()) { + if (hasValue(seoData.author())) { sb.append("\n"); } @@ -872,15 +891,14 @@ private String genOGMeta(SeoData seoData) { *

用于今日头条、抖音搜索等字节系产品的 SEO 优化。 */ private String genBytedanceMeta(String publishDate, String updateDate) { - // 字节跳动时间因子仅在日期非空时输出,列表页等无日期的页面不生成此标签 var sb = new StringBuilder(); - if (!publishDate.isBlank()) { - sb.append("\n"); + if (hasValue(publishDate)) { + sb.append("\n"); } - if (!updateDate.isBlank()) { - sb.append("\n"); + if (hasValue(updateDate)) { + sb.append("\n"); } return sb.toString(); } @@ -895,19 +913,21 @@ private String genBytedanceMeta(String publishDate, String updateDate) { */ private String genBaiduScript(String title, String url, String publishDate, String updateDate) { // 百度时间因子的 pubDate 和 upDate 为核心字段,全部为空时跳过整个 script 标签 - if (publishDate.isBlank() && updateDate.isBlank()) { + if (!hasValue(publishDate) && !hasValue(updateDate)) { return ""; } var sb = new StringBuilder(); sb.append("\n"); @@ -929,45 +949,67 @@ private String genSchemaOrgScript(SeoData seoData) { case "profile" -> "ProfilePage"; default -> "WebPage"; }; - // 使用 StringBuilder 动态构建 JSON-LD,日期字段为空时省略,避免无效结构化数据 + // 使用 StringBuilder 动态构建 JSON-LD,字段为空时省略,避免无效结构化数据 var sb = new StringBuilder(); sb.append("\n"); + if (hasValue(seoData.author())) { + sb.append(",\n \"author\": {\n"); + sb.append(" \"@type\": \"Person\",\n"); + sb.append(" \"name\": \"").append(JsonEscape.escapeJson(seoData.author())) + .append("\"\n"); + sb.append(" }"); + } + if (hasValue(seoData.siteName())) { + sb.append(",\n \"publisher\": {\n"); + sb.append(" \"@type\": \"Organization\",\n"); + sb.append(" \"name\": \"").append(JsonEscape.escapeJson(seoData.siteName())) + .append("\""); + if (hasValue(seoData.siteLogo())) { + sb.append(",\n \"logo\": {\n"); + sb.append(" \"@type\": \"ImageObject\",\n"); + sb.append(" \"url\": \"").append(JsonEscape.escapeJson(seoData.siteLogo())) + .append("\"\n"); + sb.append(" }"); + } + sb.append("\n }"); + } + if (hasValue(seoData.coverUrl())) { + sb.append(",\n \"image\": \"").append(JsonEscape.escapeJson(seoData.coverUrl())) + .append("\""); + } + if (hasValue(seoData.pageUrl())) { + sb.append(",\n \"url\": \"").append(JsonEscape.escapeJson(seoData.pageUrl())) + .append("\""); + } + if (hasValue(seoData.keywords())) { + sb.append(",\n \"keywords\": \"").append(JsonEscape.escapeJson(seoData.keywords())) + .append("\""); + } + sb.append("\n}\n\n"); return sb.toString(); } @@ -991,34 +1033,34 @@ private String genTwitterCards(SeoData seoData, String twitterCardsType, sb.append("\n"); - // 站点 Twitter 用户名(可选) - if (twitterSiteUsername != null && !twitterSiteUsername.trim().isEmpty()) { + if (hasValue(twitterSiteUsername)) { sb.append("\n"); } - // 站点 Twitter 用户 ID(可选) - if (twitterSiteUserId != null && !twitterSiteUserId.trim().isEmpty()) { + if (hasValue(twitterSiteUserId)) { sb.append("\n"); } - // 创作者 Twitter 用户名(可选) - if (twitterCreatorUsername != null && !twitterCreatorUsername.trim().isEmpty()) { + if (hasValue(twitterCreatorUsername)) { sb.append("\n"); } - // 创作者 Twitter 用户 ID(可选) - if (twitterCreatorUserId != null && !twitterCreatorUserId.trim().isEmpty()) { + if (hasValue(twitterCreatorUserId)) { sb.append("\n"); } - - // 标题、描述、图片标签始终添加 - sb.append("\n"); - sb.append("\n"); - sb.append("\n"); + if (hasValue(seoData.title())) { + sb.append("\n"); + } + if (hasValue(seoData.description())) { + sb.append("\n"); + } + if (hasValue(seoData.coverUrl())) { + sb.append("\n"); + } return sb.toString(); } @@ -1064,10 +1106,18 @@ private String formatDateTime(Instant instant, DateTimeFormatter formatter, Zone } /** - * 空安全字符串转换,将 null 转为空字符串。 + * 判断字符串是否有实际内容(非 null 且非空白)。 + * 用于输出阶段决定是否生成对应的 SEO 标签:无值则跳过,避免输出空标签。 + */ + private boolean hasValue(String value) { + return value != null && !value.isBlank(); + } + + /** + * 将相对路径或路径片段转为完整外部 URL,null 或空白时返回 null(表示无值)。 */ - private String safeString(String value) { - return value == null ? "" : value; + private String processPermalink(String path) { + return hasValue(path) ? externalLinkProcessor.processLink(path) : null; } /** @@ -1096,7 +1146,7 @@ private String firstNonBlank(String... values) { * @param title 页面标题 * @param description 页面描述/摘要 * @param coverUrl 封面图 URL(已处理为完整外部链接) - * @param postUrl 页面 URL(已处理为完整外部链接) + * @param pageUrl 页面 URL(已处理为完整外部链接) * @param author 作者名称 * @param baiduPubDate 百度格式发布时间 * @param baiduUpdDate 百度格式更新时间 @@ -1108,7 +1158,7 @@ private String firstNonBlank(String... values) { * @param pageType 页面语义类型,用于推导 og:type 和 Schema.org @type。 * 取值:website(列表/聚合页)、article(内容详情页)、profile(作者页) */ - private record SeoData(String title, String description, String coverUrl, String postUrl, + private record SeoData(String title, String description, String coverUrl, String pageUrl, String author, String baiduPubDate, String baiduUpdDate, String googlePubDate, String googleUpdDate, String siteName, String siteLogo, String keywords, String pageType) { From 42b8a48e9b36a7f0ae2ff9e9c9d31f25a1b8f3e0 Mon Sep 17 00:00:00 2001 From: HowieHz Date: Tue, 24 Mar 2026 22:34:56 +0800 Subject: [PATCH 04/19] Update README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index c50f6d4..13d2c05 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,7 @@ - **演示站点**:[https://www.lik.cc/](https://www.lik.cc/) - **文档**:[https://docs.lik.cc/](https://docs.lik.cc/) -- **QQ 交流群 - **:[![QQ群](https://www.lik.cc/upload/iShot_2025-03-03_16.03.00.png)](https://www.lik.cc/upload/iShot_2025-03-03_16.03.00.png) +- **QQ 交流群**:[![QQ群](https://www.lik.cc/upload/iShot_2025-03-03_16.03.00.png)](https://www.lik.cc/upload/iShot_2025-03-03_16.03.00.png) ## 功能特性 From e9614a07fdeb9bf9fd7aca47dc4620332345a2f2 Mon Sep 17 00:00:00 2001 From: HowieHz Date: Wed, 25 Mar 2026 07:58:39 +0800 Subject: [PATCH 05/19] Clarify author use for list pages --- src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java b/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java index 14e81e0..c03588d 100644 --- a/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java +++ b/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java @@ -498,7 +498,7 @@ private Mono buildListPageSeoData(ITemplateContext context, IModel model, Optional.ofNullable(systemInfo.getSeo()).map(SystemInfo.SeoProp::getKeywords) .orElse(null); - // 列表页没有具体的发布/更新时间和作者信息,相关字段留空;pageType 为 website + // 列表页没有具体的发布/更新时间,相关字段留空,作者使用站点名称;pageType 为 website var seoData = new SeoData(title, description, coverUrl, pageUrl, siteName, null, null, null, null, siteName, siteLogo, keywords, "website"); From 2ca71b44bb86f8c4c2dcb371e06860f2ec9e2626 Mon Sep 17 00:00:00 2001 From: HowieHz Date: Wed, 25 Mar 2026 09:31:48 +0800 Subject: [PATCH 06/19] Bump deps and refactor SEO name handling --- README.md | 4 +- build.gradle | 4 +- .../timefactor/process/TimeFactorProcess.java | 85 +- src/main/resources/plugin.yaml | 2 +- ui/package.json | 38 +- ui/pnpm-lock.yaml | 3743 ++++++++++------- 6 files changed, 2370 insertions(+), 1506 deletions(-) diff --git a/README.md b/README.md index 13d2c05..8dbc5e6 100644 --- a/README.md +++ b/README.md @@ -72,8 +72,8 @@ |--------|-----------|--------------------|:-------:|:--:|:-------:|----------------------------| | 文章详情页 | `article` | `BlogPosting` | ✅ | ✅ | ✅ 文章标签 | 字段最完整的页面类型 | | 独立页面 | `article` | `BlogPosting` | ✅ | ✅ | ✅ 站点关键词 | 独立页面无标签,关键词回退到站点级 | -| 分类详情页 | `website` | `WebPage` | ❌ | ❌ | ✅ 分类名 | 分类无发布时间和作者 | -| 标签详情页 | `website` | `WebPage` | ❌ | ❌ | ✅ 标签名 | 标签无发布时间和作者 | +| 分类详情页 | `website` | `WebPage` | ❌ | ❌ | ✅ 分类名 | 分类无发布时间和作者;描述取自分类描述字段 | +| 标签详情页 | `website` | `WebPage` | ❌ | ❌ | ✅ 标签名 | 标签无发布时间和作者;描述取自标签描述字段 | | 作者页 | `profile` | `ProfilePage` | ❌ | ✅ | ✅ 作者名 | 作者页无发布时间 | | 列表/聚合页 | `website` | `WebPage` | ❌ | ❌ | ✅ 站点关键词 | 首页、分类列表、标签列表、归档页、所有第三方插件页面 | diff --git a/build.gradle b/build.gradle index 7122185..f4a15f2 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ repositories { } dependencies { - implementation platform('run.halo.tools.platform:plugin:2.22.0') + implementation platform('run.halo.tools.platform:plugin:2.23.0') compileOnly 'run.halo.app:api' testImplementation 'run.halo.app:api' @@ -48,6 +48,6 @@ tasks.named('classes') { halo { /* debug = true suspend = true*/ - version = '2.22' + version = '2.23.1' } diff --git a/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java b/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java index c03588d..120b093 100644 --- a/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java +++ b/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java @@ -24,6 +24,7 @@ import run.halo.app.core.extension.content.SinglePage; import run.halo.app.core.extension.content.Tag; import run.halo.app.extension.ListOptions; +import run.halo.app.extension.MetadataOperator; import run.halo.app.extension.ReactiveExtensionClient; import run.halo.app.extension.index.query.Queries; import run.halo.app.extension.router.selector.FieldSelector; @@ -31,6 +32,7 @@ import run.halo.app.infra.SystemInfo; import run.halo.app.infra.SystemInfoGetter; import run.halo.app.theme.dialect.TemplateHeadProcessor; +import run.halo.app.theme.finders.vo.ExtensionVoOperator; import run.halo.app.theme.router.ModelConst; /** @@ -143,7 +145,7 @@ public Mono process(ITemplateContext context, IModel model, Optional.ofNullable(context.getVariable(ModelConst.TEMPLATE_ID)).map(Object::toString) .orElse(null); var template = TemplateEnum.fromTemplateId(templateId); - log.debug("Processing SEO for templateId: {}", templateId); + log.info("Processing SEO for templateId: {}", templateId); // 按模板类型分发,全局捕获异常避免影响页面渲染 Mono result = switch (template) { @@ -250,6 +252,7 @@ private Mono processMomentsSeoData(ITemplateContext context, IModel model) * 如需更精确的 SEO 数据,可在未来版本通过 Finder API 增强。 */ private Mono processMomentSeoData(ITemplateContext context, IModel model) { + // MomentVo 在上下文中是延迟加载的 Mono 对象,无法直接获取 metadata.name,降级使用站点信息 return buildListPageSeoData(context, model, "瞬间", "瞬间详情页面", "/moments"); } @@ -332,9 +335,7 @@ private Mono processBangumiSeoData(ITemplateContext context, IModel model) */ private Mono processPostSeoData(ITemplateContext context, IModel model) { var modelFactory = context.getModelFactory(); - // Halo 路由在上下文中设置 name = post.metadata.name - var postName = Optional.ofNullable(context.getVariable("name")).map(Object::toString) - .filter(name -> !name.isEmpty()).orElse(null); + var postName = getNameVariable(context); if (postName == null) { return Mono.empty(); } @@ -359,8 +360,8 @@ private Mono processPostSeoData(ITemplateContext context, IModel model) { */ private Mono processCategorySeoData(ITemplateContext context, IModel model) { var modelFactory = context.getModelFactory(); - var categoryName = Optional.ofNullable(context.getVariable("name")).map(Object::toString) - .filter(s -> !s.isEmpty()).orElse(null); + // 分类详情页不注入 name 变量,从上下文的 CategoryVo 对象取 metadata.name + var categoryName = getMetadataNameFromVo(context, "category"); if (categoryName == null) { return Mono.empty(); } @@ -385,13 +386,18 @@ private Mono processCategorySeoData(ITemplateContext context, IModel model */ private Mono processTagSeoData(ITemplateContext context, IModel model) { var modelFactory = context.getModelFactory(); - var tagName = Optional.ofNullable(context.getVariable("name")).map(Object::toString) - .filter(s -> !s.isEmpty()).orElse(null); + var tagName = getNameVariable(context); + log.info("processTagSeoData called, tagName={}", tagName); if (tagName == null) { return Mono.empty(); } - return client.fetch(Tag.class, tagName).flatMap(tag -> buildSeoDataForTag(tag).flatMap( - seoData -> generateSeoTags(seoData, model, modelFactory))); + return client.fetch(Tag.class, tagName) + .doOnNext(tag -> log.info("Tag fetched: {}", tag.getSpec().getDisplayName())) + .switchIfEmpty(Mono.defer(() -> { + log.warn("Tag not found via client.fetch: {}", tagName); + return Mono.empty(); + })).flatMap(tag -> buildSeoDataForTag(tag).flatMap( + seoData -> generateSeoTags(seoData, model, modelFactory))); } /** @@ -412,8 +418,7 @@ private Mono processTagSeoData(ITemplateContext context, IModel model) { */ private Mono processSinglePageSeoData(ITemplateContext context, IModel model) { var modelFactory = context.getModelFactory(); - var pageName = Optional.ofNullable(context.getVariable("name")).map(Object::toString) - .filter(s -> !s.isEmpty()).orElse(null); + var pageName = getNameVariable(context); if (pageName == null) { return Mono.empty(); } @@ -438,8 +443,7 @@ private Mono processSinglePageSeoData(ITemplateContext context, IModel mod */ private Mono processAuthorSeoData(ITemplateContext context, IModel model) { var modelFactory = context.getModelFactory(); - var userName = Optional.ofNullable(context.getVariable("name")).map(Object::toString) - .filter(s -> !s.isEmpty()).orElse(null); + var userName = getNameVariable(context); if (userName == null) { return Mono.empty(); } @@ -466,11 +470,11 @@ private Mono processAuthorSeoData(ITemplateContext context, IModel model) * @param context Thymeleaf 模板上下文 * @param model HTML 输出模型 * @param pageTitle 页面语义标题,如 "首页"、"分类"、"标签" - * @param fallbackDesc 页面描述,为空时回退到站点 SEO 描述 + * @param pageDesc 页面描述,为空时回退到站点 SEO 描述 * @param pagePath 页面路径,如 "/"、"/categories"、"/tags" */ private Mono buildListPageSeoData(ITemplateContext context, IModel model, - String pageTitle, String fallbackDesc, String pagePath) { + String pageTitle, String pageDesc, String pagePath) { var modelFactory = context.getModelFactory(); return Mono.zip(settingConfigGetter.getBasicConfig(), systemInfoGetter.get()) @@ -484,11 +488,11 @@ private Mono buildListPageSeoData(ITemplateContext context, IModel model, // 标题格式:pageTitle - siteName var title = hasValue(siteName) ? pageTitle + " - " + siteName : pageTitle; - // 描述优先级:传入描述 > 站点 SEO 描述 > 页面标题 + // 描述优先级:页面描述 > 站点 SEO 描述 > 页面标题 var siteDesc = Optional.ofNullable(systemInfo.getSeo()).map(SystemInfo.SeoProp::getDescription) .orElse(null); - var description = firstNonBlank(fallbackDesc, siteDesc, pageTitle); + var description = firstNonBlank(pageDesc, siteDesc, pageTitle); // 通过 ExternalLinkProcessor 将相对路径转为完整的外部 URL var pageUrl = externalLinkProcessor.processLink(pagePath); @@ -688,7 +692,7 @@ private Mono buildSeoDataForCategory(Category category) { *

字段映射: *

    *
  • 标题 ← {@code tag.spec.displayName} + " - " + 站点名称
  • - *
  • 描述 ← "标签: " + displayName(Tag 无 description 字段)
  • + *
  • 描述 ← {@code tag.spec.description},回退到 "标签: displayName"
  • *
  • 封面 ← {@code tag.spec.cover},回退到默认封面/站点 Logo
  • *
  • URL ← {@code tag.status.permalink}
  • *
  • 关键词 ← 标签显示名
  • @@ -705,8 +709,9 @@ private Mono buildSeoDataForTag(Tag tag) { var displayName = tag.getSpec().getDisplayName(); var siteName = systemInfo.getTitle(); var title = hasValue(siteName) ? displayName + " - " + siteName : displayName; - // Tag 没有 description 字段,使用语义描述 - var description = "标签: " + displayName; + // 描述:优先 Tag 自身的 description,回退到 "标签: displayName"(等待 API 升级 2.23.1) + // var description = + // firstNonBlank(tag.getSpec().getDescription, "标签: " + displayName); // status 字段在 Reconciler 首次处理前为 null,使用 getStatusOrDefault() 安全获取 var pageUrl = processPermalink(tag.getStatusOrDefault().getPermalink()); @@ -716,8 +721,8 @@ private Mono buildSeoDataForTag(Tag tag) { var siteLogo = processPermalink(systemInfo.getLogo()); // 标签没有独立的作者和发布时间;pageType 为 website - return new SeoData(title, description, coverUrl, pageUrl, siteName, null, null, null, - null, siteName, siteLogo, displayName, "website"); + return new SeoData(title, null, coverUrl, pageUrl, siteName, null, null, null, null, + siteName, siteLogo, displayName, "website"); }); } @@ -1105,6 +1110,40 @@ private String formatDateTime(Instant instant, DateTimeFormatter formatter, Zone .orElse(""); } + /** + * 从上下文的 {@code name} 变量获取实体标识。 + * + *

    Post、SinglePage、Tag、Author 的路由会在上下文中注入 {@code name} 变量, + * 值为实体的 metadata.name。 + * + * @return metadata.name,获取失败时返回 null + */ + private String getNameVariable(ITemplateContext context) { + return Optional.ofNullable(context.getVariable("name")).map(Object::toString) + .filter(s -> !s.isEmpty()).orElse(null); + } + + /** + * 从上下文的 Vo 对象中获取 metadata.name。 + * + *

    Category 详情页不在上下文中注入独立的 {@code name} 变量, + * 需要通过 Vo 对象的 {@link ExtensionVoOperator#getMetadata()} 提取。 + * 注意:第三方插件的 Vo(如 MomentVo)在上下文中可能是延迟加载的 Mono 对象, + * 此方法无法处理这种情况,会返回 null。 + * + * @param context Thymeleaf 模板上下文 + * @param voVariableName 上下文中 Vo 对象的变量名,如 "category" + * @return metadata.name,获取失败时返回 null + */ + private String getMetadataNameFromVo(ITemplateContext context, String voVariableName) { + var vo = context.getVariable(voVariableName); + if (vo instanceof ExtensionVoOperator evo) { + return Optional.of(evo.getMetadata()).map(MetadataOperator::getName) + .filter(s -> !s.isEmpty()).orElse(null); + } + return null; + } + /** * 判断字符串是否有实际内容(非 null 且非空白)。 * 用于输出阶段决定是否生成对应的 SEO 标签:无值则跳过,避免输出空标签。 diff --git a/src/main/resources/plugin.yaml b/src/main/resources/plugin.yaml index 3b03fc8..c40c4ce 100644 --- a/src/main/resources/plugin.yaml +++ b/src/main/resources/plugin.yaml @@ -7,7 +7,7 @@ metadata: name: time-factor spec: enabled: true - requires: ">=2.22.10" + requires: ">=2.23.1" author: name: Handsome website: https://www.lik.cc diff --git a/ui/package.json b/ui/package.json index 5512fa3..0b08c84 100644 --- a/ui/package.json +++ b/ui/package.json @@ -18,41 +18,41 @@ "tabWidth": 2 }, "dependencies": { - "@halo-dev/api-client": "^2.22.0", - "@halo-dev/components": "^2.22.0", - "@halo-dev/ui-shared": "^2.22.0", - "axios": "^1.7.2", - "canvas-confetti": "^1.9.3", + "@halo-dev/api-client": "^2.23.0", + "@halo-dev/components": "^2.23.0", + "@halo-dev/ui-shared": "^2.23.0", + "axios": "^1.13.6", + "canvas-confetti": "^1.9.4", "pinia": "^3.0.4", - "vue": "^3.5.17", + "vue": "^3.5.30", "vue-router": "^4.6.4" }, "devDependencies": { - "@rsbuild/core": "^1.3.22", - "@rsbuild/plugin-sass": "^1.3.2", - "@halo-dev/ui-plugin-bundler-kit": "^2.22.0", - "@iconify/json": "^2.2.350", - "@tsconfig/node20": "^20.1.6", + "@halo-dev/ui-plugin-bundler-kit": "^2.23.0", + "@iconify/json": "^2.2.454", + "@rsbuild/core": "^1.7.3", + "@rsbuild/plugin-sass": "^1.5.1", + "@tsconfig/node20": "^20.1.9", "@types/canvas-confetti": "^1.9.0", "@types/jsdom": "^21.1.7", - "@types/node": "^20.19.1", - "@vitest/eslint-plugin": "^1.2.7", + "@types/node": "^20.19.37", + "@vitest/eslint-plugin": "^1.6.13", "@vue/eslint-config-prettier": "^10.2.0", - "@vue/eslint-config-typescript": "^14.5.1", + "@vue/eslint-config-typescript": "^14.7.0", "@vue/test-utils": "^2.4.6", "@vue/tsconfig": "^0.7.0", - "eslint": "^9.29.0", + "eslint": "^9.39.4", "eslint-plugin-oxlint": "^0.16.12", "eslint-plugin-vue": "~10.0.1", "jsdom": "^26.1.0", "npm-run-all2": "^7.0.2", "oxlint": "^0.16.12", - "prettier": "^3.6.0", - "sass": "^1.89.2", + "prettier": "^3.8.1", + "sass": "^1.98.0", "typescript": "~5.8.3", - "unplugin-icons": "^22.1.0", + "unplugin-icons": "^22.5.0", "vitest": "^3.2.4", - "vue-tsc": "^2.2.10" + "vue-tsc": "^2.2.12" }, "packageManager": "pnpm@9.15.9" } diff --git a/ui/pnpm-lock.yaml b/ui/pnpm-lock.yaml index c40b353..82f216c 100644 --- a/ui/pnpm-lock.yaml +++ b/ui/pnpm-lock.yaml @@ -9,45 +9,45 @@ importers: .: dependencies: '@halo-dev/api-client': - specifier: ^2.22.0 - version: 2.22.0(axios@1.10.0) + specifier: ^2.23.0 + version: 2.23.0(axios@1.13.6) '@halo-dev/components': - specifier: ^2.22.0 - version: 2.22.0(vue-router@4.6.4(vue@3.5.17(typescript@5.8.3)))(vue@3.5.17(typescript@5.8.3)) + specifier: ^2.23.0 + version: 2.23.0(vue-router@4.6.4(vue@3.5.30(typescript@5.8.3)))(vue@3.5.30(typescript@5.8.3)) '@halo-dev/ui-shared': - specifier: ^2.22.0 - version: 2.22.0(axios@1.10.0)(vue-router@4.6.4(vue@3.5.17(typescript@5.8.3)))(vue@3.5.17(typescript@5.8.3)) + specifier: ^2.23.0 + version: 2.23.0(@tiptap/extension-collaboration@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5)(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.7)(y-protocols@1.0.7(yjs@13.6.30))(yjs@13.6.30))(yjs@13.6.30))(@tiptap/extension-node-range@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5))(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.7)(y-protocols@1.0.7(yjs@13.6.30))(yjs@13.6.30))(axios@1.13.6)(vue-router@4.6.4(vue@3.5.30(typescript@5.8.3)))(vue@3.5.30(typescript@5.8.3)) axios: - specifier: ^1.7.2 - version: 1.10.0 + specifier: ^1.13.6 + version: 1.13.6 canvas-confetti: - specifier: ^1.9.3 - version: 1.9.3 + specifier: ^1.9.4 + version: 1.9.4 pinia: specifier: ^3.0.4 - version: 3.0.4(typescript@5.8.3)(vue@3.5.17(typescript@5.8.3)) + version: 3.0.4(typescript@5.8.3)(vue@3.5.30(typescript@5.8.3)) vue: - specifier: ^3.5.17 - version: 3.5.17(typescript@5.8.3) + specifier: ^3.5.30 + version: 3.5.30(typescript@5.8.3) vue-router: specifier: ^4.6.4 - version: 4.6.4(vue@3.5.17(typescript@5.8.3)) + version: 4.6.4(vue@3.5.30(typescript@5.8.3)) devDependencies: '@halo-dev/ui-plugin-bundler-kit': - specifier: ^2.22.0 - version: 2.22.0(@rsbuild/core@1.4.0)(@rsbuild/plugin-vue@1.0.7(@rsbuild/core@1.4.0)(@vue/compiler-sfc@3.5.17)(vue@3.5.17(typescript@5.8.3)))(@vitejs/plugin-vue@5.2.4(vite@7.0.0(@types/node@20.19.1)(jiti@2.4.2)(sass-embedded@1.89.0)(sass@1.89.2)(terser@5.43.1))(vue@3.5.17(typescript@5.8.3)))(axios@1.10.0)(vite@7.0.0(@types/node@20.19.1)(jiti@2.4.2)(sass-embedded@1.89.0)(sass@1.89.2)(terser@5.43.1)) + specifier: ^2.23.0 + version: 2.23.0(@rsbuild/core@1.7.3)(@rsbuild/plugin-vue@1.0.7(@rsbuild/core@1.7.3)(@vue/compiler-sfc@3.5.30)(vue@3.5.30(typescript@5.8.3)))(@vitejs/plugin-vue@5.2.4(vite@7.3.1(@types/node@20.19.37)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.1))(vue@3.5.30(typescript@5.8.3)))(axios@1.13.6)(vite@7.3.1(@types/node@20.19.37)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.1)) '@iconify/json': - specifier: ^2.2.350 - version: 2.2.353 + specifier: ^2.2.454 + version: 2.2.454 '@rsbuild/core': - specifier: ^1.3.22 - version: 1.4.0 + specifier: ^1.7.3 + version: 1.7.3 '@rsbuild/plugin-sass': - specifier: ^1.3.2 - version: 1.3.2(@rsbuild/core@1.4.0) + specifier: ^1.5.1 + version: 1.5.1(@rsbuild/core@1.7.3) '@tsconfig/node20': - specifier: ^20.1.6 - version: 20.1.6 + specifier: ^20.1.9 + version: 20.1.9 '@types/canvas-confetti': specifier: ^1.9.0 version: 1.9.0 @@ -55,32 +55,32 @@ importers: specifier: ^21.1.7 version: 21.1.7 '@types/node': - specifier: ^20.19.1 - version: 20.19.1 + specifier: ^20.19.37 + version: 20.19.37 '@vitest/eslint-plugin': - specifier: ^1.2.7 - version: 1.3.3(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4(@types/node@20.19.1)(jiti@2.4.2)(jsdom@26.1.0)(sass-embedded@1.89.0)(sass@1.89.2)(terser@5.43.1)) + specifier: ^1.6.13 + version: 1.6.13(@typescript-eslint/eslint-plugin@8.57.2(@typescript-eslint/parser@8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3)(vitest@3.2.4(@types/node@20.19.37)(jiti@2.6.1)(jsdom@26.1.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.1)) '@vue/eslint-config-prettier': specifier: ^10.2.0 - version: 10.2.0(@types/eslint@9.6.1)(eslint@9.29.0(jiti@2.4.2))(prettier@3.6.2) + version: 10.2.0(@types/eslint@9.6.1)(eslint@9.39.4(jiti@2.6.1))(prettier@3.8.1) '@vue/eslint-config-typescript': - specifier: ^14.5.1 - version: 14.5.1(eslint-plugin-vue@10.0.1(eslint@9.29.0(jiti@2.4.2))(vue-eslint-parser@10.1.4(eslint@9.29.0(jiti@2.4.2))))(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + specifier: ^14.7.0 + version: 14.7.0(eslint-plugin-vue@10.0.1(eslint@9.39.4(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@9.39.4(jiti@2.6.1))))(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3) '@vue/test-utils': specifier: ^2.4.6 version: 2.4.6 '@vue/tsconfig': specifier: ^0.7.0 - version: 0.7.0(typescript@5.8.3)(vue@3.5.17(typescript@5.8.3)) + version: 0.7.0(typescript@5.8.3)(vue@3.5.30(typescript@5.8.3)) eslint: - specifier: ^9.29.0 - version: 9.29.0(jiti@2.4.2) + specifier: ^9.39.4 + version: 9.39.4(jiti@2.6.1) eslint-plugin-oxlint: specifier: ^0.16.12 version: 0.16.12 eslint-plugin-vue: specifier: ~10.0.1 - version: 10.0.1(eslint@9.29.0(jiti@2.4.2))(vue-eslint-parser@10.1.4(eslint@9.29.0(jiti@2.4.2))) + version: 10.0.1(eslint@9.39.4(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@9.39.4(jiti@2.6.1))) jsdom: specifier: ^26.1.0 version: 26.1.0 @@ -91,32 +91,29 @@ importers: specifier: ^0.16.12 version: 0.16.12 prettier: - specifier: ^3.6.0 - version: 3.6.2 + specifier: ^3.8.1 + version: 3.8.1 sass: - specifier: ^1.89.2 - version: 1.89.2 + specifier: ^1.98.0 + version: 1.98.0 typescript: specifier: ~5.8.3 version: 5.8.3 unplugin-icons: - specifier: ^22.1.0 - version: 22.1.0(@vue/compiler-sfc@3.5.17) + specifier: ^22.5.0 + version: 22.5.0(@vue/compiler-sfc@3.5.30) vitest: specifier: ^3.2.4 - version: 3.2.4(@types/node@20.19.1)(jiti@2.4.2)(jsdom@26.1.0)(sass-embedded@1.89.0)(sass@1.89.2)(terser@5.43.1) + version: 3.2.4(@types/node@20.19.37)(jiti@2.6.1)(jsdom@26.1.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.1) vue-tsc: - specifier: ^2.2.10 - version: 2.2.10(typescript@5.8.3) + specifier: ^2.2.12 + version: 2.2.12(typescript@5.8.3) packages: '@antfu/install-pkg@1.1.0': resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} - '@antfu/utils@8.1.1': - resolution: {integrity: sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ==} - '@asamuzakjp/css-color@3.2.0': resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} @@ -124,24 +121,24 @@ packages: resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.27.1': - resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} - '@babel/parser@7.27.7': - resolution: {integrity: sha512-qnzXzDXdr/po3bOTbTIQZ7+TxNKxpkN5IifVLXS+r7qwynkZfPyjZfE7hCXbo7IoO9TNcSyibgONsf2HauUd3Q==} + '@babel/parser@7.29.2': + resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/types@7.27.7': - resolution: {integrity: sha512-8OLQgDScAOHXnAz2cV+RfzzNMipuLVBz2biuAJFMV9bfkNf393je3VM8CLkjQodW5+iWsSJdSgSWT6rsZoXHPw==} + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} - '@bufbuild/protobuf@2.5.2': - resolution: {integrity: sha512-foZ7qr0IsUBjzWIq+SuBLfdQCpJ1j8cTuNNT4owngTHoN5KsJb8L9t65fzz7SCeSWzescoOil/0ldqiL041ABg==} + '@bufbuild/protobuf@2.11.0': + resolution: {integrity: sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==} - '@csstools/color-helpers@5.0.2': - resolution: {integrity: sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==} + '@csstools/color-helpers@5.1.0': + resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} engines: {node: '>=18'} '@csstools/css-calc@2.1.4': @@ -151,8 +148,8 @@ packages: '@csstools/css-parser-algorithms': ^3.0.5 '@csstools/css-tokenizer': ^3.0.4 - '@csstools/css-color-parser@3.0.10': - resolution: {integrity: sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==} + '@csstools/css-color-parser@3.1.0': + resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==} engines: {node: '>=18'} peerDependencies: '@csstools/css-parser-algorithms': ^3.0.5 @@ -168,327 +165,331 @@ packages: resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} engines: {node: '>=18'} - '@emnapi/core@1.4.3': - resolution: {integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==} + '@emnapi/core@1.9.1': + resolution: {integrity: sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==} - '@emnapi/runtime@1.4.3': - resolution: {integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==} + '@emnapi/runtime@1.9.1': + resolution: {integrity: sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==} - '@emnapi/wasi-threads@1.0.2': - resolution: {integrity: sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==} + '@emnapi/wasi-threads@1.2.0': + resolution: {integrity: sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==} - '@esbuild/aix-ppc64@0.25.5': - resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==} + '@esbuild/aix-ppc64@0.27.4': + resolution: {integrity: sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.25.5': - resolution: {integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==} + '@esbuild/android-arm64@0.27.4': + resolution: {integrity: sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.25.5': - resolution: {integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==} + '@esbuild/android-arm@0.27.4': + resolution: {integrity: sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.25.5': - resolution: {integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==} + '@esbuild/android-x64@0.27.4': + resolution: {integrity: sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.25.5': - resolution: {integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==} + '@esbuild/darwin-arm64@0.27.4': + resolution: {integrity: sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.25.5': - resolution: {integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==} + '@esbuild/darwin-x64@0.27.4': + resolution: {integrity: sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.25.5': - resolution: {integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==} + '@esbuild/freebsd-arm64@0.27.4': + resolution: {integrity: sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.5': - resolution: {integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==} + '@esbuild/freebsd-x64@0.27.4': + resolution: {integrity: sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.25.5': - resolution: {integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==} + '@esbuild/linux-arm64@0.27.4': + resolution: {integrity: sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.25.5': - resolution: {integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==} + '@esbuild/linux-arm@0.27.4': + resolution: {integrity: sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.25.5': - resolution: {integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==} + '@esbuild/linux-ia32@0.27.4': + resolution: {integrity: sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.25.5': - resolution: {integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==} + '@esbuild/linux-loong64@0.27.4': + resolution: {integrity: sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.25.5': - resolution: {integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==} + '@esbuild/linux-mips64el@0.27.4': + resolution: {integrity: sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.25.5': - resolution: {integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==} + '@esbuild/linux-ppc64@0.27.4': + resolution: {integrity: sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.25.5': - resolution: {integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==} + '@esbuild/linux-riscv64@0.27.4': + resolution: {integrity: sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.25.5': - resolution: {integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==} + '@esbuild/linux-s390x@0.27.4': + resolution: {integrity: sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.25.5': - resolution: {integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==} + '@esbuild/linux-x64@0.27.4': + resolution: {integrity: sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.25.5': - resolution: {integrity: sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==} + '@esbuild/netbsd-arm64@0.27.4': + resolution: {integrity: sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.5': - resolution: {integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==} + '@esbuild/netbsd-x64@0.27.4': + resolution: {integrity: sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.25.5': - resolution: {integrity: sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==} + '@esbuild/openbsd-arm64@0.27.4': + resolution: {integrity: sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.5': - resolution: {integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==} + '@esbuild/openbsd-x64@0.27.4': + resolution: {integrity: sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/sunos-x64@0.25.5': - resolution: {integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==} + '@esbuild/openharmony-arm64@0.27.4': + resolution: {integrity: sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.4': + resolution: {integrity: sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.25.5': - resolution: {integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==} + '@esbuild/win32-arm64@0.27.4': + resolution: {integrity: sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.25.5': - resolution: {integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==} + '@esbuild/win32-ia32@0.27.4': + resolution: {integrity: sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.25.5': - resolution: {integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==} + '@esbuild/win32-x64@0.27.4': + resolution: {integrity: sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==} engines: {node: '>=18'} cpu: [x64] os: [win32] - '@eslint-community/eslint-utils@4.7.0': - resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - '@eslint-community/regexpp@4.12.1': - resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/config-array@0.20.1': - resolution: {integrity: sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==} + '@eslint/config-array@0.21.2': + resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/config-helpers@0.2.3': - resolution: {integrity: sha512-u180qk2Um1le4yf0ruXH3PYFeEZeYC3p/4wCTKrr2U1CmGdzGi3KtY0nuPDH48UJxlKCC5RDzbcbh4X0XlqgHg==} + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/core@0.14.0': - resolution: {integrity: sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==} + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/core@0.15.1': - resolution: {integrity: sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==} + '@eslint/eslintrc@3.3.5': + resolution: {integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/eslintrc@3.3.1': - resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + '@eslint/js@9.39.4': + resolution: {integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.29.0': - resolution: {integrity: sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==} + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/object-schema@2.1.6': - resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/plugin-kit@0.3.3': - resolution: {integrity: sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@floating-ui/core@1.7.1': - resolution: {integrity: sha512-azI0DrjMMfIug/ExbBaeDVJXcY0a7EPvPjb2xAJPa4HeimBX+Z18HK8QQR3jb6356SnDDdxx+hinMLcJEDdOjw==} + '@floating-ui/core@1.7.5': + resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} '@floating-ui/dom@1.1.1': resolution: {integrity: sha512-TpIO93+DIujg3g7SykEAGZMDtbJRrmnYRCNYSjJlvIbGhBjRSNTLVbNeDQBrzy9qDgUbiWdc7KA0uZHZ2tJmiw==} - '@floating-ui/utils@0.2.9': - resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==} + '@floating-ui/dom@1.7.6': + resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} + + '@floating-ui/utils@0.2.11': + resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} - '@halo-dev/api-client@2.22.0': - resolution: {integrity: sha512-gJKJWQxG2nzcANrRddED9P0pefmdanWm0+kV3cgGrtqnF6ULnckB4KIKUPmMOwdCBg1QEPQ30Txc9AfYwlu4kQ==} + '@halo-dev/api-client@2.23.0': + resolution: {integrity: sha512-uws5E1RkiSSj23XwyTLdmpDWt1QCPfeiAPVetrOlGt/bkSWhPDSZucvZQ3/EW4r1PzXy5SV/lynULyBDc2Dc1w==} peerDependencies: axios: ^1.12.* - '@halo-dev/components@2.22.0': - resolution: {integrity: sha512-IwFmrqnlfeTZwpEhT+w20rljK0kUqIKxWNW1pFOAfcCC3TN3KbybatGX3Mv81wNWCkaJ9cvu5tSkAJO8UJSNRg==} + '@halo-dev/components@2.23.0': + resolution: {integrity: sha512-THsQ3DdklEtspd/hBCTiYyJ0rn3chJPzlBPgKpzkdhzbb5efiMI7ovKSCNakmJTbU17PtCOnB9p8/o7hLdGPcA==} peerDependencies: vue: ^3.5.x - vue-router: ^4.6.x + vue-router: ^5.0.x - '@halo-dev/ui-plugin-bundler-kit@2.22.0': - resolution: {integrity: sha512-3R6AJGA10owZ1j2WxqyPvXbg8JBuQCVMqM/DBZzvXO/2pSLHFqnCi2n7qW66kN0B621PmHr+q3hGSAsBIS4Oeg==} + '@halo-dev/richtext-editor@2.23.0': + resolution: {integrity: sha512-MbAUiG9pnRwq6kc0hz1TrxcoefaPfVRwAveFwcCnvLrqS/e/koOWRxqt1233PtvP771ASd2uvYhRW/oSEMBp4g==} + peerDependencies: + vue: ^3.5.x + + '@halo-dev/ui-plugin-bundler-kit@2.23.0': + resolution: {integrity: sha512-R3IqFhwP6vrjrVJQMbNNBse+A9FtMye1cfJdo/o5Ia6A7k7Vo0lbwogmG81pH6jqQd91B7XIqbarQv83GMrjtw==} engines: {node: ^18.0.0 || >=20.0.0} peerDependencies: - '@rsbuild/core': ^1.0.0 - '@rsbuild/plugin-vue': ^1.0.0 + '@rsbuild/core': ^1.0.0 || ^2.0.0 + '@rsbuild/plugin-vue': ^1.0.0 || ^2.0.0 '@vitejs/plugin-vue': ^5.0.0 || ^6.0.0 - vite: ^5.0.0 || ^6.0.0 || ^7.0.0 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 - '@halo-dev/ui-shared@2.22.0': - resolution: {integrity: sha512-l3/lLtnshucsurKCTrJ71l1uDRt/oaBnuuAg8973tOHVCXzFXUDaOcGsaXUalEA/wHGkyc5oCZ7w6uVzDq/8nw==} + '@halo-dev/ui-shared@2.23.0': + resolution: {integrity: sha512-BPr9y9pE7vJm62s2e7Ql63KG6fELVu1OEPD/qM18yrSKgII8Vl+/t/3jgj4RKiel2N9HPyjbeenKc6CVGnRdtQ==} peerDependencies: vue: ^3.5.x - vue-router: ^4.6.x + vue-router: ^5.0.x '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} - '@humanfs/node@0.16.6': - resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} engines: {node: '>=18.18.0'} '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} - '@humanwhocodes/retry@0.3.1': - resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} - engines: {node: '>=18.18'} - '@humanwhocodes/retry@0.4.3': resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} - '@iconify/json@2.2.353': - resolution: {integrity: sha512-X9vslZVyHn3VT9KnFthpQZMzClvCK6IwAO6CC8h3B/4McNY9AlBeN0f6S+UehIH8B/0dtYejxcoIWNo6HrGc+Q==} + '@iconify/json@2.2.454': + resolution: {integrity: sha512-U1c0+LBtcF4YDQGOVZpoA4w96hBcIVHFjBcg60pFdnF+CApRjnlIp27Jf1g7BaZlJ+vYcExyc/1EjUyZrY8MFQ==} '@iconify/types@2.0.0': resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} - '@iconify/utils@2.3.0': - resolution: {integrity: sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==} + '@iconify/utils@3.1.0': + resolution: {integrity: sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==} '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} - '@jridgewell/gen-mapping@0.3.8': - resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} - engines: {node: '>=6.0.0'} + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} - '@jridgewell/set-array@1.2.1': - resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} - engines: {node: '>=6.0.0'} - - '@jridgewell/source-map@0.3.6': - resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} + '@jridgewell/source-map@0.3.11': + resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} - '@jridgewell/sourcemap-codec@1.5.0': - resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - '@jridgewell/trace-mapping@0.3.25': - resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@module-federation/error-codes@0.15.0': - resolution: {integrity: sha512-CFJSF+XKwTcy0PFZ2l/fSUpR4z247+Uwzp1sXVkdIfJ/ATsnqf0Q01f51qqSEA6MYdQi6FKos9FIcu3dCpQNdg==} + '@module-federation/error-codes@0.22.0': + resolution: {integrity: sha512-xF9SjnEy7vTdx+xekjPCV5cIHOGCkdn3pIxo9vU7gEZMIw0SvAEdsy6Uh17xaCpm8V0FWvR0SZoK9Ik6jGOaug==} - '@module-federation/runtime-core@0.15.0': - resolution: {integrity: sha512-RYzI61fRDrhyhaEOXH3AgIGlHiot0wPFXu7F43cr+ZnTi+VlSYWLdlZ4NBuT9uV6JSmH54/c+tEZm5SXgKR2sQ==} + '@module-federation/runtime-core@0.22.0': + resolution: {integrity: sha512-GR1TcD6/s7zqItfhC87zAp30PqzvceoeDGYTgF3Vx2TXvsfDrhP6Qw9T4vudDQL3uJRne6t7CzdT29YyVxlgIA==} - '@module-federation/runtime-tools@0.15.0': - resolution: {integrity: sha512-kzFn3ObUeBp5vaEtN1WMxhTYBuYEErxugu1RzFUERD21X3BZ+b4cWwdFJuBDlsmVjctIg/QSOoZoPXRKAO0foA==} + '@module-federation/runtime-tools@0.22.0': + resolution: {integrity: sha512-4ScUJ/aUfEernb+4PbLdhM/c60VHl698Gn1gY21m9vyC1Ucn69fPCA1y2EwcCB7IItseRMoNhdcWQnzt/OPCNA==} - '@module-federation/runtime@0.15.0': - resolution: {integrity: sha512-dTPsCNum9Bhu3yPOcrPYq0YnM9eCMMMNB1wuiqf1+sFbQlNApF0vfZxooqz3ln0/MpgE0jerVvFsLVGfqvC9Ug==} + '@module-federation/runtime@0.22.0': + resolution: {integrity: sha512-38g5iPju2tPC3KHMPxRKmy4k4onNp6ypFPS1eKGsNLUkXgHsPMBFqAjDw96iEcjri91BrahG4XcdyKi97xZzlA==} - '@module-federation/sdk@0.15.0': - resolution: {integrity: sha512-PWiYbGcJrKUD6JZiEPihrXhV3bgXdll4bV7rU+opV7tHaun+Z0CdcawjZ82Xnpb8MCPGmqHwa1MPFeUs66zksw==} + '@module-federation/sdk@0.22.0': + resolution: {integrity: sha512-x4aFNBKn2KVQRuNVC5A7SnrSCSqyfIWmm1DvubjbO9iKFe7ith5niw8dqSFBekYBg2Fwy+eMg4sEFNVvCAdo6g==} - '@module-federation/webpack-bundler-runtime@0.15.0': - resolution: {integrity: sha512-i+3wu2Ljh2TmuUpsnjwZVupOVqV50jP0ndA8PSP4gwMKlgdGeaZ4VH5KkHAXGr2eiYUxYLMrJXz1+eILJqeGDg==} + '@module-federation/webpack-bundler-runtime@0.22.0': + resolution: {integrity: sha512-aM8gCqXu+/4wBmJtVeMeeMN5guw3chf+2i6HajKtQv7SJfxV/f4IyNQJUeUQu9HfiAZHjqtMV5Lvq/Lvh8LdyA==} - '@napi-rs/wasm-runtime@0.2.11': - resolution: {integrity: sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==} + '@napi-rs/wasm-runtime@1.0.7': + resolution: {integrity: sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==} '@nodelib/fs.scandir@2.1.5': - resolution: {integrity: sha1-dhnC6yGyVIP20WdUi0z9WnSIw9U=} + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} '@nodelib/fs.stat@2.0.5': - resolution: {integrity: sha1-W9Jir5Tp0lvR5xsF3u1Eh2oiLos=} + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} engines: {node: '>= 8'} '@nodelib/fs.walk@1.2.8': - resolution: {integrity: sha1-6Vc36LtnRt3t9pxVaVNJTxlv5po=} + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} '@one-ini/wasm@0.1.1': @@ -538,311 +539,554 @@ packages: cpu: [x64] os: [win32] - '@parcel/watcher-android-arm64@2.5.1': - resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} + '@parcel/watcher-android-arm64@2.5.6': + resolution: {integrity: sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [android] - '@parcel/watcher-darwin-arm64@2.5.1': - resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==} + '@parcel/watcher-darwin-arm64@2.5.6': + resolution: {integrity: sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [darwin] - '@parcel/watcher-darwin-x64@2.5.1': - resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==} + '@parcel/watcher-darwin-x64@2.5.6': + resolution: {integrity: sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [darwin] - '@parcel/watcher-freebsd-x64@2.5.1': - resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==} + '@parcel/watcher-freebsd-x64@2.5.6': + resolution: {integrity: sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [freebsd] - '@parcel/watcher-linux-arm-glibc@2.5.1': - resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==} + '@parcel/watcher-linux-arm-glibc@2.5.6': + resolution: {integrity: sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] libc: [glibc] - '@parcel/watcher-linux-arm-musl@2.5.1': - resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} + '@parcel/watcher-linux-arm-musl@2.5.6': + resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] libc: [musl] - '@parcel/watcher-linux-arm64-glibc@2.5.1': - resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} + '@parcel/watcher-linux-arm64-glibc@2.5.6': + resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] libc: [glibc] - '@parcel/watcher-linux-arm64-musl@2.5.1': - resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} + '@parcel/watcher-linux-arm64-musl@2.5.6': + resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] libc: [musl] - '@parcel/watcher-linux-x64-glibc@2.5.1': - resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} + '@parcel/watcher-linux-x64-glibc@2.5.6': + resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] libc: [glibc] - '@parcel/watcher-linux-x64-musl@2.5.1': - resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} + '@parcel/watcher-linux-x64-musl@2.5.6': + resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] libc: [musl] - '@parcel/watcher-win32-arm64@2.5.1': - resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} + '@parcel/watcher-win32-arm64@2.5.6': + resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [win32] - '@parcel/watcher-win32-ia32@2.5.1': - resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==} + '@parcel/watcher-win32-ia32@2.5.6': + resolution: {integrity: sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==} engines: {node: '>= 10.0.0'} cpu: [ia32] os: [win32] - '@parcel/watcher-win32-x64@2.5.1': - resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==} + '@parcel/watcher-win32-x64@2.5.6': + resolution: {integrity: sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [win32] - '@parcel/watcher@2.5.1': - resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==} + '@parcel/watcher@2.5.6': + resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==} engines: {node: '>= 10.0.0'} '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@pkgr/core@0.2.7': - resolution: {integrity: sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==} + '@pkgr/core@0.2.9': + resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - '@rollup/rollup-android-arm-eabi@4.44.1': - resolution: {integrity: sha512-JAcBr1+fgqx20m7Fwe1DxPUl/hPkee6jA6Pl7n1v2EFiktAHenTaXl5aIFjUIEsfn9w3HE4gK1lEgNGMzBDs1w==} + '@remirror/core-constants@3.0.0': + resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==} + + '@rollup/rollup-android-arm-eabi@4.60.0': + resolution: {integrity: sha512-WOhNW9K8bR3kf4zLxbfg6Pxu2ybOUbB2AjMDHSQx86LIF4rH4Ft7vmMwNt0loO0eonglSNy4cpD3MKXXKQu0/A==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.44.1': - resolution: {integrity: sha512-RurZetXqTu4p+G0ChbnkwBuAtwAbIwJkycw1n6GvlGlBuS4u5qlr5opix8cBAYFJgaY05TWtM+LaoFggUmbZEQ==} + '@rollup/rollup-android-arm64@4.60.0': + resolution: {integrity: sha512-u6JHLll5QKRvjciE78bQXDmqRqNs5M/3GVqZeMwvmjaNODJih/WIrJlFVEihvV0MiYFmd+ZyPr9wxOVbPAG2Iw==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.44.1': - resolution: {integrity: sha512-fM/xPesi7g2M7chk37LOnmnSTHLG/v2ggWqKj3CCA1rMA4mm5KVBT1fNoswbo1JhPuNNZrVwpTvlCVggv8A2zg==} + '@rollup/rollup-darwin-arm64@4.60.0': + resolution: {integrity: sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.44.1': - resolution: {integrity: sha512-gDnWk57urJrkrHQ2WVx9TSVTH7lSlU7E3AFqiko+bgjlh78aJ88/3nycMax52VIVjIm3ObXnDL2H00e/xzoipw==} + '@rollup/rollup-darwin-x64@4.60.0': + resolution: {integrity: sha512-WADYozJ4QCnXCH4wPB+3FuGmDPoFseVCUrANmA5LWwGmC6FL14BWC7pcq+FstOZv3baGX65tZ378uT6WG8ynTw==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.44.1': - resolution: {integrity: sha512-wnFQmJ/zPThM5zEGcnDcCJeYJgtSLjh1d//WuHzhf6zT3Md1BvvhJnWoy+HECKu2bMxaIcfWiu3bJgx6z4g2XA==} + '@rollup/rollup-freebsd-arm64@4.60.0': + resolution: {integrity: sha512-6b8wGHJlDrGeSE3aH5mGNHBjA0TTkxdoNHik5EkvPHCt351XnigA4pS7Wsj/Eo9Y8RBU6f35cjN9SYmCFBtzxw==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.44.1': - resolution: {integrity: sha512-uBmIxoJ4493YATvU2c0upGz87f99e3wop7TJgOA/bXMFd2SvKCI7xkxY/5k50bv7J6dw1SXT4MQBQSLn8Bb/Uw==} + '@rollup/rollup-freebsd-x64@4.60.0': + resolution: {integrity: sha512-h25Ga0t4jaylMB8M/JKAyrvvfxGRjnPQIR8lnCayyzEjEOx2EJIlIiMbhpWxDRKGKF8jbNH01NnN663dH638mA==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.44.1': - resolution: {integrity: sha512-n0edDmSHlXFhrlmTK7XBuwKlG5MbS7yleS1cQ9nn4kIeW+dJH+ExqNgQ0RrFRew8Y+0V/x6C5IjsHrJmiHtkxQ==} + '@rollup/rollup-linux-arm-gnueabihf@4.60.0': + resolution: {integrity: sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==} cpu: [arm] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm-musleabihf@4.44.1': - resolution: {integrity: sha512-8WVUPy3FtAsKSpyk21kV52HCxB+me6YkbkFHATzC2Yd3yuqHwy2lbFL4alJOLXKljoRw08Zk8/xEj89cLQ/4Nw==} + '@rollup/rollup-linux-arm-musleabihf@4.60.0': + resolution: {integrity: sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==} cpu: [arm] os: [linux] libc: [musl] - '@rollup/rollup-linux-arm64-gnu@4.44.1': - resolution: {integrity: sha512-yuktAOaeOgorWDeFJggjuCkMGeITfqvPgkIXhDqsfKX8J3jGyxdDZgBV/2kj/2DyPaLiX6bPdjJDTu9RB8lUPQ==} + '@rollup/rollup-linux-arm64-gnu@4.60.0': + resolution: {integrity: sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==} cpu: [arm64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm64-musl@4.44.1': - resolution: {integrity: sha512-W+GBM4ifET1Plw8pdVaecwUgxmiH23CfAUj32u8knq0JPFyK4weRy6H7ooxYFD19YxBulL0Ktsflg5XS7+7u9g==} + '@rollup/rollup-linux-arm64-musl@4.60.0': + resolution: {integrity: sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==} cpu: [arm64] os: [linux] libc: [musl] - '@rollup/rollup-linux-loongarch64-gnu@4.44.1': - resolution: {integrity: sha512-1zqnUEMWp9WrGVuVak6jWTl4fEtrVKfZY7CvcBmUUpxAJ7WcSowPSAWIKa/0o5mBL/Ij50SIf9tuirGx63Ovew==} + '@rollup/rollup-linux-loong64-gnu@4.60.0': + resolution: {integrity: sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==} cpu: [loong64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-powerpc64le-gnu@4.44.1': - resolution: {integrity: sha512-Rl3JKaRu0LHIx7ExBAAnf0JcOQetQffaw34T8vLlg9b1IhzcBgaIdnvEbbsZq9uZp3uAH+JkHd20Nwn0h9zPjA==} + '@rollup/rollup-linux-loong64-musl@4.60.0': + resolution: {integrity: sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.60.0': + resolution: {integrity: sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==} cpu: [ppc64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-riscv64-gnu@4.44.1': - resolution: {integrity: sha512-j5akelU3snyL6K3N/iX7otLBIl347fGwmd95U5gS/7z6T4ftK288jKq3A5lcFKcx7wwzb5rgNvAg3ZbV4BqUSw==} + '@rollup/rollup-linux-ppc64-musl@4.60.0': + resolution: {integrity: sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.60.0': + resolution: {integrity: sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==} cpu: [riscv64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-riscv64-musl@4.44.1': - resolution: {integrity: sha512-ppn5llVGgrZw7yxbIm8TTvtj1EoPgYUAbfw0uDjIOzzoqlZlZrLJ/KuiE7uf5EpTpCTrNt1EdtzF0naMm0wGYg==} + '@rollup/rollup-linux-riscv64-musl@4.60.0': + resolution: {integrity: sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==} cpu: [riscv64] os: [linux] libc: [musl] - '@rollup/rollup-linux-s390x-gnu@4.44.1': - resolution: {integrity: sha512-Hu6hEdix0oxtUma99jSP7xbvjkUM/ycke/AQQ4EC5g7jNRLLIwjcNwaUy95ZKBJJwg1ZowsclNnjYqzN4zwkAw==} + '@rollup/rollup-linux-s390x-gnu@4.60.0': + resolution: {integrity: sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==} cpu: [s390x] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.44.1': - resolution: {integrity: sha512-EtnsrmZGomz9WxK1bR5079zee3+7a+AdFlghyd6VbAjgRJDbTANJ9dcPIPAi76uG05micpEL+gPGmAKYTschQw==} + '@rollup/rollup-linux-x64-gnu@4.60.0': + resolution: {integrity: sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==} cpu: [x64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-musl@4.44.1': - resolution: {integrity: sha512-iAS4p+J1az6Usn0f8xhgL4PaU878KEtutP4hqw52I4IO6AGoyOkHCxcc4bqufv1tQLdDWFx8lR9YlwxKuv3/3g==} + '@rollup/rollup-linux-x64-musl@4.60.0': + resolution: {integrity: sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==} cpu: [x64] os: [linux] libc: [musl] - '@rollup/rollup-win32-arm64-msvc@4.44.1': - resolution: {integrity: sha512-NtSJVKcXwcqozOl+FwI41OH3OApDyLk3kqTJgx8+gp6On9ZEt5mYhIsKNPGuaZr3p9T6NWPKGU/03Vw4CNU9qg==} + '@rollup/rollup-openbsd-x64@4.60.0': + resolution: {integrity: sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.60.0': + resolution: {integrity: sha512-pESDkos/PDzYwtyzB5p/UoNU/8fJo68vcXM9ZW2V0kjYayj1KaaUfi1NmTUTUpMn4UhU4gTuK8gIaFO4UGuMbA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.60.0': + resolution: {integrity: sha512-hj1wFStD7B1YBeYmvY+lWXZ7ey73YGPcViMShYikqKT1GtstIKQAtfUI6yrzPjAy/O7pO0VLXGmUVWXQMaYgTQ==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.44.1': - resolution: {integrity: sha512-JYA3qvCOLXSsnTR3oiyGws1Dm0YTuxAAeaYGVlGpUsHqloPcFjPg+X0Fj2qODGLNwQOAcCiQmHub/V007kiH5A==} + '@rollup/rollup-win32-ia32-msvc@4.60.0': + resolution: {integrity: sha512-SyaIPFoxmUPlNDq5EHkTbiKzmSEmq/gOYFI/3HHJ8iS/v1mbugVa7dXUzcJGQfoytp9DJFLhHH4U3/eTy2Bq4w==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.44.1': - resolution: {integrity: sha512-J8o22LuF0kTe7m+8PvW9wk3/bRq5+mRo5Dqo6+vXb7otCm3TPhYOJqOaQtGU9YMWQSL3krMnoOxMr0+9E6F3Ug==} + '@rollup/rollup-win32-x64-gnu@4.60.0': + resolution: {integrity: sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA==} cpu: [x64] os: [win32] - '@rsbuild/core@1.4.0': - resolution: {integrity: sha512-uLqcdO1v1qJw5UdiE34YrbdAFP/XtdetIB/JXWdGXZN40Yw/5yDU8PHKOb1i1L0hWhg21QPGXtvmLMXd7J7tRA==} - engines: {node: '>=16.10.0'} + '@rollup/rollup-win32-x64-msvc@4.60.0': + resolution: {integrity: sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w==} + cpu: [x64] + os: [win32] + + '@rsbuild/core@1.7.3': + resolution: {integrity: sha512-kI1oQvCXbQYxUvQPnDLdjSX4gFsbrFNpuUj6jXEJ7IcJ74Q+n4oeFj74/8tKerhxhe0L90m/ZQfzLeN5ORGA9w==} + engines: {node: '>=18.12.0'} hasBin: true - '@rsbuild/plugin-sass@1.3.2': - resolution: {integrity: sha512-askbmJllDZ7LYchT8AqdKt2zKNyBauq2KgA9peBExqjTIYGP+ZXA3UB4V8zGXoACqqAYl/jqf8LUjx6nRWHFSg==} + '@rsbuild/plugin-sass@1.5.1': + resolution: {integrity: sha512-rwou4KvDerqvAI0hstjlwF9jgzxPaSOr0PkwtEPjTnQcR5mK6AXZICpUBJbHGgjlAM0aTegKf5IYl/ENvaAQrQ==} peerDependencies: - '@rsbuild/core': 1.x + '@rsbuild/core': ^1.3.0 || ^2.0.0-0 + peerDependenciesMeta: + '@rsbuild/core': + optional: true '@rsbuild/plugin-vue@1.0.7': resolution: {integrity: sha512-VIXFIU2gcpRjDxZNR9QUZjFqWu3oPZ5a5AUe8cyv2moJBQzRPJ18VtPnIIcdlQB36Q8lm2Do0SSVqkXAuui65g==} peerDependencies: '@rsbuild/core': 1.x - '@rspack/binding-darwin-arm64@1.4.0': - resolution: {integrity: sha512-fOvbe71PAu9mM4f9KYssielLlTaQ0RcaS6fARkOuQRS3HfmZnAcMJA9kDkMAWBdsHQSW2RPHLji0Ng7lumAFvg==} + '@rspack/binding-darwin-arm64@1.7.10': + resolution: {integrity: sha512-bsXi7I6TpH+a4L6okIUh1JDvwT+XcK/L7Yvhu5G2t5YYyd2fl5vMM5O9cePRpEb0RdqJZ3Z8i9WIWHap9aQ8Gw==} cpu: [arm64] os: [darwin] - '@rspack/binding-darwin-x64@1.4.0': - resolution: {integrity: sha512-1U7u5LdB+FwC90i3U9WDNeQoD8xoCfQBZPI0M6nB8pBCLW3HIP6Lzd60hTSKPuWOW6ZuXroUGoRG0z/ba8QlNg==} + '@rspack/binding-darwin-x64@1.7.10': + resolution: {integrity: sha512-h/kOGL1bUflDDYnbiUjaRE9kagJpour4FatGihueV03+cRGQ6jpde+BjUakqzMx65CeDbeYI6jAiPhElnlAtRw==} cpu: [x64] os: [darwin] - '@rspack/binding-linux-arm64-gnu@1.4.0': - resolution: {integrity: sha512-XYOfmHDYo88jruqAtyjklTqrNeixigP2QjsiZDiB0YvRQyaoLTw3hZ7RvhJbVruxFibwF75yiMlMD9LJgDTxrg==} + '@rspack/binding-linux-arm64-gnu@1.7.10': + resolution: {integrity: sha512-Z4reus7UxGM4+JuhiIht8KuGP1KgM7nNhOlXUHcQCMswP/Rymj5oJQN3TDWgijFUZs09ULl8t3T+AQAVTd/WvA==} cpu: [arm64] os: [linux] libc: [glibc] - '@rspack/binding-linux-arm64-musl@1.4.0': - resolution: {integrity: sha512-IvQNw3wN5Dro1CeT4f95W7R/ZAKREcsf9s0Kqk4NtBhaPxcEf+nV72mMDBzPUakEI/g32e5Evb91w3y0EFWhIg==} + '@rspack/binding-linux-arm64-musl@1.7.10': + resolution: {integrity: sha512-LYaoVmWizG4oQ3g+St3eM5qxsyfH07kLirP7NJcDMgvu3eQ29MeyTZ3ugkgW6LvlmJue7eTQyf6CZlanoF5SSg==} cpu: [arm64] os: [linux] libc: [musl] - '@rspack/binding-linux-x64-gnu@1.4.0': - resolution: {integrity: sha512-b71w0Iflh/7gvwrpdfJYC4FsCb84mQen3YDYtiBF7VWdXKTN5OzWoibX/3i68l3HBncCf0vmWRr9XFrdejEZEg==} + '@rspack/binding-linux-x64-gnu@1.7.10': + resolution: {integrity: sha512-aIm2G4Kcm3qxDTNqKarK0oaLY2iXnCmpRQQhAcMlR0aS2LmxL89XzVeRr9GFA1MzGrAsZONWCLkxQvn3WUbm4Q==} cpu: [x64] os: [linux] libc: [glibc] - '@rspack/binding-linux-x64-musl@1.4.0': - resolution: {integrity: sha512-aGanEWtYRBiuS6RdI3rOwcV/tetq5duyYRmm/i1RoGAyfGp7vtoPpKVl7PP/hODWp3lZ9PVn3FhxsEaV7LY36Q==} + '@rspack/binding-linux-x64-musl@1.7.10': + resolution: {integrity: sha512-SIHQbAgB9IPH0H3H+i5rN5jo9yA/yTMq8b7XfRkTMvZ7P7MXxJ0dE8EJu3BmCLM19sqnTc2eX+SVfE8ZMDzghA==} cpu: [x64] os: [linux] libc: [musl] - '@rspack/binding-wasm32-wasi@1.4.0': - resolution: {integrity: sha512-3LMP/bs/qCQw7Os/Y9HXVZ15TyXhngM1EQtX0ULJLPdOaX5yO/znXwdzRCI03T8i3MIZxno9EdgHlg1FNt4Puw==} + '@rspack/binding-wasm32-wasi@1.7.10': + resolution: {integrity: sha512-J9HDXHD1tj+9FmX4+K3CTkO7dCE2bootlR37YuC2Owc0Lwl1/i2oGT71KHnMqI9faF/hipAaQM5OywkiiuNB7w==} cpu: [wasm32] - '@rspack/binding-win32-arm64-msvc@1.4.0': - resolution: {integrity: sha512-cduBQqs8bGMFui7gpq+NlMOq1lr78jCvfUNEvZ59hU9xIx3Xewq1yQRNKuCikx24HJCtKRgzYdSMKMpUwnNNMg==} + '@rspack/binding-win32-arm64-msvc@1.7.10': + resolution: {integrity: sha512-FaQGSCXH89nMOYW0bVp0bKQDQbrOEFFm7yedla7g6mkWlFVQo5UyBxid5wJUCqGJBtJepRxeRfByWiaI5nVGvg==} cpu: [arm64] os: [win32] - '@rspack/binding-win32-ia32-msvc@1.4.0': - resolution: {integrity: sha512-ZNWutTwz4iL/9QD0rYk+JKpes5Nx+bQys5aBVM2z/BefvWInw+3beeIRV6SgY8UiO5jc5GEwmdwdv8duzUrhbg==} + '@rspack/binding-win32-ia32-msvc@1.7.10': + resolution: {integrity: sha512-/66TNLOeM4R5dHhRWRVbMTgWghgxz+32ym0c/zGGXQRoMbz7210EoL40ALUgdBdeeREO8LoV+Mn7v8/QZCwHzw==} cpu: [ia32] os: [win32] - '@rspack/binding-win32-x64-msvc@1.4.0': - resolution: {integrity: sha512-pNwpjFCVYQpmA156GgjEqnHly3UZqNXYCJPkouAnUvVE9m0Xoo9May7v7ovKUH4ayySTryL42Ii8tL4iStyJuA==} + '@rspack/binding-win32-x64-msvc@1.7.10': + resolution: {integrity: sha512-SUa3v1W7PGFCy6AHRmDsm43/tkfaZFi1TN2oIk5aCdT9T51baDVBjAbehRDu9xFbK4piL3k7uqIVSIrKgVqk1g==} cpu: [x64] os: [win32] - '@rspack/binding@1.4.0': - resolution: {integrity: sha512-Kex9H6w44oTYzaQK/goV/BhUJoeWL8rPI1symDjRRvSK4ca/P4OceHvlQzfU0WGg2VKnhTAxH3+KMlpYQFabOQ==} + '@rspack/binding@1.7.10': + resolution: {integrity: sha512-j+DPEaSJLRgasxXNpYQpvC7wUkQF5WoWPiTfm4fLczwlAmYwGSVkJiyWDrOlvVPiGGYiXIaXEjVWTw6fT6/vnA==} - '@rspack/core@1.4.0': - resolution: {integrity: sha512-eIzbMYdrpJLjfkelKFLpxUObuv2gAmAuebUJmXeyf2OlFT/DGgoWRDGOVX4MpIHgcE1XCi27sqvOdRU4HA7Zgw==} - engines: {node: '>=16.0.0'} + '@rspack/core@1.7.10': + resolution: {integrity: sha512-dO7J0aHSa9Fg2kGT0+ZsM500lMdlNIyCHavIaz7dTDn6KXvFz1qbWQ/48x3OlNFw1mA0jxAjjw9e7h3sWQZUNg==} + engines: {node: '>=18.12.0'} peerDependencies: '@swc/helpers': '>=0.5.1' peerDependenciesMeta: '@swc/helpers': optional: true - '@rspack/lite-tapable@1.0.1': - resolution: {integrity: sha512-VynGOEsVw2s8TAlLf/uESfrgfrq2+rcXB1muPJYBWbsm1Oa6r5qVQhjA5ggM6z/coYPrsVMgovl3Ff7Q7OCp1w==} - engines: {node: '>=16.0.0'} + '@rspack/lite-tapable@1.1.0': + resolution: {integrity: sha512-E2B0JhYFmVAwdDiG14+DW0Di4Ze4Jg10Pc4/lILUrd5DRCaklduz2OvJ5HYQ6G+hd+WTzqQb3QnDNfK4yvAFYw==} + + '@swc/helpers@0.5.19': + resolution: {integrity: sha512-QamiFeIK3txNjgUTNppE6MiG3p7TdninpZu0E0PbqVh1a9FNLT2FRhisaa4NcaX52XVhA5l7Pk58Ft7Sqi/2sA==} + + '@tiptap/core@3.20.5': + resolution: {integrity: sha512-Pkjd41UJ4F6Z8cPV+gEvqnt1VhY2g66xMjbpxREs0ECA5jRezCNKSZcc2pueQRTMtmn1SaSzGM9U/ifhVlVYOA==} + peerDependencies: + '@tiptap/pm': ^3.20.5 + + '@tiptap/extension-blockquote@3.20.5': + resolution: {integrity: sha512-0wU6H/MWWes0rGzgSW6MMU6YDs/3ofUDkqmqCqmb+Siu1ZD0bpzOYpBtujgOYDY8moB9+zCE3G9HSYGcmZxHew==} + peerDependencies: + '@tiptap/core': ^3.20.5 + + '@tiptap/extension-bold@3.20.5': + resolution: {integrity: sha512-hraiiWkF58n8Jy0Wl3OGwjCTrGWwZZxez/IlexrzKQ/nMFdjDpensZucWwu59zhAM9fqZwGSLDtCFuak03WKnA==} + peerDependencies: + '@tiptap/core': ^3.20.5 + + '@tiptap/extension-bubble-menu@3.20.5': + resolution: {integrity: sha512-6FsASu4o32bp3FzBVb5N2ERjrBy83DtJQAGv9/ycYqsgv2kq9DNlhvtNI7GPiTW7a73ZcImjIX+jEWrARbzOlQ==} + peerDependencies: + '@tiptap/core': ^3.20.5 + '@tiptap/pm': ^3.20.5 + + '@tiptap/extension-code-block@3.20.5': + resolution: {integrity: sha512-0YZnqfqZ1IjzKBM4aezw8j3LZWJFEfs4+mbizHNlnZSYpKzpESYLeaLWGO5SpqF9Z8tmYmSoCaf0fqi5LwgdIA==} + peerDependencies: + '@tiptap/core': ^3.20.5 + '@tiptap/pm': ^3.20.5 + + '@tiptap/extension-code@3.20.5': + resolution: {integrity: sha512-jBZK/CfdMvg1gkNK/zNAk02IExpBPwUfNLRPiJvGhReL2Q73naKxZGQGp+5Lej9VaeFB70UKuRma/iIzuZbgsA==} + peerDependencies: + '@tiptap/core': ^3.20.5 + + '@tiptap/extension-collaboration@3.20.5': + resolution: {integrity: sha512-IalIm6BznHds2VzR4+6gMAgi4VXwZAXdYkl28EPZ8/xscBaUn1tCxcTBbCpmN3UhkABaCoJtmmER5TDy+x72Ag==} + peerDependencies: + '@tiptap/core': ^3.20.5 + '@tiptap/pm': ^3.20.5 + '@tiptap/y-tiptap': ^3.0.2 + yjs: ^13 - '@swc/helpers@0.5.17': - resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==} + '@tiptap/extension-color@3.20.5': + resolution: {integrity: sha512-M984t0/z/Vo2b0yMd90WUFoP94yv9MOxif/pWDK52nJrkn70VXOO0AzgWucLKWPDpn9gJZg73ry/GTHMLPuOYQ==} + peerDependencies: + '@tiptap/extension-text-style': ^3.20.5 + + '@tiptap/extension-details@3.20.5': + resolution: {integrity: sha512-tKggXGlnG+TUZnrxRUnyGvDNxvYCtEttoLRiMnKVr4vltfCHNYnhAAIPyVesb2+leyJaBhdOf66cVol5SbO7cQ==} + peerDependencies: + '@tiptap/core': ^3.20.5 + '@tiptap/extension-text-style': ^3.20.5 + '@tiptap/pm': ^3.20.5 + + '@tiptap/extension-document@3.20.5': + resolution: {integrity: sha512-BpNGHtOTAjjs/6QbkrafMTlaJqb0gsPngFzd5rB0csxx7rYRE9nIEY+oZ44qMw161+2YB4u20L17SX2mUJANBw==} + peerDependencies: + '@tiptap/core': ^3.20.5 + + '@tiptap/extension-drag-handle-vue-3@3.20.5': + resolution: {integrity: sha512-x16hJd9zSREA9nbnX5Jixf2yRUAYGLKaQi1tJWZwL7lDLaqLwAEEyaS6VEBjnn2BBtoiVEOIfGgv4s3i8v+uhQ==} + peerDependencies: + '@tiptap/extension-drag-handle': ^3.20.5 + '@tiptap/pm': ^3.20.5 + '@tiptap/vue-3': ^3.20.5 + vue: ^3.0.0 + + '@tiptap/extension-drag-handle@3.20.5': + resolution: {integrity: sha512-D2W2fkpmXKRG4i0K0XVfbsTRYQMU9RudQIXBB7HnYZFosyJs3dvsT1cFurDUANKLPCxbI24eiubAKs6b1vzGpA==} + peerDependencies: + '@tiptap/core': ^3.20.5 + '@tiptap/extension-collaboration': ^3.20.5 + '@tiptap/extension-node-range': ^3.20.5 + '@tiptap/pm': ^3.20.5 + '@tiptap/y-tiptap': ^3.0.2 + + '@tiptap/extension-floating-menu@3.20.5': + resolution: {integrity: sha512-mTzBNUeAocinrxa5xV+5hGnnNCQB0pVI1GSBwUTHwdB7jNwBqfKAILmtLZONgmhxKWLmGa6WCA59sk+yDI+N0A==} + peerDependencies: + '@floating-ui/dom': ^1.0.0 + '@tiptap/core': ^3.20.5 + '@tiptap/pm': ^3.20.5 + + '@tiptap/extension-hard-break@3.20.5': + resolution: {integrity: sha512-+aILNDO7BsXf0IJ4/0BYh570usFK3Q1t/ZQd8zhHuO2ATeWeDVu1x2F+ouFS4X8fmoCcioMzw15aoz93GET6kQ==} + peerDependencies: + '@tiptap/core': ^3.20.5 + + '@tiptap/extension-heading@3.20.5': + resolution: {integrity: sha512-zXxuIrCSpzgXzRxgCbRE8DZ/NFuinVaniE3pp/9LYAWgRlsAyko8pI2XrVvzzXmDQqRGi2HrNVkNy1yutUWSWQ==} + peerDependencies: + '@tiptap/core': ^3.20.5 + + '@tiptap/extension-highlight@3.20.5': + resolution: {integrity: sha512-rcOOsmpUP7pKRd4TEQocHbKmS7QZZ2aM8N2Rwi5hwgK1ZvqObbcbBIIYpj5Uz72cNmWw26lV+0Y2sGHCsrU4jw==} + peerDependencies: + '@tiptap/core': ^3.20.5 + + '@tiptap/extension-horizontal-rule@3.20.5': + resolution: {integrity: sha512-4UtpUHg8cRzxWjJUGtni5VnXYbhsO7ygf1H1pr4Rv63XMBg9lfYDeSwByIuVy9biEFP7eGEFnezzb5Zlh1btmQ==} + peerDependencies: + '@tiptap/core': ^3.20.5 + '@tiptap/pm': ^3.20.5 + + '@tiptap/extension-image@3.20.5': + resolution: {integrity: sha512-qxKupWKhX75Xc9GJ9Uel+KIFL9x6tb8W3RvQM1UolyJX/H7wyBO7sXp9XmKRkHZsDXRgLVbnkYBe+X83o16AIA==} + peerDependencies: + '@tiptap/core': ^3.20.5 + + '@tiptap/extension-italic@3.20.5': + resolution: {integrity: sha512-7bZCgdJVTvhR5vSmNgFQbGvgRoC6m26KcUpHqWiKA95kLL5Wk4YlMCIqdiDpvJ1eakeFEvDcGZvFLg5+1NiQ+w==} + peerDependencies: + '@tiptap/core': ^3.20.5 + + '@tiptap/extension-link@3.20.5': + resolution: {integrity: sha512-0PukrSYnHX2CrGSThlKfQWxpPWmL7QAvdpDUraKknGvVNSH7tUjchTshy5JdLrn/SQAU92REowRCB6zzCNEFjA==} + peerDependencies: + '@tiptap/core': ^3.20.5 + '@tiptap/pm': ^3.20.5 + + '@tiptap/extension-list@3.20.5': + resolution: {integrity: sha512-s+Y8Q7Orq+WQiwgFB/VPMYZe+6EAR2F69xCpvOynlzTInLO4cF6QpXomuGEYAZxLHe8ZBmeIaR7y8MH/OgjrDw==} + peerDependencies: + '@tiptap/core': ^3.20.5 + '@tiptap/pm': ^3.20.5 + + '@tiptap/extension-node-range@3.20.5': + resolution: {integrity: sha512-6CbgZULF+dQ9KTothAORBZAXPdGneWicMWTV3Gyeh9gNySC18QsGQj3D2GxnllpMekmZXydtDbNFSoCiWjKFWQ==} + peerDependencies: + '@tiptap/core': ^3.20.5 + '@tiptap/pm': ^3.20.5 + + '@tiptap/extension-paragraph@3.20.5': + resolution: {integrity: sha512-mwuhwmff67IpGfOViyRvUC14IlkpsOnB+hSExVnq5+hCntjt/Cr2Z8GGOgzHeIM2FIS0UqX9Lv/b6ttUg4+Now==} + peerDependencies: + '@tiptap/core': ^3.20.5 + + '@tiptap/extension-strike@3.20.5': + resolution: {integrity: sha512-uwhvmfS4ciGYJRLUg0AHbWsprMCwyWVWd2RXOLRm0ZQeWkvzonPXZhJvzIhIgsFkPLj/dsN5t0+LdiK4UQMnyA==} + peerDependencies: + '@tiptap/core': ^3.20.5 + + '@tiptap/extension-subscript@3.20.5': + resolution: {integrity: sha512-LLBkBIUbbep966Hp/WB07ZXP+dpnCG2NRDb1R2Q2P1SkIe1SyyLf5QsvUHmlE4rBDqn+Ra+WzF/qrHoOnYnK3A==} + peerDependencies: + '@tiptap/core': ^3.20.5 + '@tiptap/pm': ^3.20.5 + + '@tiptap/extension-superscript@3.20.5': + resolution: {integrity: sha512-PSC3s54QY1HXSBmjQe3KRe7oFWuIIrVwFznbEq/qKfEXjf1wskdtxqpkTpekpPfjZtWajikKBSpyRh4YgqovGQ==} + peerDependencies: + '@tiptap/core': ^3.20.5 + '@tiptap/pm': ^3.20.5 + + '@tiptap/extension-table@3.20.5': + resolution: {integrity: sha512-YvTB5OfGqjqHqutkSyywplouFvJwlsDTpZAjtAh5TzKfOan42aiVepmHVpteoQP6LH0mSjw69RndFMIYhIGmSQ==} + peerDependencies: + '@tiptap/core': ^3.20.5 + '@tiptap/pm': ^3.20.5 + + '@tiptap/extension-text-align@3.20.5': + resolution: {integrity: sha512-r7xIt6AE1Zf6epMg2GLAD55YIqJewunQnLUeApLkcWdTNuCJqZUrmVIXUvTtj2Ivw0VIaPoCtnB+VBvXEuC3Kg==} + peerDependencies: + '@tiptap/core': ^3.20.5 + + '@tiptap/extension-text-style@3.20.5': + resolution: {integrity: sha512-x1tgCkF+KptIbNajduZFVxU5BazQTzpxmkyBEpXmyeiKrLDDv3IWGUsHXv8FWYUolcF9kmEJY5fIjOutvkF/yQ==} + peerDependencies: + '@tiptap/core': ^3.20.5 + + '@tiptap/extension-text@3.20.5': + resolution: {integrity: sha512-DMa9g5cH2d/Gx1KXtV7txTxaa6FBqgG8glmfug+N93VMb8sEZR1Yu1az++yAep4SGGq9GWIGZCUS3H6W66et6Q==} + peerDependencies: + '@tiptap/core': ^3.20.5 + + '@tiptap/extension-underline@3.20.5': + resolution: {integrity: sha512-HMhr5KIAqZsEhlN8RxKHr/ql1a8OvBa9fLf69IwUVFolBcDExHWUtaEV/axYVRQJvvIy2oKGJxlJWDZ4hkotHQ==} + peerDependencies: + '@tiptap/core': ^3.20.5 + + '@tiptap/extensions@3.20.5': + resolution: {integrity: sha512-c4am6SznqfMnbUNSh4MvufiD7cMLdqL1BArok22uBgSWkS1sB9RVBYe8+x0jrOkk0UPEVlzDHbQ+nU+WmIyS2Q==} + peerDependencies: + '@tiptap/core': ^3.20.5 + '@tiptap/pm': ^3.20.5 - '@tsconfig/node20@20.1.6': - resolution: {integrity: sha512-sz+Hqx9zwZDpZIV871WSbUzSqNIsXzghZydypnfgzPKLltVJfkINfUeTct31n/tTSa9ZE1ZOfKdRre1uHHquYQ==} + '@tiptap/pm@3.20.5': + resolution: {integrity: sha512-yJhDa7Chx2EqJMX/jlewBv0za7slf1dKHWYve1XaApuVHEkxl0Ul3EDbwnx316vIITkuFW/pWSwkSsAplyBeCw==} + + '@tiptap/suggestion@3.20.5': + resolution: {integrity: sha512-5fqRNgnzYdJ1oDpyLqwrbVsZwvI+5VW/U89LPMvBYM7sFS7Xd0xfyxyAOWcJN4V0zLeTcuElWN3R+IUTLKbU+Q==} + peerDependencies: + '@tiptap/core': ^3.20.5 + '@tiptap/pm': ^3.20.5 + + '@tiptap/vue-3@3.20.5': + resolution: {integrity: sha512-5uUK3RAMNvUetZOv56Kz8nurhxHxMH60GgCCrVFgIBZoTc14u3d3v7EpcA6gNgzogutrR8GxvyFU3iIkj4kkHA==} + peerDependencies: + '@floating-ui/dom': ^1.0.0 + '@tiptap/core': ^3.20.5 + '@tiptap/pm': ^3.20.5 + vue: ^3.0.0 + + '@tiptap/y-tiptap@3.0.2': + resolution: {integrity: sha512-flMn/YW6zTbc6cvDaUPh/NfLRTXDIqgpBUkYzM74KA1snqQwhOMjnRcnpu4hDFrTnPO6QGzr99vRyXEA7M44WA==} + engines: {node: '>=16.0.0', npm: '>=8.0.0'} + peerDependencies: + prosemirror-model: ^1.7.1 + prosemirror-state: ^1.2.3 + prosemirror-view: ^1.9.10 + y-protocols: ^1.0.1 + yjs: ^13.5.38 - '@tybys/wasm-util@0.9.0': - resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==} + '@tsconfig/node20@20.1.9': + resolution: {integrity: sha512-IjlTv1RsvnPtUcjTqtVsZExKVq+KQx4g5pCP5tI7rAs6Xesl2qFwSz/tPDBC4JajkL/MlezBu3gPUwqRHl+RIg==} + + '@tybys/wasm-util@0.10.1': + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} '@types/canvas-confetti@1.9.0': resolution: {integrity: sha512-aBGj/dULrimR1XDZLtG9JwxX1b4HPRF6CX9Yfwh3NvstZEm1ZL7RBnel4keCPSqs1ANRu1u2Aoz9R+VmtjYuTg==} - '@types/chai@5.2.2': - resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} @@ -853,6 +1097,9 @@ packages: '@types/eslint@9.6.1': resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} + '@types/esrecurse@4.3.1': + resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -862,69 +1109,78 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/node@20.19.1': - resolution: {integrity: sha512-jJD50LtlD2dodAEO653i3YF04NWak6jN3ky+Ri3Em3mGR39/glWiboM/IePaRbgwSfqM1TpGXfAg8ohn/4dTgA==} + '@types/linkify-it@5.0.0': + resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} + + '@types/markdown-it@14.1.2': + resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} + + '@types/mdurl@2.0.0': + resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} + + '@types/node@20.19.37': + resolution: {integrity: sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==} '@types/tough-cookie@4.0.5': resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} - '@typescript-eslint/eslint-plugin@8.35.0': - resolution: {integrity: sha512-ijItUYaiWuce0N1SoSMrEd0b6b6lYkYt99pqCPfybd+HKVXtEvYhICfLdwp42MhiI5mp0oq7PKEL+g1cNiz/Eg==} + '@typescript-eslint/eslint-plugin@8.57.2': + resolution: {integrity: sha512-NZZgp0Fm2IkD+La5PR81sd+g+8oS6JwJje+aRWsDocxHkjyRw0J5L5ZTlN3LI1LlOcGL7ph3eaIUmTXMIjLk0w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.35.0 - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' + '@typescript-eslint/parser': ^8.57.2 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.35.0': - resolution: {integrity: sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==} + '@typescript-eslint/parser@8.57.2': + resolution: {integrity: sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.35.0': - resolution: {integrity: sha512-41xatqRwWZuhUMF/aZm2fcUsOFKNcG28xqRSS6ZVr9BVJtGExosLAm5A1OxTjRMagx8nJqva+P5zNIGt8RIgbQ==} + '@typescript-eslint/project-service@8.57.2': + resolution: {integrity: sha512-FuH0wipFywXRTHf+bTTjNyuNQQsQC3qh/dYzaM4I4W0jrCqjCVuUh99+xd9KamUfmCGPvbO8NDngo/vsnNVqgw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <5.9.0' + typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.35.0': - resolution: {integrity: sha512-+AgL5+mcoLxl1vGjwNfiWq5fLDZM1TmTPYs2UkyHfFhgERxBbqHlNjRzhThJqz+ktBqTChRYY6zwbMwy0591AA==} + '@typescript-eslint/scope-manager@8.57.2': + resolution: {integrity: sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.35.0': - resolution: {integrity: sha512-04k/7247kZzFraweuEirmvUj+W3bJLI9fX6fbo1Qm2YykuBvEhRTPl8tcxlYO8kZZW+HIXfkZNoasVb8EV4jpA==} + '@typescript-eslint/tsconfig-utils@8.57.2': + resolution: {integrity: sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <5.9.0' + typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.35.0': - resolution: {integrity: sha512-ceNNttjfmSEoM9PW87bWLDEIaLAyR+E6BoYJQ5PfaDau37UGca9Nyq3lBk8Bw2ad0AKvYabz6wxc7DMTO2jnNA==} + '@typescript-eslint/type-utils@8.57.2': + resolution: {integrity: sha512-Co6ZCShm6kIbAM/s+oYVpKFfW7LBc6FXoPXjTRQ449PPNBY8U0KZXuevz5IFuuUj2H9ss40atTaf9dlGLzbWZg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.35.0': - resolution: {integrity: sha512-0mYH3emanku0vHw2aRLNGqe7EXh9WHEhi7kZzscrMDf6IIRUQ5Jk4wp1QrledE/36KtdZrVfKnE32eZCf/vaVQ==} + '@typescript-eslint/types@8.57.2': + resolution: {integrity: sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.35.0': - resolution: {integrity: sha512-F+BhnaBemgu1Qf8oHrxyw14wq6vbL8xwWKKMwTMwYIRmFFY/1n/9T/jpbobZL8vp7QyEUcC6xGrnAO4ua8Kp7w==} + '@typescript-eslint/typescript-estree@8.57.2': + resolution: {integrity: sha512-2MKM+I6g8tJxfSmFKOnHv2t8Sk3T6rF20A1Puk0svLK+uVapDZB/4pfAeB7nE83uAZrU6OxW+HmOd5wHVdXwXA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <5.9.0' + typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.35.0': - resolution: {integrity: sha512-nqoMu7WWM7ki5tPgLVsmPM8CkqtoPUG6xXGeefM5t4x3XumOEKMoUZPdi+7F+/EotukN4R9OWdmDxN80fqoZeg==} + '@typescript-eslint/utils@8.57.2': + resolution: {integrity: sha512-krRIbvPK1ju1WBKIefiX+bngPs+odIQUtR7kymzPfo1POVw3jlF+nLkmexdSSd4UCbDcQn+wMBATOOmpBbqgKg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@8.35.0': - resolution: {integrity: sha512-zTh2+1Y8ZpmeQaQVIc/ZZxsx8UzgKJyNg1PTvjzC7WMhPSVS8bfDX34k1SrwOf016qd5RU3az2UxUNue3IfQ5g==} + '@typescript-eslint/visitor-keys@8.57.2': + resolution: {integrity: sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@vitejs/plugin-vue@5.2.4': @@ -934,13 +1190,17 @@ packages: vite: ^5.0.0 || ^6.0.0 vue: ^3.2.25 - '@vitest/eslint-plugin@1.3.3': - resolution: {integrity: sha512-zOB4T5f80JXfP5DC2yQl7azRYq8PmGqYle3uxh3a0NnbKc+EaSYSpEcrVAh2r5W97pi3BVv7oRb5NdEQy0cCXA==} + '@vitest/eslint-plugin@1.6.13': + resolution: {integrity: sha512-ui7JGWBoQpS5NKKW0FDb1eTuFEZ5EupEv2Psemuyfba7DfA5K52SeDLelt6P4pQJJ/4UGkker/BgMk/KrjH3WQ==} + engines: {node: '>=18'} peerDependencies: - eslint: '>= 8.57.0' - typescript: '>= 5.0.0' + '@typescript-eslint/eslint-plugin': '*' + eslint: '>=8.57.0' + typescript: '>=5.0.0' vitest: '*' peerDependenciesMeta: + '@typescript-eslint/eslint-plugin': + optional: true typescript: optional: true vitest: @@ -984,17 +1244,17 @@ packages: '@volar/typescript@2.4.15': resolution: {integrity: sha512-2aZ8i0cqPGjXb4BhkMsPYDkkuc2ZQ6yOpqwAuNwUoncELqoy5fRgOQtLR9gB0g902iS0NAkvpIzs27geVyVdPg==} - '@vue/compiler-core@3.5.17': - resolution: {integrity: sha512-Xe+AittLbAyV0pabcN7cP7/BenRBNcteM4aSDCtRvGw0d9OL+HG1u/XHLY/kt1q4fyMeZYXyIYrsHuPSiDPosA==} + '@vue/compiler-core@3.5.30': + resolution: {integrity: sha512-s3DfdZkcu/qExZ+td75015ljzHc6vE+30cFMGRPROYjqkroYI5NV2X1yAMX9UeyBNWB9MxCfPcsjpLS11nzkkw==} - '@vue/compiler-dom@3.5.17': - resolution: {integrity: sha512-+2UgfLKoaNLhgfhV5Ihnk6wB4ljyW1/7wUIog2puUqajiC29Lp5R/IKDdkebh9jTbTogTbsgB+OY9cEWzG95JQ==} + '@vue/compiler-dom@3.5.30': + resolution: {integrity: sha512-eCFYESUEVYHhiMuK4SQTldO3RYxyMR/UQL4KdGD1Yrkfdx4m/HYuZ9jSfPdA+nWJY34VWndiYdW/wZXyiPEB9g==} - '@vue/compiler-sfc@3.5.17': - resolution: {integrity: sha512-rQQxbRJMgTqwRugtjw0cnyQv9cP4/4BxWfTdRBkqsTfLOHWykLzbOc3C4GGzAmdMDxhzU/1Ija5bTjMVrddqww==} + '@vue/compiler-sfc@3.5.30': + resolution: {integrity: sha512-LqmFPDn89dtU9vI3wHJnwaV6GfTRD87AjWpTWpyrdVOObVtjIuSeZr181z5C4PmVx/V3j2p+0f7edFKGRMpQ5A==} - '@vue/compiler-ssr@3.5.17': - resolution: {integrity: sha512-hkDbA0Q20ZzGgpj5uZjb9rBzQtIHLS78mMilwrlpWk2Ep37DYntUz0PonQ6kr113vfOEdM+zTBuJDaceNIW0tQ==} + '@vue/compiler-ssr@3.5.30': + resolution: {integrity: sha512-NsYK6OMTnx109PSL2IAyf62JP6EUdk4Dmj6AkWcJGBvN0dQoMYtVekAmdqgTtWQgEJo+Okstbf/1p7qZr5H+bA==} '@vue/compiler-vue2@2.7.16': resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==} @@ -1017,41 +1277,41 @@ packages: eslint: '>= 8.21.0' prettier: '>= 3.0.0' - '@vue/eslint-config-typescript@14.5.1': - resolution: {integrity: sha512-ys6qdYHGXS/WLt0r5vUcTiG163F4NbNpx3ABTsGITw8k5uCFiv4g9E1N9Jydlw62KzJMVKGcpXbg6LCA3fV+eA==} + '@vue/eslint-config-typescript@14.7.0': + resolution: {integrity: sha512-iegbMINVc+seZ/QxtzWiOBozctrHiF2WvGedruu2EbLujg9VuU0FQiNcN2z1ycuaoKKpF4m2qzB5HDEMKbxtIg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^9.10.0 + eslint: ^9.10.0 || ^10.0.0 eslint-plugin-vue: ^9.28.0 || ^10.0.0 typescript: '>=4.8.4' peerDependenciesMeta: typescript: optional: true - '@vue/language-core@2.2.10': - resolution: {integrity: sha512-+yNoYx6XIKuAO8Mqh1vGytu8jkFEOH5C8iOv3i8Z/65A7x9iAOXA97Q+PqZ3nlm2lxf5rOJuIGI/wDtx/riNYw==} + '@vue/language-core@2.2.12': + resolution: {integrity: sha512-IsGljWbKGU1MZpBPN+BvPAdr55YPkj2nB/TBNGNC32Vy2qLG25DYu/NBN2vNtZqdRbTRjaoYrahLrToim2NanA==} peerDependencies: typescript: '*' peerDependenciesMeta: typescript: optional: true - '@vue/reactivity@3.5.17': - resolution: {integrity: sha512-l/rmw2STIscWi7SNJp708FK4Kofs97zc/5aEPQh4bOsReD/8ICuBcEmS7KGwDj5ODQLYWVN2lNibKJL1z5b+Lw==} + '@vue/reactivity@3.5.30': + resolution: {integrity: sha512-179YNgKATuwj9gB+66snskRDOitDiuOZqkYia7mHKJaidOMo/WJxHKF8DuGc4V4XbYTJANlfEKb0yxTQotnx4Q==} - '@vue/runtime-core@3.5.17': - resolution: {integrity: sha512-QQLXa20dHg1R0ri4bjKeGFKEkJA7MMBxrKo2G+gJikmumRS7PTD4BOU9FKrDQWMKowz7frJJGqBffYMgQYS96Q==} + '@vue/runtime-core@3.5.30': + resolution: {integrity: sha512-e0Z+8PQsUTdwV8TtEsLzUM7SzC7lQwYKePydb7K2ZnmS6jjND+WJXkmmfh/swYzRyfP1EY3fpdesyYoymCzYfg==} - '@vue/runtime-dom@3.5.17': - resolution: {integrity: sha512-8El0M60TcwZ1QMz4/os2MdlQECgGoVHPuLnQBU3m9h3gdNRW9xRmI8iLS4t/22OQlOE6aJvNNlBiCzPHur4H9g==} + '@vue/runtime-dom@3.5.30': + resolution: {integrity: sha512-2UIGakjU4WSQ0T4iwDEW0W7vQj6n7AFn7taqZ9Cvm0Q/RA2FFOziLESrDL4GmtI1wV3jXg5nMoJSYO66egDUBw==} - '@vue/server-renderer@3.5.17': - resolution: {integrity: sha512-BOHhm8HalujY6lmC3DbqF6uXN/K00uWiEeF22LfEsm9Q93XeJ/plHTepGwf6tqFcF7GA5oGSSAAUock3VvzaCA==} + '@vue/server-renderer@3.5.30': + resolution: {integrity: sha512-v+R34icapydRwbZRD0sXwtHqrQJv38JuMB4JxbOxd8NEpGLny7cncMp53W9UH/zo4j8eDHjQ1dEJXwzFQknjtQ==} peerDependencies: - vue: 3.5.17 + vue: 3.5.30 - '@vue/shared@3.5.17': - resolution: {integrity: sha512-CabR+UN630VnsJO/jHWYBC1YVXyMq94KKp6iF5MQgZJs5I8cmjw6oVMO1oDbtBkENSHSSn/UadWlW/OAgdmKrg==} + '@vue/shared@3.5.30': + resolution: {integrity: sha512-YXgQ7JjaO18NeK2K9VTbDHaFy62WrObMa6XERNfNOkAhD1F1oDSf3ZJ7K6GqabZ0BvSDHajp8qfS5Sa2I9n8uQ==} '@vue/test-utils@2.4.6': resolution: {integrity: sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==} @@ -1122,18 +1382,24 @@ packages: resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + acorn-import-phases@1.0.4: + resolution: {integrity: sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==} + engines: {node: '>=10.13.0'} + peerDependencies: + acorn: ^8.14.0 + acorn-jsx@5.3.2: - resolution: {integrity: sha1-ftW7VZCLOy8bxVxq8WU7rafweTc=} + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn@8.15.0: - resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} engines: {node: '>=0.4.0'} hasBin: true - agent-base@7.1.3: - resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} ajv-formats@2.1.1: @@ -1149,11 +1415,11 @@ packages: peerDependencies: ajv: ^8.8.2 - ajv@6.12.6: - resolution: {integrity: sha1-uvWmLoArB9l3A0WG+MO69a3ybfQ=} + ajv@6.14.0: + resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} - ajv@8.17.1: - resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + ajv@8.18.0: + resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} alien-signals@1.0.13: resolution: {integrity: sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==} @@ -1162,42 +1428,51 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - ansi-regex@6.1.0: - resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} engines: {node: '>=12'} ansi-styles@4.3.0: - resolution: {integrity: sha1-7dgDYornHATIWuegkG7a00tkiTc=} + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} - ansi-styles@6.2.1: - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} argparse@2.0.1: - resolution: {integrity: sha1-JG9Q88p4oyQPbJl+ipvR6sSeSzg=} + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} asynckit@0.4.0: - resolution: {integrity: sha1-x57Zf380y48robyXkLzDZkdLS3k=} + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - axios@1.10.0: - resolution: {integrity: sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==} + axios@1.13.6: + resolution: {integrity: sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==} balanced-match@1.0.2: - resolution: {integrity: sha1-6D46fj8wCzTLnYf2FfoMvzV2kO4=} + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + baseline-browser-mapping@2.10.10: + resolution: {integrity: sha512-sUoJ3IMxx4AyRqO4MLeHlnGDkyXRoUG0/AI9fjK+vS72ekpV0yWVY7O0BVjmBcRtkNcsAO2QDZ4tdKKGoI6YaQ==} + engines: {node: '>=6.0.0'} + hasBin: true big.js@5.2.2: - resolution: {integrity: sha1-ZfCvOC9Xi83HQr2cKB6cstd2gyg=} + resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==} birpc@2.9.0: resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==} boolbase@1.0.0: - resolution: {integrity: sha1-aN/1++YMUes3cl6p4+0xDcwed24=} + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -1205,18 +1480,19 @@ packages: brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + brace-expansion@5.0.5: + resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} + engines: {node: 18 || 20 || >=22} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.25.1: - resolution: {integrity: sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==} + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - buffer-builder@0.2.0: - resolution: {integrity: sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==} - buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -1233,25 +1509,25 @@ packages: engines: {node: '>= 0.4'} callsites@3.1.0: - resolution: {integrity: sha1-s2MKvYlDQy9Us/BRkjjjPNffL3M=} + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - caniuse-lite@1.0.30001726: - resolution: {integrity: sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==} + caniuse-lite@1.0.30001781: + resolution: {integrity: sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==} - canvas-confetti@1.9.3: - resolution: {integrity: sha512-rFfTURMvmVEX1gyXFgn5QMn81bYk70qa0HLzcIOSVEyl57n6o9ItHeBtUSWdvKAPY0xlvBHno4/v3QPrT83q9g==} + canvas-confetti@1.9.4: + resolution: {integrity: sha512-yxQbJkAVrFXWNbTUjPqjF7G+g6pDotOUHGbkZq2NELZUMDpiJ85rIEazVb8GTaAptNW2miJAXbs1BtioA251Pw==} - chai@5.2.0: - resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} - engines: {node: '>=12'} + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} chalk@4.1.2: - resolution: {integrity: sha1-qsTit3NKdAhnrrFr8CqtVWoeegE=} + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - check-error@2.1.1: - resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} engines: {node: '>= 16'} chokidar@4.0.3: @@ -1263,17 +1539,17 @@ packages: engines: {node: '>=6.0'} color-convert@2.0.1: - resolution: {integrity: sha1-ctOmjVmMm9s68q0ehPIdiWq9TeM=} + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} color-name@1.1.4: - resolution: {integrity: sha1-wqCah6y95pVD3m9j+jmVyCbFNqI=} + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} colorjs.io@0.5.2: resolution: {integrity: sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==} combined-stream@1.0.8: - resolution: {integrity: sha1-w9RaizT9cwYxoRCoolIGgrMdWn8=} + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} commander@10.0.1: @@ -1283,31 +1559,37 @@ packages: commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + compute-scroll-into-view@3.1.1: + resolution: {integrity: sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==} + concat-map@0.0.1: - resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} confbox@0.1.8: resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} - confbox@0.2.2: - resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} + confbox@0.2.4: + resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==} config-chain@1.1.13: - resolution: {integrity: sha1-+tB5Wqamza/57Rto6d/5Q3LCMvQ=} + resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} copy-anything@4.0.5: resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==} engines: {node: '>=18'} - core-js@3.43.0: - resolution: {integrity: sha512-N6wEbTTZSYOY2rYAn85CuvWWkCK6QweMn7/4Nr3w+gDBeBhk/x4EJeY6FPo4QzDoJZxVTv8U7CMvgWk6pOHHqA==} + core-js@3.47.0: + resolution: {integrity: sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==} + + crelt@1.0.6: + resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} cssesc@3.0.0: - resolution: {integrity: sha1-N3QZGZA7hoVl4cCep0dEXNGJg+4=} + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} hasBin: true @@ -1315,18 +1597,18 @@ packages: resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==} engines: {node: '>=18'} - csstype@3.1.3: - resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} data-urls@5.0.0: resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} engines: {node: '>=18'} de-indent@1.0.2: - resolution: {integrity: sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=} + resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} - debug@4.4.1: - resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} peerDependencies: supports-color: '*' @@ -1334,28 +1616,27 @@ packages: supports-color: optional: true - decimal.js@10.5.0: - resolution: {integrity: sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==} + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} deep-eql@5.0.2: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} deep-is@0.1.4: - resolution: {integrity: sha1-pvLc5hL63S7x9Rm3NVHxfoUZmDE=} + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} delayed-stream@1.0.0: - resolution: {integrity: sha1-3zrhmayt+31ECqrgsp4icrJOxhk=} + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} - detect-libc@1.0.3: - resolution: {integrity: sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=} - engines: {node: '>=0.10'} - hasBin: true + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} @@ -1364,26 +1645,26 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - editorconfig@1.0.4: - resolution: {integrity: sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==} + editorconfig@1.0.7: + resolution: {integrity: sha512-e0GOtq/aTQhVdNyDU9e02+wz9oDDM+SIOQxWME2QRjzRX5yyLAuHDE+0aE8vHb9XRC8XD37eO2u57+F09JqFhw==} engines: {node: '>=14'} hasBin: true - electron-to-chromium@1.5.177: - resolution: {integrity: sha512-7EH2G59nLsEMj97fpDuvVcYi6lwTcM1xuWw3PssD8xzboAW7zj7iB3COEEEATUfjLHrs5uKBLQT03V/8URx06g==} + electron-to-chromium@1.5.322: + resolution: {integrity: sha512-vFU34OcrvMcH66T+dYC3G4nURmgfDVewMIu6Q2urXpumAPSMmzvcn04KVVV8Opikq8Vs5nUbO/8laNhNRqSzYw==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} emoji-regex@9.2.2: - resolution: {integrity: sha1-hAyIA7DYBH9P8M+WMXazLU7z7XI=} + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} emojis-list@3.0.0: - resolution: {integrity: sha1-VXBmIEatKeLpFucariYKvf9Pang=} + resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==} engines: {node: '>= 4'} - enhanced-resolve@5.18.2: - resolution: {integrity: sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==} + enhanced-resolve@5.20.1: + resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==} engines: {node: '>=10.13.0'} entities@4.5.0: @@ -1394,6 +1675,10 @@ packages: resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} + entities@7.0.1: + resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} + engines: {node: '>=0.12'} + es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -1405,6 +1690,9 @@ packages: es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-module-lexer@2.0.0: + resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} + es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} @@ -1413,8 +1701,8 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} - esbuild@0.25.5: - resolution: {integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==} + esbuild@0.27.4: + resolution: {integrity: sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==} engines: {node: '>=18'} hasBin: true @@ -1423,11 +1711,11 @@ packages: engines: {node: '>=6'} escape-string-regexp@4.0.0: - resolution: {integrity: sha1-FLqDpdNz49MR5a/KKc9b+tllvzQ=} + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - eslint-config-prettier@10.1.5: - resolution: {integrity: sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==} + eslint-config-prettier@10.1.8: + resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} hasBin: true peerDependencies: eslint: '>=7.0.0' @@ -1435,8 +1723,8 @@ packages: eslint-plugin-oxlint@0.16.12: resolution: {integrity: sha512-41nSsLHg2oOnl7E/Bb5dypPuIAlMDTobo71+HeRVn1wipC5VXU8GBPVXiwi2RVcnwpEHy3TwEYcatrDMjKd3Sg==} - eslint-plugin-prettier@5.5.1: - resolution: {integrity: sha512-dobTkHT6XaEVOo8IO90Q4DOSxnm3Y151QxPJlM/vKC0bVy+d6cVWQZLlFiuZPP0wS6vZwSKeJgKkcS+KfMBlRw==} + eslint-plugin-prettier@5.5.5: + resolution: {integrity: sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: '@types/eslint': '>=8.0.0' @@ -1464,6 +1752,10 @@ packages: resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-scope@9.1.2: + resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1472,8 +1764,12 @@ packages: resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.29.0: - resolution: {integrity: sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==} + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@9.39.4: + resolution: {integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -1486,12 +1782,16 @@ packages: resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - esquery@1.6.0: - resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + espree@11.2.0: + resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} engines: {node: '>=0.10'} esrecurse@4.3.0: - resolution: {integrity: sha1-eteWTWeauyi+5yzsY3WLHF0smSE=} + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} engines: {node: '>=4.0'} estraverse@4.3.0: @@ -1499,32 +1799,32 @@ packages: engines: {node: '>=4.0'} estraverse@5.3.0: - resolution: {integrity: sha1-LupSkHAvJquP5TcDcP+GyWXSESM=} + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} estree-walker@2.0.2: - resolution: {integrity: sha1-UvAQF4wqTBF6d1fP6UKtt9LaTKw=} + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} esutils@2.0.3: - resolution: {integrity: sha1-dNLrTeC42hKTcRkQ1Qd1ubcQ72Q=} + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} events@3.3.0: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} - expect-type@1.2.1: - resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==} + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} - exsolve@1.0.7: - resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==} + exsolve@1.0.8: + resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} fast-deep-equal@3.1.3: - resolution: {integrity: sha1-On1WtVnWy8PrUSMlJE5hmmXGxSU=} + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} fast-diff@1.3.0: resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} @@ -1534,19 +1834,20 @@ packages: engines: {node: '>=8.6.0'} fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha1-h0v2nG9ATCtdmcSBNBOZ/VWJJjM=} + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} fast-levenshtein@2.0.6: - resolution: {integrity: sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=} + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fast-uri@3.0.6: - resolution: {integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==} + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} - fastq@1.19.1: - resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} - fdir@6.4.6: - resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} peerDependencies: picomatch: ^3 || ^4 peerDependenciesMeta: @@ -1562,15 +1863,15 @@ packages: engines: {node: '>=8'} find-up@5.0.0: - resolution: {integrity: sha1-TJKBnstwg1YeT0okCoa+UZj1Nvw=} + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} flat-cache@4.0.1: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} - flatted@3.3.3: - resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} floating-vue@5.2.2: resolution: {integrity: sha512-afW+h2CFafo+7Y9Lvw/xsqjaQlKLdJV7h1fCHfcYQ1C4SVMlu7OAekqWgu5d4SgvkBVU0pVpLlVsrSTBURFRkg==} @@ -1581,8 +1882,8 @@ packages: '@nuxt/kit': optional: true - follow-redirects@1.15.9: - resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} engines: {node: '>=4.0'} peerDependencies: debug: '*' @@ -1594,8 +1895,8 @@ packages: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} - form-data@4.0.3: - resolution: {integrity: sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==} + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} engines: {node: '>= 6'} fsevents@2.3.3: @@ -1614,29 +1915,30 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} + github-markdown-css@5.9.0: + resolution: {integrity: sha512-tmT5sY+zvg2302XLYEfH2mtkViIM1SWf2nvYoF5N1ZsO0V6B2qZTiw3GOzw4vpjLygK/KG35qRlPFweHqfzz5w==} + engines: {node: '>=10'} + glob-parent@5.1.2: - resolution: {integrity: sha1-hpgyxYA0/mikCTwX3BXoNA2EAcQ=} + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} glob-parent@6.0.2: - resolution: {integrity: sha1-bSN9mQg5UMeSkPJMdkKj3poo+eM=} + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} glob-to-regexp@0.4.1: resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} - glob@10.4.5: - resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} - globals@15.15.0: - resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} - engines: {node: '>=18'} - gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -1644,11 +1946,8 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - has-flag@4.0.0: - resolution: {integrity: sha1-lEdx/ZyByBJlxNaUGGDaBrtZR5s=} + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} has-symbols@1.1.0: @@ -1667,7 +1966,7 @@ packages: engines: {node: '>= 0.4'} he@1.2.0: - resolution: {integrity: sha1-hK5l+n6vsWX922FWauFLrwVmTw8=} + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true hookable@5.5.3: @@ -1686,7 +1985,7 @@ packages: engines: {node: '>= 14'} iconv-lite@0.6.3: - resolution: {integrity: sha1-pS+AvzjaGVLrXGgXkHGYcaGnJQE=} + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} ignore@5.3.2: @@ -1697,22 +1996,22 @@ packages: resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} engines: {node: '>= 4'} - immutable@5.1.3: - resolution: {integrity: sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==} + immutable@5.1.5: + resolution: {integrity: sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==} import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} imurmurhash@0.1.4: - resolution: {integrity: sha1-khi5srkoojixPcT7a21XbyMUU+o=} + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} ini@1.3.8: - resolution: {integrity: sha1-op2kJbSIBvNHZ6Tvzjlyaa8oQyw=} + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} is-extglob@2.1.1: - resolution: {integrity: sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=} + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} is-fullwidth-code-point@3.0.0: @@ -1720,26 +2019,29 @@ packages: engines: {node: '>=8'} is-glob@4.0.3: - resolution: {integrity: sha1-ZPYeQsu7LuwgcanawLKLoeZdUIQ=} + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} is-number@7.0.0: - resolution: {integrity: sha1-dTU0W4lnNNX4DE0GxQlVUnoU8Ss=} + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} is-potential-custom-element-name@1.0.1: - resolution: {integrity: sha1-Fx7W8Z46xVQ5Tt94yqBXhKRb67U=} + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} is-what@5.5.0: resolution: {integrity: sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==} engines: {node: '>=18'} isexe@2.0.0: - resolution: {integrity: sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=} + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - isexe@3.1.1: - resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} - engines: {node: '>=16'} + isexe@3.1.5: + resolution: {integrity: sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==} + engines: {node: '>=18'} + + isomorphic.js@0.2.5: + resolution: {integrity: sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==} jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} @@ -1748,8 +2050,8 @@ packages: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} - jiti@2.4.2: - resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true js-beautify@1.15.4: @@ -1764,10 +2066,6 @@ packages: js-tokens@9.0.1: resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} - js-yaml@4.1.0: - resolution: {integrity: sha1-wftl+PUBeQHN0slRhkuhhFihBgI=} - hasBin: true - js-yaml@4.1.1: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true @@ -1782,7 +2080,7 @@ packages: optional: true json-buffer@3.0.1: - resolution: {integrity: sha1-kziAKjDTtmBfvgYT4JQAjKjAWhM=} + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} @@ -1792,13 +2090,13 @@ packages: engines: {node: ^18.17.0 || >=20.5.0} json-schema-traverse@0.4.1: - resolution: {integrity: sha1-afaofZUTq4u4/mO9sJecRI5oRmA=} + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=} + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} @@ -1811,57 +2109,69 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - kolorist@1.8.0: - resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} - levn@0.4.1: - resolution: {integrity: sha1-rkViwAdHO5MqYgDUAyaN0v/8at4=} + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} - loader-runner@4.3.0: - resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==} + lib0@0.2.117: + resolution: {integrity: sha512-DeXj9X5xDCjgKLU/7RR+/HQEVzuuEUiwldwOGsHK/sfAfELGWEyTcf0x+uOvCvK3O2zPmZePXWL85vtia6GyZw==} + engines: {node: '>=16'} + hasBin: true + + linkify-it@5.0.0: + resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + + linkifyjs@4.3.2: + resolution: {integrity: sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==} + + loader-runner@4.3.1: + resolution: {integrity: sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==} engines: {node: '>=6.11.5'} loader-utils@2.0.4: resolution: {integrity: sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==} engines: {node: '>=8.9.0'} - local-pkg@1.1.1: - resolution: {integrity: sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg==} + local-pkg@1.1.2: + resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} engines: {node: '>=14'} locate-path@6.0.0: - resolution: {integrity: sha1-VTIeswn+u8WcSAHZMackUqaB0oY=} + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} lodash.merge@4.6.2: - resolution: {integrity: sha1-VYqlO0O2YeGSWgr9+japoQhf5Xo=} + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - lodash@4.17.21: - resolution: {integrity: sha1-Z5WRxWTDv/quhFTPCz3zcMPWkRw=} - - loupe@3.1.4: - resolution: {integrity: sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==} + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - magic-string@0.30.17: - resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + markdown-it@14.1.1: + resolution: {integrity: sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==} + hasBin: true math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mdurl@2.0.0: + resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + memorystream@0.3.1: - resolution: {integrity: sha1-htcJCzDORV1j+64S3aUaR93K+bI=} + resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} engines: {node: '>= 0.10.0'} merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} merge2@1.4.1: - resolution: {integrity: sha1-Q2iJL4hekHRVpv19xVwMnUBJkK4=} + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} micromatch@4.0.8: @@ -1876,29 +2186,29 @@ packages: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@10.2.4: + resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} + engines: {node: 18 || 20 || >=22} - minimatch@9.0.1: - resolution: {integrity: sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==} - engines: {node: '>=16 || 14 >=14.17'} + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} engines: {node: '>=16 || 14 >=14.17'} - minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} engines: {node: '>=16 || 14 >=14.17'} mitt@3.0.1: resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} - mlly@1.7.4: - resolution: {integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==} + mlly@1.8.2: + resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} ms@2.1.3: - resolution: {integrity: sha1-V0yBOM4dK1hh8LRFedut1gxmFbI=} + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} muggle-string@0.4.1: resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} @@ -1909,7 +2219,7 @@ packages: hasBin: true natural-compare@1.4.0: - resolution: {integrity: sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=} + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} @@ -1917,8 +2227,8 @@ packages: node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} - node-releases@2.0.19: - resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + node-releases@2.0.36: + resolution: {integrity: sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==} nopt@7.2.1: resolution: {integrity: sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==} @@ -1937,8 +2247,8 @@ packages: nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} - nwsapi@2.2.20: - resolution: {integrity: sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==} + nwsapi@2.2.23: + resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==} object-inspect@1.13.4: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} @@ -1948,50 +2258,50 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} + orderedmap@2.1.1: + resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==} + oxlint@0.16.12: resolution: {integrity: sha512-1oN3P9bzE90zkbjLTc+uICVLwSR+eQaDaYVipS0BtmtmEd3ccQue0y7npCinb35YqKzIv1LZxhoU9nm5fgmQuw==} engines: {node: '>=8.*'} hasBin: true p-limit@3.1.0: - resolution: {integrity: sha1-4drMvnjQ0TiMoYxk/qOOPlfjcGs=} + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} p-locate@5.0.0: - resolution: {integrity: sha1-g8gxXGeFAF470CGDlBHJ4RDm2DQ=} + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - package-manager-detector@1.3.0: - resolution: {integrity: sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==} + package-manager-detector@1.6.0: + resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} parent-module@1.0.1: - resolution: {integrity: sha1-aR0nCeeMefrjoVZiJFLQB2LKqqI=} + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} parse5@7.3.0: resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} path-browserify@1.0.1: - resolution: {integrity: sha1-2YRUqcN1PVeQhg8W9ohnueRr4f0=} + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} path-exists@4.0.0: - resolution: {integrity: sha1-UTvb4tO5XXdi6METfvoZXGxhtbM=} + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} path-key@3.1.1: - resolution: {integrity: sha1-WB9q3mWMu6ZaDTOA3ndTKVBU83U=} + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} path-scurry@1.11.1: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} - pathe@1.1.2: - resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} - pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -2005,12 +2315,12 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} engines: {node: '>=8.6'} - picomatch@4.0.2: - resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} pidtree@0.6.0: @@ -2030,52 +2340,111 @@ packages: pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} - pkg-types@2.1.1: - resolution: {integrity: sha512-eY0QFb6eSwc9+0d/5D2lFFUq+A3n3QNGSy/X2Nvp+6MfzGw2u6EbA7S80actgjY1lkvvI0pqB+a4hioMh443Ew==} + pkg-types@2.3.0: + resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} postcss-selector-parser@6.1.2: resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} engines: {node: '>=4'} - postcss@8.5.6: - resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + postcss@8.5.8: + resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} engines: {node: ^10 || ^12 || >=14} prelude-ls@1.2.1: - resolution: {integrity: sha1-3rxkidem5rDnYRiIzsiAM30xY5Y=} + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - prettier-linter-helpers@1.0.0: - resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + prettier-linter-helpers@1.0.1: + resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==} engines: {node: '>=6.0.0'} - prettier@3.6.2: - resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} + prettier@3.8.1: + resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} engines: {node: '>=14'} hasBin: true + prosemirror-changeset@2.4.0: + resolution: {integrity: sha512-LvqH2v7Q2SF6yxatuPP2e8vSUKS/L+xAU7dPDC4RMyHMhZoGDfBC74mYuyYF4gLqOEG758wajtyhNnsTkuhvng==} + + prosemirror-collab@1.3.1: + resolution: {integrity: sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==} + + prosemirror-commands@1.7.1: + resolution: {integrity: sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==} + + prosemirror-dropcursor@1.8.2: + resolution: {integrity: sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==} + + prosemirror-gapcursor@1.4.1: + resolution: {integrity: sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==} + + prosemirror-history@1.5.0: + resolution: {integrity: sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==} + + prosemirror-inputrules@1.5.1: + resolution: {integrity: sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==} + + prosemirror-keymap@1.2.3: + resolution: {integrity: sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==} + + prosemirror-markdown@1.13.4: + resolution: {integrity: sha512-D98dm4cQ3Hs6EmjK500TdAOew4Z03EV71ajEFiWra3Upr7diytJsjF4mPV2dW+eK5uNectiRj0xFxYI9NLXDbw==} + + prosemirror-menu@1.3.0: + resolution: {integrity: sha512-TImyPXCHPcDsSka2/lwJ6WjTASr4re/qWq1yoTTuLOqfXucwF6VcRa2LWCkM/EyTD1UO3CUwiH8qURJoWJRxwg==} + + prosemirror-model@1.25.4: + resolution: {integrity: sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==} + + prosemirror-schema-basic@1.2.4: + resolution: {integrity: sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==} + + prosemirror-schema-list@1.5.1: + resolution: {integrity: sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==} + + prosemirror-state@1.4.4: + resolution: {integrity: sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==} + + prosemirror-tables@1.8.5: + resolution: {integrity: sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==} + + prosemirror-trailing-node@3.0.0: + resolution: {integrity: sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==} + peerDependencies: + prosemirror-model: ^1.22.1 + prosemirror-state: ^1.4.2 + prosemirror-view: ^1.33.8 + + prosemirror-transform@1.11.0: + resolution: {integrity: sha512-4I7Ce4KpygXb9bkiPS3hTEk4dSHorfRw8uI0pE8IhxlK2GXsqv5tIA7JUSxtSu7u8APVOTtbUBxTmnHIxVkIJw==} + + prosemirror-view@1.41.7: + resolution: {integrity: sha512-jUwKNCEIGiqdvhlS91/2QAg21e4dfU5bH2iwmSDQeosXJgKF7smG0YSplOWK0cjSNgIqXe7VXqo7EIfUFJdt3w==} + proto-list@1.2.4: - resolution: {integrity: sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=} + resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} proxy-from-env@1.1.0: - resolution: {integrity: sha1-4QLxbKNVQkhldV0sno6k8k1Yw+I=} + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + punycode.js@2.3.1: + resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} + engines: {node: '>=6'} punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - qs@6.14.0: - resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} + qs@6.15.0: + resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==} engines: {node: '>=0.6'} - quansync@0.2.10: - resolution: {integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==} + quansync@0.2.11: + resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} queue-microtask@1.2.3: - resolution: {integrity: sha1-SSkii7xyTfrEPg77BYyve2z7YkM=} - - randombytes@2.1.0: - resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} read-package-json-fast@4.0.0: resolution: {integrity: sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg==} @@ -2085,15 +2454,15 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} - reduce-configs@1.1.0: - resolution: {integrity: sha512-DQxy6liNadHfrLahZR7lMdc227NYVaQZhY5FMsxLEjX8X0SCuH+ESHSLCoz2yDZFq1/CLMDOAHdsEHwOEXKtvg==} + reduce-configs@1.1.1: + resolution: {integrity: sha512-EYtsVGAQarE8daT54cnaY1PIknF2VB78ug6Zre2rs36EsJfC40EG6hmTU2A2P1ZuXnKAt2KI0fzOGHcX7wzdPw==} require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} resolve-from@4.0.0: - resolution: {integrity: sha1-SrzYUq0y3Xuqv+m0DgCjbbXzkuY=} + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} reusify@1.1.0: @@ -2103,178 +2472,170 @@ packages: rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - rollup@4.44.1: - resolution: {integrity: sha512-x8H8aPvD+xbl0Do8oez5f5o8eMS3trfCghc4HhLAnCkj7Vl0d1JWGs0UF/D886zLW2rOj2QymV/JcSSsw+XDNg==} + rollup@4.60.0: + resolution: {integrity: sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rope-sequence@1.3.4: + resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} + rrweb-cssom@0.8.0: resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} run-parallel@1.2.0: - resolution: {integrity: sha1-ZtE2jae9+SHrnZW9GpIp5/IaQ+4=} + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - safer-buffer@2.1.2: - resolution: {integrity: sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo=} + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + sass-embedded-all-unknown@1.98.0: + resolution: {integrity: sha512-6n4RyK7/1mhdfYvpP3CClS3fGoYqDvRmLClCESS6I7+SAzqjxvGG6u5Fo+cb1nrPNbbilgbM4QKdgcgWHO9NCA==} + cpu: ['!arm', '!arm64', '!riscv64', '!x64'] - sass-embedded-android-arm64@1.89.0: - resolution: {integrity: sha512-pr4R3p5R+Ul9ZA5nzYbBJQFJXW6dMGzgpNBhmaToYDgDhmNX5kg0mZAUlGLHvisLdTiR6oEfDDr9QI6tnD2nqA==} + sass-embedded-android-arm64@1.98.0: + resolution: {integrity: sha512-M9Ra98A6vYJHpwhoC/5EuH1eOshQ9ZyNwC8XifUDSbRl/cGeQceT1NReR9wFj3L7s1pIbmes1vMmaY2np0uAKQ==} engines: {node: '>=14.0.0'} cpu: [arm64] os: [android] - sass-embedded-android-arm@1.89.0: - resolution: {integrity: sha512-s6jxkEZQQrtyIGZX6Sbcu7tEixFG2VkqFgrX11flm/jZex7KaxnZtFace+wnYAgHqzzYpx0kNzJUpT+GXxm8CA==} + sass-embedded-android-arm@1.98.0: + resolution: {integrity: sha512-LjGiMhHgu7VL1n7EJxTCre1x14bUsWd9d3dnkS2rku003IWOI/fxc7OXgaKagoVzok1kv09rzO3vFXJR5ZeONQ==} engines: {node: '>=14.0.0'} cpu: [arm] os: [android] - sass-embedded-android-ia32@1.89.0: - resolution: {integrity: sha512-GoNnNGYmp1F0ZMHqQbAurlQsjBMZKtDd5H60Ruq86uQFdnuNqQ9wHKJsJABxMnjfAn60IjefytM5PYTMcAmbfA==} - engines: {node: '>=14.0.0'} - cpu: [ia32] - os: [android] - - sass-embedded-android-riscv64@1.89.0: - resolution: {integrity: sha512-di+i4KkKAWTNksaQYTqBEERv46qV/tvv14TPswEfak7vcTQ2pj2mvV4KGjLYfU2LqRkX/NTXix9KFthrzFN51Q==} + sass-embedded-android-riscv64@1.98.0: + resolution: {integrity: sha512-WPe+0NbaJIZE1fq/RfCZANMeIgmy83x4f+SvFOG7LhUthHpZWcOcrPTsCKKmN3xMT3iw+4DXvqTYOCYGRL3hcQ==} engines: {node: '>=14.0.0'} cpu: [riscv64] os: [android] - sass-embedded-android-x64@1.89.0: - resolution: {integrity: sha512-1cRRDAnmAS1wLaxfFf6PCHu9sKW8FNxdM7ZkanwxO9mztrCu/uvfqTmaurY9+RaKvPus7sGYFp46/TNtl/wRjg==} + sass-embedded-android-x64@1.98.0: + resolution: {integrity: sha512-zrD25dT7OHPEgLWuPEByybnIfx4rnCtfge4clBgjZdZ3lF6E7qNLRBtSBmoFflh6Vg0RlEjJo5VlpnTMBM5MQQ==} engines: {node: '>=14.0.0'} cpu: [x64] os: [android] - sass-embedded-darwin-arm64@1.89.0: - resolution: {integrity: sha512-EUNUzI0UkbQ6dASPyf09S3x7fNT54PjyD594ZGTY14Yh4qTuacIj27ckLmreAJNNu5QxlbhyYuOtz+XN5bMMxA==} + sass-embedded-darwin-arm64@1.98.0: + resolution: {integrity: sha512-cgr1z9rBnCdMf8K+JabIaYd9Rag2OJi5mjq08XJfbJGMZV/TA6hFJCLGkr5/+ZOn4/geTM5/3aSfQ8z5EIJAOg==} engines: {node: '>=14.0.0'} cpu: [arm64] os: [darwin] - sass-embedded-darwin-x64@1.89.0: - resolution: {integrity: sha512-23R8zSuB31Fq/MYpmQ38UR2C26BsYb66VVpJgWmWl/N+sgv/+l9ECuSPMbYNgM3vb9TP9wk9dgL6KkiCS5tAyg==} + sass-embedded-darwin-x64@1.98.0: + resolution: {integrity: sha512-OLBOCs/NPeiMqTdOrMFbVHBQFj19GS3bSVSxIhcCq16ZyhouUkYJEZjxQgzv9SWA2q6Ki8GCqp4k6jMeUY9dcA==} engines: {node: '>=14.0.0'} cpu: [x64] os: [darwin] - sass-embedded-linux-arm64@1.89.0: - resolution: {integrity: sha512-g9Lp57qyx51ttKj0AN/edV43Hu1fBObvD7LpYwVfs6u3I95r0Adi90KujzNrUqXxJVmsfUwseY8kA8zvcRjhYA==} + sass-embedded-linux-arm64@1.98.0: + resolution: {integrity: sha512-axOE3t2MTBwCtkUCbrdM++Gj0gC0fdHJPrgzQ+q1WUmY9NoNMGqflBtk5mBZaWUeha2qYO3FawxCB8lctFwCtw==} engines: {node: '>=14.0.0'} cpu: [arm64] os: [linux] + libc: glibc - sass-embedded-linux-arm@1.89.0: - resolution: {integrity: sha512-KAzA1XD74d8/fiJXxVnLfFwfpmD2XqUJZz+DL6ZAPNLH1sb+yCP7brktaOyClDc/MBu61JERdHaJjIZhfX0Yqw==} + sass-embedded-linux-arm@1.98.0: + resolution: {integrity: sha512-03baQZCxVyEp8v1NWBRlzGYrmVT/LK7ZrHlF1piscGiGxwfdxoLXVuxsylx3qn/dD/4i/rh7Bzk7reK1br9jvQ==} engines: {node: '>=14.0.0'} cpu: [arm] os: [linux] + libc: glibc - sass-embedded-linux-ia32@1.89.0: - resolution: {integrity: sha512-5fxBeXyvBr3pb+vyrx9V6yd7QDRXkAPbwmFVVhjqshBABOXelLysEFea7xokh/tM8JAAQ4O8Ls3eW3Eojb477g==} - engines: {node: '>=14.0.0'} - cpu: [ia32] - os: [linux] - - sass-embedded-linux-musl-arm64@1.89.0: - resolution: {integrity: sha512-50oelrOtN64u15vJN9uJryIuT0+UPjyeoq0zdWbY8F7LM9294Wf+Idea+nqDUWDCj1MHndyPFmR1mjeuRouJhw==} + sass-embedded-linux-musl-arm64@1.98.0: + resolution: {integrity: sha512-LeqNxQA8y4opjhe68CcFvMzCSrBuJqYVFbwElEj9bagHXQHTp9xVPJRn6VcrC+0VLEDq13HVXMv7RslIuU0zmA==} engines: {node: '>=14.0.0'} cpu: [arm64] os: [linux] + libc: musl - sass-embedded-linux-musl-arm@1.89.0: - resolution: {integrity: sha512-0Q1JeEU4/tzH7fwAwarfIh+Swn3aXG/jPhVsZpbR1c1VzkeaPngmXdmLJcVXsdb35tjk84DuYcFtJlE1HYGw4Q==} + sass-embedded-linux-musl-arm@1.98.0: + resolution: {integrity: sha512-OBkjTDPYR4hSaueOGIM6FDpl9nt/VZwbSRpbNu9/eEJcxE8G/vynRugW8KRZmCFjPy8j/jkGBvvS+k9iOqKV3g==} engines: {node: '>=14.0.0'} cpu: [arm] os: [linux] + libc: musl - sass-embedded-linux-musl-ia32@1.89.0: - resolution: {integrity: sha512-ILWqpTd+0RdsSw977iVAJf4CLetIbcQgLQf17ycS1N4StZKVRZs1bBfZhg/f/HU/4p5HondPAwepgJepZZdnFA==} - engines: {node: '>=14.0.0'} - cpu: [ia32] - os: [linux] - - sass-embedded-linux-musl-riscv64@1.89.0: - resolution: {integrity: sha512-n2V+Tdjj7SAuiuElJYhWiHjjB1YU0cuFvL1/m5K+ecdNStfHFWIzvBT6/vzQnBOWjI4eZECNVuQ8GwGWCufZew==} + sass-embedded-linux-musl-riscv64@1.98.0: + resolution: {integrity: sha512-7w6hSuOHKt8FZsmjRb3iGSxEzM87fO9+M8nt5JIQYMhHTj5C+JY/vcske0v715HCVj5e1xyTnbGXf8FcASeAIw==} engines: {node: '>=14.0.0'} cpu: [riscv64] os: [linux] + libc: musl - sass-embedded-linux-musl-x64@1.89.0: - resolution: {integrity: sha512-KOHJdouBK3SLJKZLnFYzuxs3dn+6jaeO3p4p1JUYAcVfndcvh13Sg2sLGfOfpg7Og6ws2Nnqnx0CyL26jPJ7ag==} + sass-embedded-linux-musl-x64@1.98.0: + resolution: {integrity: sha512-QikNyDEJOVqPmxyCFkci8ZdCwEssdItfjQFJB+D+Uy5HFqcS5Lv3d3GxWNX/h1dSb23RPyQdQc267ok5SbEyJw==} engines: {node: '>=14.0.0'} cpu: [x64] os: [linux] + libc: musl - sass-embedded-linux-riscv64@1.89.0: - resolution: {integrity: sha512-0A/UWeKX6MYhVLWLkdX3NPKHO+mvIwzaf6TxGCy3vS3TODWaeDUeBhHShAr7YlOKv5xRGxf7Gx7FXCPV0mUyMA==} + sass-embedded-linux-riscv64@1.98.0: + resolution: {integrity: sha512-E7fNytc/v4xFBQKzgzBddV/jretA4ULAPO6XmtBiQu4zZBdBozuSxsQLe2+XXeb0X4S2GIl72V7IPABdqke/vA==} engines: {node: '>=14.0.0'} cpu: [riscv64] os: [linux] + libc: glibc - sass-embedded-linux-x64@1.89.0: - resolution: {integrity: sha512-dRBoOFPDWctHPYK3hTk3YzyX/icVrXiw7oOjbtpaDr6JooqIWBe16FslkWyvQzdmfOFy80raKVjgoqT7DsznkQ==} + sass-embedded-linux-x64@1.98.0: + resolution: {integrity: sha512-VsvP0t/uw00mMNPv3vwyYKUrFbqzxQHnRMO+bHdAMjvLw4NFf6mscpym9Bzf+NXwi1ZNKnB6DtXjmcpcvqFqYg==} engines: {node: '>=14.0.0'} cpu: [x64] os: [linux] + libc: glibc - sass-embedded-win32-arm64@1.89.0: - resolution: {integrity: sha512-RnlVZ14hC/W7ubzvhqnbGfjU5PFNoFP/y5qycgCy+Mezb0IKbWvZ2Lyzux8TbL3OIjOikkNpfXoNQrX706WLAA==} - engines: {node: '>=14.0.0'} - cpu: [arm64] - os: [win32] + sass-embedded-unknown-all@1.98.0: + resolution: {integrity: sha512-C4MMzcAo3oEDQnW7L8SBgB9F2Fq5qHPnaYTZRMOH3Mp/7kM4OooBInXpCiiFjLnjY95hzP4KyctVx0uYR6MYlQ==} + os: ['!android', '!darwin', '!linux', '!win32'] - sass-embedded-win32-ia32@1.89.0: - resolution: {integrity: sha512-eFe9VMNG+90nuoE3eXDy+38+uEHGf7xcqalq5+0PVZfR+H9RlaEbvIUNflZV94+LOH8Jb4lrfuekhHgWDJLfSg==} + sass-embedded-win32-arm64@1.98.0: + resolution: {integrity: sha512-nP/10xbAiPbhQkMr3zQfXE4TuOxPzWRQe1Hgbi90jv2R4TbzbqQTuZVOaJf7KOAN4L2Bo6XCTRjK5XkVnwZuwQ==} engines: {node: '>=14.0.0'} - cpu: [ia32] + cpu: [arm64] os: [win32] - sass-embedded-win32-x64@1.89.0: - resolution: {integrity: sha512-AaGpr5R6MLCuSvkvDdRq49ebifwLcuGPk0/10hbYw9nh3jpy2/CylYubQpIpR4yPcuD1wFwFqufTXC3HJYGb0g==} + sass-embedded-win32-x64@1.98.0: + resolution: {integrity: sha512-/lbrVsfbcbdZQ5SJCWcV0NVPd6YRs+FtAnfedp4WbCkO/ZO7Zt/58MvI4X2BVpRY/Nt5ZBo1/7v2gYcQ+J4svQ==} engines: {node: '>=14.0.0'} cpu: [x64] os: [win32] - sass-embedded@1.89.0: - resolution: {integrity: sha512-EDrK1el9zdgJFpocCGlxatDWaP18tJBWoM1hxzo2KJBvjdmBichXI6O6KlQrigvQPO3uJ8DfmFmAAx7s7CG6uw==} + sass-embedded@1.98.0: + resolution: {integrity: sha512-Do7u6iRb6K+lrllcTkB1BXcHwOxcKe3rEfOF/GcCLE2w3WpddakRAosJOHFUR37DpsvimQXEt5abs3NzUjEIqg==} engines: {node: '>=16.0.0'} hasBin: true - sass@1.89.2: - resolution: {integrity: sha512-xCmtksBKd/jdJ9Bt9p7nPKiuqrlBMBuuGkQlkhZjjQk3Ty48lv93k5Dq6OPkKt4XwxDJ7tvlfrTa1MPA9bf+QA==} + sass@1.98.0: + resolution: {integrity: sha512-+4N/u9dZ4PrgzGgPlKnaaRQx64RO0JBKs9sDhQ2pLgN6JQZ25uPQZKQYaBJU48Kd5BxgXoJ4e09Dq7nMcOUW3A==} engines: {node: '>=14.0.0'} hasBin: true saxes@6.0.0: - resolution: {integrity: sha1-/ltKR2jfTxSiAbG6amXB89mYjMU=} + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} engines: {node: '>=v12.22.7'} - schema-utils@4.3.2: - resolution: {integrity: sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==} + schema-utils@4.3.3: + resolution: {integrity: sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==} engines: {node: '>= 10.13.0'} - semver@7.7.2: - resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + scroll-into-view-if-needed@3.1.0: + resolution: {integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==} + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} engines: {node: '>=10'} hasBin: true - serialize-javascript@6.0.2: - resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} - shebang-command@2.0.0: - resolution: {integrity: sha1-zNCvT4g1+9wmW4JGGq8MNmY/NOo=} + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} shebang-regex@3.0.0: - resolution: {integrity: sha1-rhbxZE2HPsrYQ7AwexQzYtTEIXI=} + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} shell-quote@1.8.3: @@ -2322,8 +2683,8 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - std-env@3.9.0: - resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} @@ -2337,50 +2698,50 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - strip-ansi@7.1.0: - resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} engines: {node: '>=12'} strip-json-comments@3.1.1: - resolution: {integrity: sha1-MfEoGzgyYwQ0gxwxDAHMzajL4AY=} + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - strip-literal@3.0.0: - resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==} + strip-literal@3.1.0: + resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} superjson@2.2.6: resolution: {integrity: sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==} engines: {node: '>=16'} supports-color@7.2.0: - resolution: {integrity: sha1-G33NyzK4E4gBs+R4umpRyqiWSNo=} + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} supports-color@8.1.1: - resolution: {integrity: sha1-zW/BfihQDP9WwbhsCn/UpUpzAFw=} + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} engines: {node: '>=10'} symbol-tree@3.2.4: - resolution: {integrity: sha1-QwY30ki6d+B4iDlR+5qg7tfGP6I=} + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} sync-child-process@1.0.2: resolution: {integrity: sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==} engines: {node: '>=16.0.0'} - sync-message-port@1.1.3: - resolution: {integrity: sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==} + sync-message-port@1.2.0: + resolution: {integrity: sha512-gAQ9qrUN/UCypHtGFbbe7Rc/f9bzO88IwrG8TDo/aMKAApKyD6E3W4Cm0EfhfBb6Z6SKt59tTCTfD+n1xmAvMg==} engines: {node: '>=16.0.0'} - synckit@0.11.8: - resolution: {integrity: sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==} + synckit@0.11.12: + resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==} engines: {node: ^14.18.0 || >=16.0.0} - tapable@2.2.2: - resolution: {integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==} + tapable@2.3.2: + resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==} engines: {node: '>=6'} - terser-webpack-plugin@5.3.14: - resolution: {integrity: sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==} + terser-webpack-plugin@5.4.0: + resolution: {integrity: sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==} engines: {node: '>= 10.13.0'} peerDependencies: '@swc/core': '*' @@ -2395,8 +2756,8 @@ packages: uglify-js: optional: true - terser@5.43.1: - resolution: {integrity: sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==} + terser@5.46.1: + resolution: {integrity: sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==} engines: {node: '>=10'} hasBin: true @@ -2406,11 +2767,12 @@ packages: tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - tinyexec@1.0.1: - resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==} + tinyexec@1.0.4: + resolution: {integrity: sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==} + engines: {node: '>=18'} - tinyglobby@0.2.14: - resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} tinypool@1.1.1: @@ -2421,8 +2783,8 @@ packages: resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} engines: {node: '>=14.0.0'} - tinyspy@4.0.3: - resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==} + tinyspy@4.0.4: + resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} engines: {node: '>=14.0.0'} tldts-core@6.1.86: @@ -2433,7 +2795,7 @@ packages: hasBin: true to-regex-range@5.0.1: - resolution: {integrity: sha1-FkjESq58jZiKMmAY7XL1tN0DkuQ=} + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} tough-cookie@5.1.2: @@ -2444,8 +2806,8 @@ packages: resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} engines: {node: '>=18'} - ts-api-utils@2.1.0: - resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} engines: {node: '>=18.12'} peerDependencies: typescript: '>=4.8.4' @@ -2454,29 +2816,32 @@ packages: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} type-check@0.4.0: - resolution: {integrity: sha1-B7ggO/pwVsBlcFDjzNLDdzC6uPE=} + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - typescript-eslint@8.35.0: - resolution: {integrity: sha512-uEnz70b7kBz6eg/j0Czy6K5NivaYopgxRjsnAJ2Fx5oTLo3wefTHIbL7AkQr1+7tJCRVpTs/wiM8JR/11Loq9A==} + typescript-eslint@8.57.2: + resolution: {integrity: sha512-VEPQ0iPgWO/sBaZOU1xo4nuNdODVOajPnTIbog2GKYr31nIlZ0fWPoCQgGfF3ETyBl1vn63F/p50Um9Z4J8O8A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' typescript@5.8.3: resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} engines: {node: '>=14.17'} hasBin: true - ufo@1.6.1: - resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} + uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + + ufo@1.6.3: + resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - unplugin-icons@22.1.0: - resolution: {integrity: sha512-ect2ZNtk1Zgwb0NVHd0C1IDW/MV+Jk/xaq4t8o6rYdVS3+L660ZdD5kTSQZvsgdwCvquRw+/wYn75hsweRjoIA==} + unplugin-icons@22.5.0: + resolution: {integrity: sha512-MBlMtT5RuMYZy4TZgqUL2OTtOdTUVsS1Mhj6G1pEzMlFJlEnq6mhUfoIt45gBWxHcsOdXJDWLg3pRZ+YmvAVWQ==} peerDependencies: '@svgr/core': '>=7.0.0' '@svgx/core': ^1.0.1 @@ -2498,21 +2863,21 @@ packages: vue-template-es2015-compiler: optional: true - unplugin@2.3.5: - resolution: {integrity: sha512-RyWSb5AHmGtjjNQ6gIlA67sHOsWpsbWpwDokLwTcejVdOjEkJZh7QKu14J00gDDVSh8kGH4KYC/TNBceXFZhtw==} + unplugin@2.3.11: + resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} engines: {node: '>=18.12.0'} - update-browserslist-db@1.1.3: - resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' uri-js@4.4.1: - resolution: {integrity: sha1-mxpSWVIlhZ5V9mnZKPiMbFfyp34=} + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} util-deprecate@1.0.2: - resolution: {integrity: sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=} + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} varint@6.0.0: resolution: {integrity: sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==} @@ -2522,8 +2887,8 @@ packages: engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true - vite@7.0.0: - resolution: {integrity: sha512-ixXJB1YRgDIw2OszKQS9WxGHKwLdCsbQNkpJN171udl6szi/rIySHL6/Os3s2+oE4P/FLD4dxg4mD7Wust+u5g==} + vite@7.3.1: + resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -2593,14 +2958,14 @@ packages: vscode-uri@3.1.0: resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} - vue-component-type-helpers@2.2.10: - resolution: {integrity: sha512-iDUO7uQK+Sab2tYuiP9D1oLujCWlhHELHMgV/cB13cuGbG4qwkLHvtfWb6FzvxrIOPDnU0oHsz2MlQjhYDeaHA==} + vue-component-type-helpers@2.2.12: + resolution: {integrity: sha512-YbGqHZ5/eW4SnkPNR44mKVc6ZKQoRs/Rux1sxC6rdwXb4qpbOSYfDr9DsTHolOTGmIKgM9j141mZbBeg05R1pw==} - vue-eslint-parser@10.1.4: - resolution: {integrity: sha512-EIZvCukIEMHEb3mxOKemtvWR1fcUAdWWAgkfyjmRHzvyhrZvBvH9oz69+thDIWhGiIQjZnPkCn8yHqvjM+a9eg==} + vue-eslint-parser@10.4.0: + resolution: {integrity: sha512-Vxi9pJdbN3ZnVGLODVtZ7y4Y2kzAAE2Cm0CZ3ZDRvydVYxZ6VrnBhLikBsRS+dpwj4Jv4UCv21PTEwF5rQ9WXg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.57.0 || ^9.0.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 vue-loader@17.4.2: resolution: {integrity: sha512-yTKOA4R/VN4jqjw4y5HrynFL8AK0Z3/Jt7eOJXEitsm0GMRHDBjCfCiuTiLP7OESvsZYo2pATCWhDqxC5ZrM6w==} @@ -2624,41 +2989,44 @@ packages: peerDependencies: vue: ^3.5.0 - vue-tsc@2.2.10: - resolution: {integrity: sha512-jWZ1xSaNbabEV3whpIDMbjVSVawjAyW+x1n3JeGQo7S0uv2n9F/JMgWW90tGWNFRKya4YwKMZgCtr0vRAM7DeQ==} + vue-tsc@2.2.12: + resolution: {integrity: sha512-P7OP77b2h/Pmk+lZdJ0YWs+5tJ6J2+uOQPo7tlBnY44QqQSPYvS0qVT4wqDJgwrZaLe47etJLLQRFia71GYITw==} hasBin: true peerDependencies: typescript: '>=5.0.0' - vue@3.5.17: - resolution: {integrity: sha512-LbHV3xPN9BeljML+Xctq4lbz2lVHCR6DtbpTf5XIO6gugpXUN49j2QQPcMj086r9+AkJ0FfUT8xjulKKBkkr9g==} + vue@3.5.30: + resolution: {integrity: sha512-hTHLc6VNZyzzEH/l7PFGjpcTvUgiaPK5mdLkbjrTeWSRcEfxFrv56g/XckIYlE9ckuobsdwqd5mk2g1sBkMewg==} peerDependencies: typescript: '*' peerDependenciesMeta: typescript: optional: true + w3c-keyname@2.2.8: + resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + w3c-xmlserializer@5.0.0: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} - watchpack@2.4.4: - resolution: {integrity: sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==} + watchpack@2.5.1: + resolution: {integrity: sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==} engines: {node: '>=10.13.0'} webidl-conversions@7.0.0: - resolution: {integrity: sha1-JWtOGIK+feu/AdBfCqIDl3jqCAo=} + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} engines: {node: '>=12'} - webpack-sources@3.3.3: - resolution: {integrity: sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==} + webpack-sources@3.3.4: + resolution: {integrity: sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==} engines: {node: '>=10.13.0'} webpack-virtual-modules@0.6.2: resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} - webpack@5.99.9: - resolution: {integrity: sha512-brOPwM3JnmOa+7kd3NsmOUOwbDAj8FT9xDsG3IW0MgbN9yZV7Oi/s/+MNQ/EcSMqw7qfoRyXPoeEWT8zLVdVGg==} + webpack@5.105.4: + resolution: {integrity: sha512-jTywjboN9aHxFlToqb0K0Zs9SbBoW4zRUlGzI2tYNxVYcEi/IPpn+Xi4ye5jTLvX2YeLuic/IvxNot+Q1jMoOw==} engines: {node: '>=10.13.0'} hasBin: true peerDependencies: @@ -2670,6 +3038,7 @@ packages: whatwg-encoding@3.1.1: resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} engines: {node: '>=18'} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation whatwg-mimetype@4.0.0: resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} @@ -2680,7 +3049,7 @@ packages: engines: {node: '>=18'} which@2.0.2: - resolution: {integrity: sha1-fGqN0KY2oDJ+ELWckobu6T8/UbE=} + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} hasBin: true @@ -2706,8 +3075,8 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} - ws@8.18.2: - resolution: {integrity: sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==} + ws@8.20.0: + resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -2719,7 +3088,7 @@ packages: optional: true xml-name-validator@4.0.0: - resolution: {integrity: sha1-eaAG4uYxSahgDxVDDwpHJdFSSDU=} + resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} engines: {node: '>=12'} xml-name-validator@5.0.0: @@ -2727,54 +3096,62 @@ packages: engines: {node: '>=18'} xmlchars@2.2.0: - resolution: {integrity: sha1-Bg/hvLf5x2/ioX24apvDq4lCEMs=} + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + + y-protocols@1.0.7: + resolution: {integrity: sha512-YSVsLoXxO67J6eE/nV4AtFtT3QEotZf5sK5BHxFBXso7VDUT3Tx07IfA6hsu5Q5OmBdMkQVmFZ9QOA7fikWvnw==} + engines: {node: '>=16.0.0', npm: '>=8.0.0'} + peerDependencies: + yjs: ^13.0.0 + + yjs@13.6.30: + resolution: {integrity: sha512-vv/9h42eCMC81ZHDFswuu/MKzkl/vyq1BhaNGfHyOonwlG4CJbQF4oiBBJPvfdeCt/PlVDWh7Nov9D34YY09uQ==} + engines: {node: '>=16.0.0', npm: '>=8.0.0'} yocto-queue@0.1.0: - resolution: {integrity: sha1-ApTrPe4FAo0x7hpfosVWpqrxChs=} + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} snapshots: '@antfu/install-pkg@1.1.0': dependencies: - package-manager-detector: 1.3.0 - tinyexec: 1.0.1 - - '@antfu/utils@8.1.1': {} + package-manager-detector: 1.6.0 + tinyexec: 1.0.4 '@asamuzakjp/css-color@3.2.0': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - '@csstools/css-color-parser': 3.0.10(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 lru-cache: 10.4.3 '@babel/helper-string-parser@7.27.1': {} - '@babel/helper-validator-identifier@7.27.1': {} + '@babel/helper-validator-identifier@7.28.5': {} - '@babel/parser@7.27.7': + '@babel/parser@7.29.2': dependencies: - '@babel/types': 7.27.7 + '@babel/types': 7.29.0 - '@babel/types@7.27.7': + '@babel/types@7.29.0': dependencies: '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 - '@bufbuild/protobuf@2.5.2': {} + '@bufbuild/protobuf@2.11.0': {} - '@csstools/color-helpers@5.0.2': {} + '@csstools/color-helpers@5.1.0': {} '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': dependencies: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/css-color-parser@3.0.10(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': dependencies: - '@csstools/color-helpers': 5.0.2 + '@csstools/color-helpers': 5.1.0 '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 @@ -2785,282 +3162,335 @@ snapshots: '@csstools/css-tokenizer@3.0.4': {} - '@emnapi/core@1.4.3': + '@emnapi/core@1.9.1': dependencies: - '@emnapi/wasi-threads': 1.0.2 + '@emnapi/wasi-threads': 1.2.0 tslib: 2.8.1 optional: true - '@emnapi/runtime@1.4.3': + '@emnapi/runtime@1.9.1': dependencies: tslib: 2.8.1 optional: true - '@emnapi/wasi-threads@1.0.2': + '@emnapi/wasi-threads@1.2.0': dependencies: tslib: 2.8.1 optional: true - '@esbuild/aix-ppc64@0.25.5': + '@esbuild/aix-ppc64@0.27.4': optional: true - '@esbuild/android-arm64@0.25.5': + '@esbuild/android-arm64@0.27.4': optional: true - '@esbuild/android-arm@0.25.5': + '@esbuild/android-arm@0.27.4': optional: true - '@esbuild/android-x64@0.25.5': + '@esbuild/android-x64@0.27.4': optional: true - '@esbuild/darwin-arm64@0.25.5': + '@esbuild/darwin-arm64@0.27.4': optional: true - '@esbuild/darwin-x64@0.25.5': + '@esbuild/darwin-x64@0.27.4': optional: true - '@esbuild/freebsd-arm64@0.25.5': + '@esbuild/freebsd-arm64@0.27.4': optional: true - '@esbuild/freebsd-x64@0.25.5': + '@esbuild/freebsd-x64@0.27.4': optional: true - '@esbuild/linux-arm64@0.25.5': + '@esbuild/linux-arm64@0.27.4': optional: true - '@esbuild/linux-arm@0.25.5': + '@esbuild/linux-arm@0.27.4': optional: true - '@esbuild/linux-ia32@0.25.5': + '@esbuild/linux-ia32@0.27.4': optional: true - '@esbuild/linux-loong64@0.25.5': + '@esbuild/linux-loong64@0.27.4': optional: true - '@esbuild/linux-mips64el@0.25.5': + '@esbuild/linux-mips64el@0.27.4': optional: true - '@esbuild/linux-ppc64@0.25.5': + '@esbuild/linux-ppc64@0.27.4': optional: true - '@esbuild/linux-riscv64@0.25.5': + '@esbuild/linux-riscv64@0.27.4': optional: true - '@esbuild/linux-s390x@0.25.5': + '@esbuild/linux-s390x@0.27.4': optional: true - '@esbuild/linux-x64@0.25.5': + '@esbuild/linux-x64@0.27.4': optional: true - '@esbuild/netbsd-arm64@0.25.5': + '@esbuild/netbsd-arm64@0.27.4': optional: true - '@esbuild/netbsd-x64@0.25.5': + '@esbuild/netbsd-x64@0.27.4': optional: true - '@esbuild/openbsd-arm64@0.25.5': + '@esbuild/openbsd-arm64@0.27.4': optional: true - '@esbuild/openbsd-x64@0.25.5': + '@esbuild/openbsd-x64@0.27.4': optional: true - '@esbuild/sunos-x64@0.25.5': + '@esbuild/openharmony-arm64@0.27.4': optional: true - '@esbuild/win32-arm64@0.25.5': + '@esbuild/sunos-x64@0.27.4': optional: true - '@esbuild/win32-ia32@0.25.5': + '@esbuild/win32-arm64@0.27.4': optional: true - '@esbuild/win32-x64@0.25.5': + '@esbuild/win32-ia32@0.27.4': optional: true - '@eslint-community/eslint-utils@4.7.0(eslint@9.29.0(jiti@2.4.2))': + '@esbuild/win32-x64@0.27.4': + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@2.6.1))': dependencies: - eslint: 9.29.0(jiti@2.4.2) + eslint: 9.39.4(jiti@2.6.1) eslint-visitor-keys: 3.4.3 - '@eslint-community/regexpp@4.12.1': {} + '@eslint-community/regexpp@4.12.2': {} - '@eslint/config-array@0.20.1': + '@eslint/config-array@0.21.2': dependencies: - '@eslint/object-schema': 2.1.6 - debug: 4.4.1 - minimatch: 3.1.2 + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.5 transitivePeerDependencies: - supports-color - '@eslint/config-helpers@0.2.3': {} - - '@eslint/core@0.14.0': + '@eslint/config-helpers@0.4.2': dependencies: - '@types/json-schema': 7.0.15 + '@eslint/core': 0.17.0 - '@eslint/core@0.15.1': + '@eslint/core@0.17.0': dependencies: '@types/json-schema': 7.0.15 - '@eslint/eslintrc@3.3.1': + '@eslint/eslintrc@3.3.5': dependencies: - ajv: 6.12.6 - debug: 4.4.1 + ajv: 6.14.0 + debug: 4.4.3 espree: 10.4.0 globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.1 - js-yaml: 4.1.0 - minimatch: 3.1.2 + js-yaml: 4.1.1 + minimatch: 3.1.5 strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color - '@eslint/js@9.29.0': {} + '@eslint/js@9.39.4': {} - '@eslint/object-schema@2.1.6': {} + '@eslint/object-schema@2.1.7': {} - '@eslint/plugin-kit@0.3.3': + '@eslint/plugin-kit@0.4.1': dependencies: - '@eslint/core': 0.15.1 + '@eslint/core': 0.17.0 levn: 0.4.1 - '@floating-ui/core@1.7.1': + '@floating-ui/core@1.7.5': dependencies: - '@floating-ui/utils': 0.2.9 + '@floating-ui/utils': 0.2.11 '@floating-ui/dom@1.1.1': dependencies: - '@floating-ui/core': 1.7.1 + '@floating-ui/core': 1.7.5 + + '@floating-ui/dom@1.7.6': + dependencies: + '@floating-ui/core': 1.7.5 + '@floating-ui/utils': 0.2.11 - '@floating-ui/utils@0.2.9': {} + '@floating-ui/utils@0.2.11': {} - '@halo-dev/api-client@2.22.0(axios@1.10.0)': + '@halo-dev/api-client@2.23.0(axios@1.13.6)': dependencies: - axios: 1.10.0 - qs: 6.14.0 + axios: 1.13.6 + qs: 6.15.0 - '@halo-dev/components@2.22.0(vue-router@4.6.4(vue@3.5.17(typescript@5.8.3)))(vue@3.5.17(typescript@5.8.3))': + '@halo-dev/components@2.23.0(vue-router@4.6.4(vue@3.5.30(typescript@5.8.3)))(vue@3.5.30(typescript@5.8.3))': dependencies: - floating-vue: 5.2.2(vue@3.5.17(typescript@5.8.3)) - vue: 3.5.17(typescript@5.8.3) - vue-router: 4.6.4(vue@3.5.17(typescript@5.8.3)) + floating-vue: 5.2.2(vue@3.5.30(typescript@5.8.3)) + vue: 3.5.30(typescript@5.8.3) + vue-router: 4.6.4(vue@3.5.30(typescript@5.8.3)) transitivePeerDependencies: - '@nuxt/kit' - '@halo-dev/ui-plugin-bundler-kit@2.22.0(@rsbuild/core@1.4.0)(@rsbuild/plugin-vue@1.0.7(@rsbuild/core@1.4.0)(@vue/compiler-sfc@3.5.17)(vue@3.5.17(typescript@5.8.3)))(@vitejs/plugin-vue@5.2.4(vite@7.0.0(@types/node@20.19.1)(jiti@2.4.2)(sass-embedded@1.89.0)(sass@1.89.2)(terser@5.43.1))(vue@3.5.17(typescript@5.8.3)))(axios@1.10.0)(vite@7.0.0(@types/node@20.19.1)(jiti@2.4.2)(sass-embedded@1.89.0)(sass@1.89.2)(terser@5.43.1))': + '@halo-dev/richtext-editor@2.23.0(@tiptap/extension-collaboration@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5)(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.7)(y-protocols@1.0.7(yjs@13.6.30))(yjs@13.6.30))(yjs@13.6.30))(@tiptap/extension-node-range@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5))(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.7)(y-protocols@1.0.7(yjs@13.6.30))(yjs@13.6.30))(axios@1.13.6)(vue-router@4.6.4(vue@3.5.30(typescript@5.8.3)))(vue@3.5.30(typescript@5.8.3))': + dependencies: + '@floating-ui/dom': 1.7.6 + '@halo-dev/api-client': 2.23.0(axios@1.13.6) + '@halo-dev/components': 2.23.0(vue-router@4.6.4(vue@3.5.30(typescript@5.8.3)))(vue@3.5.30(typescript@5.8.3)) + '@halo-dev/ui-shared': 2.23.0(@tiptap/extension-collaboration@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5)(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.7)(y-protocols@1.0.7(yjs@13.6.30))(yjs@13.6.30))(yjs@13.6.30))(@tiptap/extension-node-range@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5))(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.7)(y-protocols@1.0.7(yjs@13.6.30))(yjs@13.6.30))(axios@1.13.6)(vue-router@4.6.4(vue@3.5.30(typescript@5.8.3)))(vue@3.5.30(typescript@5.8.3)) + '@tiptap/core': 3.20.5(@tiptap/pm@3.20.5) + '@tiptap/extension-blockquote': 3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5)) + '@tiptap/extension-bold': 3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5)) + '@tiptap/extension-code': 3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5)) + '@tiptap/extension-code-block': 3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5) + '@tiptap/extension-color': 3.20.5(@tiptap/extension-text-style@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))) + '@tiptap/extension-details': 3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/extension-text-style@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5)))(@tiptap/pm@3.20.5) + '@tiptap/extension-document': 3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5)) + '@tiptap/extension-drag-handle': 3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/extension-collaboration@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5)(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.7)(y-protocols@1.0.7(yjs@13.6.30))(yjs@13.6.30))(yjs@13.6.30))(@tiptap/extension-node-range@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5)(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.7)(y-protocols@1.0.7(yjs@13.6.30))(yjs@13.6.30)) + '@tiptap/extension-drag-handle-vue-3': 3.20.5(@tiptap/extension-drag-handle@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/extension-collaboration@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5)(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.7)(y-protocols@1.0.7(yjs@13.6.30))(yjs@13.6.30))(yjs@13.6.30))(@tiptap/extension-node-range@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5)(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.7)(y-protocols@1.0.7(yjs@13.6.30))(yjs@13.6.30)))(@tiptap/pm@3.20.5)(@tiptap/vue-3@3.20.5(@floating-ui/dom@1.7.6)(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5)(vue@3.5.30(typescript@5.8.3)))(vue@3.5.30(typescript@5.8.3)) + '@tiptap/extension-hard-break': 3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5)) + '@tiptap/extension-heading': 3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5)) + '@tiptap/extension-highlight': 3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5)) + '@tiptap/extension-horizontal-rule': 3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5) + '@tiptap/extension-image': 3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5)) + '@tiptap/extension-italic': 3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5)) + '@tiptap/extension-link': 3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5) + '@tiptap/extension-list': 3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5) + '@tiptap/extension-paragraph': 3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5)) + '@tiptap/extension-strike': 3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5)) + '@tiptap/extension-subscript': 3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5) + '@tiptap/extension-superscript': 3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5) + '@tiptap/extension-table': 3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5) + '@tiptap/extension-text': 3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5)) + '@tiptap/extension-text-align': 3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5)) + '@tiptap/extension-text-style': 3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5)) + '@tiptap/extension-underline': 3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5)) + '@tiptap/extensions': 3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5) + '@tiptap/pm': 3.20.5 + '@tiptap/suggestion': 3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5) + '@tiptap/vue-3': 3.20.5(@floating-ui/dom@1.7.6)(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5)(vue@3.5.30(typescript@5.8.3)) + github-markdown-css: 5.9.0 + linkifyjs: 4.3.2 + scroll-into-view-if-needed: 3.1.0 + vue: 3.5.30(typescript@5.8.3) + transitivePeerDependencies: + - '@nuxt/kit' + - '@tiptap/extension-collaboration' + - '@tiptap/extension-node-range' + - '@tiptap/y-tiptap' + - axios + - vue-router + + '@halo-dev/ui-plugin-bundler-kit@2.23.0(@rsbuild/core@1.7.3)(@rsbuild/plugin-vue@1.0.7(@rsbuild/core@1.7.3)(@vue/compiler-sfc@3.5.30)(vue@3.5.30(typescript@5.8.3)))(@vitejs/plugin-vue@5.2.4(vite@7.3.1(@types/node@20.19.37)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.1))(vue@3.5.30(typescript@5.8.3)))(axios@1.13.6)(vite@7.3.1(@types/node@20.19.37)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.1))': dependencies: - '@halo-dev/api-client': 2.22.0(axios@1.10.0) - '@rsbuild/core': 1.4.0 - '@rsbuild/plugin-vue': 1.0.7(@rsbuild/core@1.4.0)(@vue/compiler-sfc@3.5.17)(vue@3.5.17(typescript@5.8.3)) - '@vitejs/plugin-vue': 5.2.4(vite@7.0.0(@types/node@20.19.1)(jiti@2.4.2)(sass-embedded@1.89.0)(sass@1.89.2)(terser@5.43.1))(vue@3.5.17(typescript@5.8.3)) + '@halo-dev/api-client': 2.23.0(axios@1.13.6) + '@rsbuild/core': 1.7.3 + '@rsbuild/plugin-vue': 1.0.7(@rsbuild/core@1.7.3)(@vue/compiler-sfc@3.5.30)(vue@3.5.30(typescript@5.8.3)) + '@vitejs/plugin-vue': 5.2.4(vite@7.3.1(@types/node@20.19.37)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.1))(vue@3.5.30(typescript@5.8.3)) js-yaml: 4.1.1 - vite: 7.0.0(@types/node@20.19.1)(jiti@2.4.2)(sass-embedded@1.89.0)(sass@1.89.2)(terser@5.43.1) + vite: 7.3.1(@types/node@20.19.37)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.1) transitivePeerDependencies: - axios - '@halo-dev/ui-shared@2.22.0(axios@1.10.0)(vue-router@4.6.4(vue@3.5.17(typescript@5.8.3)))(vue@3.5.17(typescript@5.8.3))': + '@halo-dev/ui-shared@2.23.0(@tiptap/extension-collaboration@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5)(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.7)(y-protocols@1.0.7(yjs@13.6.30))(yjs@13.6.30))(yjs@13.6.30))(@tiptap/extension-node-range@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5))(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.7)(y-protocols@1.0.7(yjs@13.6.30))(yjs@13.6.30))(axios@1.13.6)(vue-router@4.6.4(vue@3.5.30(typescript@5.8.3)))(vue@3.5.30(typescript@5.8.3))': dependencies: - '@halo-dev/api-client': 2.22.0(axios@1.10.0) + '@halo-dev/api-client': 2.23.0(axios@1.13.6) + '@halo-dev/richtext-editor': 2.23.0(@tiptap/extension-collaboration@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5)(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.7)(y-protocols@1.0.7(yjs@13.6.30))(yjs@13.6.30))(yjs@13.6.30))(@tiptap/extension-node-range@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5))(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.7)(y-protocols@1.0.7(yjs@13.6.30))(yjs@13.6.30))(axios@1.13.6)(vue-router@4.6.4(vue@3.5.30(typescript@5.8.3)))(vue@3.5.30(typescript@5.8.3)) mitt: 3.0.1 - vue: 3.5.17(typescript@5.8.3) - vue-router: 4.6.4(vue@3.5.17(typescript@5.8.3)) + vue: 3.5.30(typescript@5.8.3) + vue-router: 4.6.4(vue@3.5.30(typescript@5.8.3)) transitivePeerDependencies: + - '@nuxt/kit' + - '@tiptap/extension-collaboration' + - '@tiptap/extension-node-range' + - '@tiptap/y-tiptap' - axios '@humanfs/core@0.19.1': {} - '@humanfs/node@0.16.6': + '@humanfs/node@0.16.7': dependencies: '@humanfs/core': 0.19.1 - '@humanwhocodes/retry': 0.3.1 + '@humanwhocodes/retry': 0.4.3 '@humanwhocodes/module-importer@1.0.1': {} - '@humanwhocodes/retry@0.3.1': {} - '@humanwhocodes/retry@0.4.3': {} - '@iconify/json@2.2.353': + '@iconify/json@2.2.454': dependencies: '@iconify/types': 2.0.0 - pathe: 1.1.2 + pathe: 2.0.3 '@iconify/types@2.0.0': {} - '@iconify/utils@2.3.0': + '@iconify/utils@3.1.0': dependencies: '@antfu/install-pkg': 1.1.0 - '@antfu/utils': 8.1.1 '@iconify/types': 2.0.0 - debug: 4.4.1 - globals: 15.15.0 - kolorist: 1.8.0 - local-pkg: 1.1.1 - mlly: 1.7.4 - transitivePeerDependencies: - - supports-color + mlly: 1.8.2 '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.0 + strip-ansi: 7.2.0 strip-ansi-cjs: strip-ansi@6.0.1 wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 - '@jridgewell/gen-mapping@0.3.8': + '@jridgewell/gen-mapping@0.3.13': dependencies: - '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.5.0 - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 - '@jridgewell/resolve-uri@3.1.2': {} + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 - '@jridgewell/set-array@1.2.1': {} + '@jridgewell/resolve-uri@3.1.2': {} - '@jridgewell/source-map@0.3.6': + '@jridgewell/source-map@0.3.11': dependencies: - '@jridgewell/gen-mapping': 0.3.8 - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 - '@jridgewell/sourcemap-codec@1.5.0': {} + '@jridgewell/sourcemap-codec@1.5.5': {} - '@jridgewell/trace-mapping@0.3.25': + '@jridgewell/trace-mapping@0.3.31': dependencies: '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/sourcemap-codec': 1.5.5 - '@module-federation/error-codes@0.15.0': {} + '@module-federation/error-codes@0.22.0': {} - '@module-federation/runtime-core@0.15.0': + '@module-federation/runtime-core@0.22.0': dependencies: - '@module-federation/error-codes': 0.15.0 - '@module-federation/sdk': 0.15.0 + '@module-federation/error-codes': 0.22.0 + '@module-federation/sdk': 0.22.0 - '@module-federation/runtime-tools@0.15.0': + '@module-federation/runtime-tools@0.22.0': dependencies: - '@module-federation/runtime': 0.15.0 - '@module-federation/webpack-bundler-runtime': 0.15.0 + '@module-federation/runtime': 0.22.0 + '@module-federation/webpack-bundler-runtime': 0.22.0 - '@module-federation/runtime@0.15.0': + '@module-federation/runtime@0.22.0': dependencies: - '@module-federation/error-codes': 0.15.0 - '@module-federation/runtime-core': 0.15.0 - '@module-federation/sdk': 0.15.0 + '@module-federation/error-codes': 0.22.0 + '@module-federation/runtime-core': 0.22.0 + '@module-federation/sdk': 0.22.0 - '@module-federation/sdk@0.15.0': {} + '@module-federation/sdk@0.22.0': {} - '@module-federation/webpack-bundler-runtime@0.15.0': + '@module-federation/webpack-bundler-runtime@0.22.0': dependencies: - '@module-federation/runtime': 0.15.0 - '@module-federation/sdk': 0.15.0 + '@module-federation/runtime': 0.22.0 + '@module-federation/sdk': 0.22.0 - '@napi-rs/wasm-runtime@0.2.11': + '@napi-rs/wasm-runtime@1.0.7': dependencies: - '@emnapi/core': 1.4.3 - '@emnapi/runtime': 1.4.3 - '@tybys/wasm-util': 0.9.0 + '@emnapi/core': 1.9.1 + '@emnapi/runtime': 1.9.1 + '@tybys/wasm-util': 0.10.1 optional: true '@nodelib/fs.scandir@2.1.5': @@ -3073,7 +3503,7 @@ snapshots: '@nodelib/fs.walk@1.2.8': dependencies: '@nodelib/fs.scandir': 2.1.5 - fastq: 1.19.1 + fastq: 1.20.1 '@one-ini/wasm@0.1.1': {} @@ -3101,154 +3531,172 @@ snapshots: '@oxlint/win32-x64@0.16.12': optional: true - '@parcel/watcher-android-arm64@2.5.1': + '@parcel/watcher-android-arm64@2.5.6': optional: true - '@parcel/watcher-darwin-arm64@2.5.1': + '@parcel/watcher-darwin-arm64@2.5.6': optional: true - '@parcel/watcher-darwin-x64@2.5.1': + '@parcel/watcher-darwin-x64@2.5.6': optional: true - '@parcel/watcher-freebsd-x64@2.5.1': + '@parcel/watcher-freebsd-x64@2.5.6': optional: true - '@parcel/watcher-linux-arm-glibc@2.5.1': + '@parcel/watcher-linux-arm-glibc@2.5.6': optional: true - '@parcel/watcher-linux-arm-musl@2.5.1': + '@parcel/watcher-linux-arm-musl@2.5.6': optional: true - '@parcel/watcher-linux-arm64-glibc@2.5.1': + '@parcel/watcher-linux-arm64-glibc@2.5.6': optional: true - '@parcel/watcher-linux-arm64-musl@2.5.1': + '@parcel/watcher-linux-arm64-musl@2.5.6': optional: true - '@parcel/watcher-linux-x64-glibc@2.5.1': + '@parcel/watcher-linux-x64-glibc@2.5.6': optional: true - '@parcel/watcher-linux-x64-musl@2.5.1': + '@parcel/watcher-linux-x64-musl@2.5.6': optional: true - '@parcel/watcher-win32-arm64@2.5.1': + '@parcel/watcher-win32-arm64@2.5.6': optional: true - '@parcel/watcher-win32-ia32@2.5.1': + '@parcel/watcher-win32-ia32@2.5.6': optional: true - '@parcel/watcher-win32-x64@2.5.1': + '@parcel/watcher-win32-x64@2.5.6': optional: true - '@parcel/watcher@2.5.1': + '@parcel/watcher@2.5.6': dependencies: - detect-libc: 1.0.3 + detect-libc: 2.1.2 is-glob: 4.0.3 - micromatch: 4.0.8 node-addon-api: 7.1.1 + picomatch: 4.0.4 optionalDependencies: - '@parcel/watcher-android-arm64': 2.5.1 - '@parcel/watcher-darwin-arm64': 2.5.1 - '@parcel/watcher-darwin-x64': 2.5.1 - '@parcel/watcher-freebsd-x64': 2.5.1 - '@parcel/watcher-linux-arm-glibc': 2.5.1 - '@parcel/watcher-linux-arm-musl': 2.5.1 - '@parcel/watcher-linux-arm64-glibc': 2.5.1 - '@parcel/watcher-linux-arm64-musl': 2.5.1 - '@parcel/watcher-linux-x64-glibc': 2.5.1 - '@parcel/watcher-linux-x64-musl': 2.5.1 - '@parcel/watcher-win32-arm64': 2.5.1 - '@parcel/watcher-win32-ia32': 2.5.1 - '@parcel/watcher-win32-x64': 2.5.1 + '@parcel/watcher-android-arm64': 2.5.6 + '@parcel/watcher-darwin-arm64': 2.5.6 + '@parcel/watcher-darwin-x64': 2.5.6 + '@parcel/watcher-freebsd-x64': 2.5.6 + '@parcel/watcher-linux-arm-glibc': 2.5.6 + '@parcel/watcher-linux-arm-musl': 2.5.6 + '@parcel/watcher-linux-arm64-glibc': 2.5.6 + '@parcel/watcher-linux-arm64-musl': 2.5.6 + '@parcel/watcher-linux-x64-glibc': 2.5.6 + '@parcel/watcher-linux-x64-musl': 2.5.6 + '@parcel/watcher-win32-arm64': 2.5.6 + '@parcel/watcher-win32-ia32': 2.5.6 + '@parcel/watcher-win32-x64': 2.5.6 optional: true '@pkgjs/parseargs@0.11.0': optional: true - '@pkgr/core@0.2.7': {} + '@pkgr/core@0.2.9': {} + + '@remirror/core-constants@3.0.0': {} + + '@rollup/rollup-android-arm-eabi@4.60.0': + optional: true + + '@rollup/rollup-android-arm64@4.60.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.60.0': + optional: true + + '@rollup/rollup-darwin-x64@4.60.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.60.0': + optional: true - '@rollup/rollup-android-arm-eabi@4.44.1': + '@rollup/rollup-freebsd-x64@4.60.0': optional: true - '@rollup/rollup-android-arm64@4.44.1': + '@rollup/rollup-linux-arm-gnueabihf@4.60.0': optional: true - '@rollup/rollup-darwin-arm64@4.44.1': + '@rollup/rollup-linux-arm-musleabihf@4.60.0': optional: true - '@rollup/rollup-darwin-x64@4.44.1': + '@rollup/rollup-linux-arm64-gnu@4.60.0': optional: true - '@rollup/rollup-freebsd-arm64@4.44.1': + '@rollup/rollup-linux-arm64-musl@4.60.0': optional: true - '@rollup/rollup-freebsd-x64@4.44.1': + '@rollup/rollup-linux-loong64-gnu@4.60.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.44.1': + '@rollup/rollup-linux-loong64-musl@4.60.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.44.1': + '@rollup/rollup-linux-ppc64-gnu@4.60.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.44.1': + '@rollup/rollup-linux-ppc64-musl@4.60.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.44.1': + '@rollup/rollup-linux-riscv64-gnu@4.60.0': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.44.1': + '@rollup/rollup-linux-riscv64-musl@4.60.0': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.44.1': + '@rollup/rollup-linux-s390x-gnu@4.60.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.44.1': + '@rollup/rollup-linux-x64-gnu@4.60.0': optional: true - '@rollup/rollup-linux-riscv64-musl@4.44.1': + '@rollup/rollup-linux-x64-musl@4.60.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.44.1': + '@rollup/rollup-openbsd-x64@4.60.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.44.1': + '@rollup/rollup-openharmony-arm64@4.60.0': optional: true - '@rollup/rollup-linux-x64-musl@4.44.1': + '@rollup/rollup-win32-arm64-msvc@4.60.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.44.1': + '@rollup/rollup-win32-ia32-msvc@4.60.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.44.1': + '@rollup/rollup-win32-x64-gnu@4.60.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.44.1': + '@rollup/rollup-win32-x64-msvc@4.60.0': optional: true - '@rsbuild/core@1.4.0': + '@rsbuild/core@1.7.3': dependencies: - '@rspack/core': 1.4.0(@swc/helpers@0.5.17) - '@rspack/lite-tapable': 1.0.1 - '@swc/helpers': 0.5.17 - core-js: 3.43.0 - jiti: 2.4.2 + '@rspack/core': 1.7.10(@swc/helpers@0.5.19) + '@rspack/lite-tapable': 1.1.0 + '@swc/helpers': 0.5.19 + core-js: 3.47.0 + jiti: 2.6.1 - '@rsbuild/plugin-sass@1.3.2(@rsbuild/core@1.4.0)': + '@rsbuild/plugin-sass@1.5.1(@rsbuild/core@1.7.3)': dependencies: - '@rsbuild/core': 1.4.0 deepmerge: 4.3.1 loader-utils: 2.0.4 - postcss: 8.5.6 - reduce-configs: 1.1.0 - sass-embedded: 1.89.0 + postcss: 8.5.8 + reduce-configs: 1.1.1 + sass-embedded: 1.98.0 + optionalDependencies: + '@rsbuild/core': 1.7.3 - '@rsbuild/plugin-vue@1.0.7(@rsbuild/core@1.4.0)(@vue/compiler-sfc@3.5.17)(vue@3.5.17(typescript@5.8.3))': + '@rsbuild/plugin-vue@1.0.7(@rsbuild/core@1.7.3)(@vue/compiler-sfc@3.5.30)(vue@3.5.30(typescript@5.8.3))': dependencies: - '@rsbuild/core': 1.4.0 - vue-loader: 17.4.2(@vue/compiler-sfc@3.5.17)(vue@3.5.17(typescript@5.8.3))(webpack@5.99.9) - webpack: 5.99.9 + '@rsbuild/core': 1.7.3 + vue-loader: 17.4.2(@vue/compiler-sfc@3.5.30)(vue@3.5.30(typescript@5.8.3))(webpack@5.105.4) + webpack: 5.105.4 transitivePeerDependencies: - '@swc/core' - '@vue/compiler-sfc' @@ -3257,77 +3705,280 @@ snapshots: - vue - webpack-cli - '@rspack/binding-darwin-arm64@1.4.0': + '@rspack/binding-darwin-arm64@1.7.10': optional: true - '@rspack/binding-darwin-x64@1.4.0': + '@rspack/binding-darwin-x64@1.7.10': optional: true - '@rspack/binding-linux-arm64-gnu@1.4.0': + '@rspack/binding-linux-arm64-gnu@1.7.10': optional: true - '@rspack/binding-linux-arm64-musl@1.4.0': + '@rspack/binding-linux-arm64-musl@1.7.10': optional: true - '@rspack/binding-linux-x64-gnu@1.4.0': + '@rspack/binding-linux-x64-gnu@1.7.10': optional: true - '@rspack/binding-linux-x64-musl@1.4.0': + '@rspack/binding-linux-x64-musl@1.7.10': optional: true - '@rspack/binding-wasm32-wasi@1.4.0': + '@rspack/binding-wasm32-wasi@1.7.10': dependencies: - '@napi-rs/wasm-runtime': 0.2.11 + '@napi-rs/wasm-runtime': 1.0.7 optional: true - '@rspack/binding-win32-arm64-msvc@1.4.0': + '@rspack/binding-win32-arm64-msvc@1.7.10': optional: true - '@rspack/binding-win32-ia32-msvc@1.4.0': + '@rspack/binding-win32-ia32-msvc@1.7.10': optional: true - '@rspack/binding-win32-x64-msvc@1.4.0': + '@rspack/binding-win32-x64-msvc@1.7.10': optional: true - '@rspack/binding@1.4.0': + '@rspack/binding@1.7.10': optionalDependencies: - '@rspack/binding-darwin-arm64': 1.4.0 - '@rspack/binding-darwin-x64': 1.4.0 - '@rspack/binding-linux-arm64-gnu': 1.4.0 - '@rspack/binding-linux-arm64-musl': 1.4.0 - '@rspack/binding-linux-x64-gnu': 1.4.0 - '@rspack/binding-linux-x64-musl': 1.4.0 - '@rspack/binding-wasm32-wasi': 1.4.0 - '@rspack/binding-win32-arm64-msvc': 1.4.0 - '@rspack/binding-win32-ia32-msvc': 1.4.0 - '@rspack/binding-win32-x64-msvc': 1.4.0 - - '@rspack/core@1.4.0(@swc/helpers@0.5.17)': - dependencies: - '@module-federation/runtime-tools': 0.15.0 - '@rspack/binding': 1.4.0 - '@rspack/lite-tapable': 1.0.1 + '@rspack/binding-darwin-arm64': 1.7.10 + '@rspack/binding-darwin-x64': 1.7.10 + '@rspack/binding-linux-arm64-gnu': 1.7.10 + '@rspack/binding-linux-arm64-musl': 1.7.10 + '@rspack/binding-linux-x64-gnu': 1.7.10 + '@rspack/binding-linux-x64-musl': 1.7.10 + '@rspack/binding-wasm32-wasi': 1.7.10 + '@rspack/binding-win32-arm64-msvc': 1.7.10 + '@rspack/binding-win32-ia32-msvc': 1.7.10 + '@rspack/binding-win32-x64-msvc': 1.7.10 + + '@rspack/core@1.7.10(@swc/helpers@0.5.19)': + dependencies: + '@module-federation/runtime-tools': 0.22.0 + '@rspack/binding': 1.7.10 + '@rspack/lite-tapable': 1.1.0 optionalDependencies: - '@swc/helpers': 0.5.17 + '@swc/helpers': 0.5.19 - '@rspack/lite-tapable@1.0.1': {} + '@rspack/lite-tapable@1.1.0': {} - '@swc/helpers@0.5.17': + '@swc/helpers@0.5.19': dependencies: tslib: 2.8.1 - '@tsconfig/node20@20.1.6': {} + '@tiptap/core@3.20.5(@tiptap/pm@3.20.5)': + dependencies: + '@tiptap/pm': 3.20.5 + + '@tiptap/extension-blockquote@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))': + dependencies: + '@tiptap/core': 3.20.5(@tiptap/pm@3.20.5) + + '@tiptap/extension-bold@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))': + dependencies: + '@tiptap/core': 3.20.5(@tiptap/pm@3.20.5) + + '@tiptap/extension-bubble-menu@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5)': + dependencies: + '@floating-ui/dom': 1.7.6 + '@tiptap/core': 3.20.5(@tiptap/pm@3.20.5) + '@tiptap/pm': 3.20.5 + optional: true + + '@tiptap/extension-code-block@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5)': + dependencies: + '@tiptap/core': 3.20.5(@tiptap/pm@3.20.5) + '@tiptap/pm': 3.20.5 + + '@tiptap/extension-code@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))': + dependencies: + '@tiptap/core': 3.20.5(@tiptap/pm@3.20.5) + + '@tiptap/extension-collaboration@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5)(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.7)(y-protocols@1.0.7(yjs@13.6.30))(yjs@13.6.30))(yjs@13.6.30)': + dependencies: + '@tiptap/core': 3.20.5(@tiptap/pm@3.20.5) + '@tiptap/pm': 3.20.5 + '@tiptap/y-tiptap': 3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.7)(y-protocols@1.0.7(yjs@13.6.30))(yjs@13.6.30) + yjs: 13.6.30 + + '@tiptap/extension-color@3.20.5(@tiptap/extension-text-style@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5)))': + dependencies: + '@tiptap/extension-text-style': 3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5)) + + '@tiptap/extension-details@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/extension-text-style@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5)))(@tiptap/pm@3.20.5)': + dependencies: + '@tiptap/core': 3.20.5(@tiptap/pm@3.20.5) + '@tiptap/extension-text-style': 3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5)) + '@tiptap/pm': 3.20.5 + + '@tiptap/extension-document@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))': + dependencies: + '@tiptap/core': 3.20.5(@tiptap/pm@3.20.5) + + '@tiptap/extension-drag-handle-vue-3@3.20.5(@tiptap/extension-drag-handle@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/extension-collaboration@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5)(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.7)(y-protocols@1.0.7(yjs@13.6.30))(yjs@13.6.30))(yjs@13.6.30))(@tiptap/extension-node-range@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5)(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.7)(y-protocols@1.0.7(yjs@13.6.30))(yjs@13.6.30)))(@tiptap/pm@3.20.5)(@tiptap/vue-3@3.20.5(@floating-ui/dom@1.7.6)(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5)(vue@3.5.30(typescript@5.8.3)))(vue@3.5.30(typescript@5.8.3))': + dependencies: + '@tiptap/extension-drag-handle': 3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/extension-collaboration@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5)(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.7)(y-protocols@1.0.7(yjs@13.6.30))(yjs@13.6.30))(yjs@13.6.30))(@tiptap/extension-node-range@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5)(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.7)(y-protocols@1.0.7(yjs@13.6.30))(yjs@13.6.30)) + '@tiptap/pm': 3.20.5 + '@tiptap/vue-3': 3.20.5(@floating-ui/dom@1.7.6)(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5)(vue@3.5.30(typescript@5.8.3)) + vue: 3.5.30(typescript@5.8.3) + + '@tiptap/extension-drag-handle@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/extension-collaboration@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5)(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.7)(y-protocols@1.0.7(yjs@13.6.30))(yjs@13.6.30))(yjs@13.6.30))(@tiptap/extension-node-range@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5)(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.7)(y-protocols@1.0.7(yjs@13.6.30))(yjs@13.6.30))': + dependencies: + '@floating-ui/dom': 1.7.6 + '@tiptap/core': 3.20.5(@tiptap/pm@3.20.5) + '@tiptap/extension-collaboration': 3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5)(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.7)(y-protocols@1.0.7(yjs@13.6.30))(yjs@13.6.30))(yjs@13.6.30) + '@tiptap/extension-node-range': 3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5) + '@tiptap/pm': 3.20.5 + '@tiptap/y-tiptap': 3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.7)(y-protocols@1.0.7(yjs@13.6.30))(yjs@13.6.30) + + '@tiptap/extension-floating-menu@3.20.5(@floating-ui/dom@1.7.6)(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5)': + dependencies: + '@floating-ui/dom': 1.7.6 + '@tiptap/core': 3.20.5(@tiptap/pm@3.20.5) + '@tiptap/pm': 3.20.5 + optional: true + + '@tiptap/extension-hard-break@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))': + dependencies: + '@tiptap/core': 3.20.5(@tiptap/pm@3.20.5) + + '@tiptap/extension-heading@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))': + dependencies: + '@tiptap/core': 3.20.5(@tiptap/pm@3.20.5) + + '@tiptap/extension-highlight@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))': + dependencies: + '@tiptap/core': 3.20.5(@tiptap/pm@3.20.5) + + '@tiptap/extension-horizontal-rule@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5)': + dependencies: + '@tiptap/core': 3.20.5(@tiptap/pm@3.20.5) + '@tiptap/pm': 3.20.5 + + '@tiptap/extension-image@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))': + dependencies: + '@tiptap/core': 3.20.5(@tiptap/pm@3.20.5) + + '@tiptap/extension-italic@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))': + dependencies: + '@tiptap/core': 3.20.5(@tiptap/pm@3.20.5) + + '@tiptap/extension-link@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5)': + dependencies: + '@tiptap/core': 3.20.5(@tiptap/pm@3.20.5) + '@tiptap/pm': 3.20.5 + linkifyjs: 4.3.2 + + '@tiptap/extension-list@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5)': + dependencies: + '@tiptap/core': 3.20.5(@tiptap/pm@3.20.5) + '@tiptap/pm': 3.20.5 + + '@tiptap/extension-node-range@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5)': + dependencies: + '@tiptap/core': 3.20.5(@tiptap/pm@3.20.5) + '@tiptap/pm': 3.20.5 + + '@tiptap/extension-paragraph@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))': + dependencies: + '@tiptap/core': 3.20.5(@tiptap/pm@3.20.5) + + '@tiptap/extension-strike@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))': + dependencies: + '@tiptap/core': 3.20.5(@tiptap/pm@3.20.5) + + '@tiptap/extension-subscript@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5)': + dependencies: + '@tiptap/core': 3.20.5(@tiptap/pm@3.20.5) + '@tiptap/pm': 3.20.5 - '@tybys/wasm-util@0.9.0': + '@tiptap/extension-superscript@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5)': + dependencies: + '@tiptap/core': 3.20.5(@tiptap/pm@3.20.5) + '@tiptap/pm': 3.20.5 + + '@tiptap/extension-table@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5)': + dependencies: + '@tiptap/core': 3.20.5(@tiptap/pm@3.20.5) + '@tiptap/pm': 3.20.5 + + '@tiptap/extension-text-align@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))': + dependencies: + '@tiptap/core': 3.20.5(@tiptap/pm@3.20.5) + + '@tiptap/extension-text-style@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))': + dependencies: + '@tiptap/core': 3.20.5(@tiptap/pm@3.20.5) + + '@tiptap/extension-text@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))': + dependencies: + '@tiptap/core': 3.20.5(@tiptap/pm@3.20.5) + + '@tiptap/extension-underline@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))': + dependencies: + '@tiptap/core': 3.20.5(@tiptap/pm@3.20.5) + + '@tiptap/extensions@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5)': + dependencies: + '@tiptap/core': 3.20.5(@tiptap/pm@3.20.5) + '@tiptap/pm': 3.20.5 + + '@tiptap/pm@3.20.5': + dependencies: + prosemirror-changeset: 2.4.0 + prosemirror-collab: 1.3.1 + prosemirror-commands: 1.7.1 + prosemirror-dropcursor: 1.8.2 + prosemirror-gapcursor: 1.4.1 + prosemirror-history: 1.5.0 + prosemirror-inputrules: 1.5.1 + prosemirror-keymap: 1.2.3 + prosemirror-markdown: 1.13.4 + prosemirror-menu: 1.3.0 + prosemirror-model: 1.25.4 + prosemirror-schema-basic: 1.2.4 + prosemirror-schema-list: 1.5.1 + prosemirror-state: 1.4.4 + prosemirror-tables: 1.8.5 + prosemirror-trailing-node: 3.0.0(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.7) + prosemirror-transform: 1.11.0 + prosemirror-view: 1.41.7 + + '@tiptap/suggestion@3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5)': + dependencies: + '@tiptap/core': 3.20.5(@tiptap/pm@3.20.5) + '@tiptap/pm': 3.20.5 + + '@tiptap/vue-3@3.20.5(@floating-ui/dom@1.7.6)(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5)(vue@3.5.30(typescript@5.8.3))': + dependencies: + '@floating-ui/dom': 1.7.6 + '@tiptap/core': 3.20.5(@tiptap/pm@3.20.5) + '@tiptap/pm': 3.20.5 + vue: 3.5.30(typescript@5.8.3) + optionalDependencies: + '@tiptap/extension-bubble-menu': 3.20.5(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5) + '@tiptap/extension-floating-menu': 3.20.5(@floating-ui/dom@1.7.6)(@tiptap/core@3.20.5(@tiptap/pm@3.20.5))(@tiptap/pm@3.20.5) + + '@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.7)(y-protocols@1.0.7(yjs@13.6.30))(yjs@13.6.30)': + dependencies: + lib0: 0.2.117 + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-view: 1.41.7 + y-protocols: 1.0.7(yjs@13.6.30) + yjs: 13.6.30 + + '@tsconfig/node20@20.1.9': {} + + '@tybys/wasm-util@0.10.1': dependencies: tslib: 2.8.1 optional: true '@types/canvas-confetti@1.9.0': {} - '@types/chai@5.2.2': + '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 '@types/deep-eql@4.0.2': {} @@ -3341,144 +3992,156 @@ snapshots: '@types/estree': 1.0.8 '@types/json-schema': 7.0.15 + '@types/esrecurse@4.3.1': {} + '@types/estree@1.0.8': {} '@types/jsdom@21.1.7': dependencies: - '@types/node': 20.19.1 + '@types/node': 20.19.37 '@types/tough-cookie': 4.0.5 parse5: 7.3.0 '@types/json-schema@7.0.15': {} - '@types/node@20.19.1': + '@types/linkify-it@5.0.0': {} + + '@types/markdown-it@14.1.2': + dependencies: + '@types/linkify-it': 5.0.0 + '@types/mdurl': 2.0.0 + + '@types/mdurl@2.0.0': {} + + '@types/node@20.19.37': dependencies: undici-types: 6.21.0 '@types/tough-cookie@4.0.5': {} - '@typescript-eslint/eslint-plugin@8.35.0(@typescript-eslint/parser@8.35.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)': + '@typescript-eslint/eslint-plugin@8.57.2(@typescript-eslint/parser@8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3)': dependencies: - '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.35.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/scope-manager': 8.35.0 - '@typescript-eslint/type-utils': 8.35.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/utils': 8.35.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/visitor-keys': 8.35.0 - eslint: 9.29.0(jiti@2.4.2) - graphemer: 1.4.0 + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.57.2 + '@typescript-eslint/type-utils': 8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3) + '@typescript-eslint/utils': 8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.57.2 + eslint: 9.39.4(jiti@2.6.1) ignore: 7.0.5 natural-compare: 1.4.0 - ts-api-utils: 2.1.0(typescript@5.8.3) + ts-api-utils: 2.5.0(typescript@5.8.3) typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.35.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)': + '@typescript-eslint/parser@8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3)': dependencies: - '@typescript-eslint/scope-manager': 8.35.0 - '@typescript-eslint/types': 8.35.0 - '@typescript-eslint/typescript-estree': 8.35.0(typescript@5.8.3) - '@typescript-eslint/visitor-keys': 8.35.0 - debug: 4.4.1 - eslint: 9.29.0(jiti@2.4.2) + '@typescript-eslint/scope-manager': 8.57.2 + '@typescript-eslint/types': 8.57.2 + '@typescript-eslint/typescript-estree': 8.57.2(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.57.2 + debug: 4.4.3 + eslint: 9.39.4(jiti@2.6.1) typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.35.0(typescript@5.8.3)': + '@typescript-eslint/project-service@8.57.2(typescript@5.8.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.35.0(typescript@5.8.3) - '@typescript-eslint/types': 8.35.0 - debug: 4.4.1 + '@typescript-eslint/tsconfig-utils': 8.57.2(typescript@5.8.3) + '@typescript-eslint/types': 8.57.2 + debug: 4.4.3 typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.35.0': + '@typescript-eslint/scope-manager@8.57.2': dependencies: - '@typescript-eslint/types': 8.35.0 - '@typescript-eslint/visitor-keys': 8.35.0 + '@typescript-eslint/types': 8.57.2 + '@typescript-eslint/visitor-keys': 8.57.2 - '@typescript-eslint/tsconfig-utils@8.35.0(typescript@5.8.3)': + '@typescript-eslint/tsconfig-utils@8.57.2(typescript@5.8.3)': dependencies: typescript: 5.8.3 - '@typescript-eslint/type-utils@8.35.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)': + '@typescript-eslint/type-utils@8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3)': dependencies: - '@typescript-eslint/typescript-estree': 8.35.0(typescript@5.8.3) - '@typescript-eslint/utils': 8.35.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) - debug: 4.4.1 - eslint: 9.29.0(jiti@2.4.2) - ts-api-utils: 2.1.0(typescript@5.8.3) + '@typescript-eslint/types': 8.57.2 + '@typescript-eslint/typescript-estree': 8.57.2(typescript@5.8.3) + '@typescript-eslint/utils': 8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3) + debug: 4.4.3 + eslint: 9.39.4(jiti@2.6.1) + ts-api-utils: 2.5.0(typescript@5.8.3) typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.35.0': {} + '@typescript-eslint/types@8.57.2': {} - '@typescript-eslint/typescript-estree@8.35.0(typescript@5.8.3)': + '@typescript-eslint/typescript-estree@8.57.2(typescript@5.8.3)': dependencies: - '@typescript-eslint/project-service': 8.35.0(typescript@5.8.3) - '@typescript-eslint/tsconfig-utils': 8.35.0(typescript@5.8.3) - '@typescript-eslint/types': 8.35.0 - '@typescript-eslint/visitor-keys': 8.35.0 - debug: 4.4.1 - fast-glob: 3.3.3 - is-glob: 4.0.3 - minimatch: 9.0.5 - semver: 7.7.2 - ts-api-utils: 2.1.0(typescript@5.8.3) + '@typescript-eslint/project-service': 8.57.2(typescript@5.8.3) + '@typescript-eslint/tsconfig-utils': 8.57.2(typescript@5.8.3) + '@typescript-eslint/types': 8.57.2 + '@typescript-eslint/visitor-keys': 8.57.2 + debug: 4.4.3 + minimatch: 10.2.4 + semver: 7.7.4 + tinyglobby: 0.2.15 + ts-api-utils: 2.5.0(typescript@5.8.3) typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.35.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)': + '@typescript-eslint/utils@8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3)': dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.29.0(jiti@2.4.2)) - '@typescript-eslint/scope-manager': 8.35.0 - '@typescript-eslint/types': 8.35.0 - '@typescript-eslint/typescript-estree': 8.35.0(typescript@5.8.3) - eslint: 9.29.0(jiti@2.4.2) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) + '@typescript-eslint/scope-manager': 8.57.2 + '@typescript-eslint/types': 8.57.2 + '@typescript-eslint/typescript-estree': 8.57.2(typescript@5.8.3) + eslint: 9.39.4(jiti@2.6.1) typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.35.0': + '@typescript-eslint/visitor-keys@8.57.2': dependencies: - '@typescript-eslint/types': 8.35.0 - eslint-visitor-keys: 4.2.1 + '@typescript-eslint/types': 8.57.2 + eslint-visitor-keys: 5.0.1 - '@vitejs/plugin-vue@5.2.4(vite@7.0.0(@types/node@20.19.1)(jiti@2.4.2)(sass-embedded@1.89.0)(sass@1.89.2)(terser@5.43.1))(vue@3.5.17(typescript@5.8.3))': + '@vitejs/plugin-vue@5.2.4(vite@7.3.1(@types/node@20.19.37)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.1))(vue@3.5.30(typescript@5.8.3))': dependencies: - vite: 7.0.0(@types/node@20.19.1)(jiti@2.4.2)(sass-embedded@1.89.0)(sass@1.89.2)(terser@5.43.1) - vue: 3.5.17(typescript@5.8.3) + vite: 7.3.1(@types/node@20.19.37)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.1) + vue: 3.5.30(typescript@5.8.3) - '@vitest/eslint-plugin@1.3.3(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4(@types/node@20.19.1)(jiti@2.4.2)(jsdom@26.1.0)(sass-embedded@1.89.0)(sass@1.89.2)(terser@5.43.1))': + '@vitest/eslint-plugin@1.6.13(@typescript-eslint/eslint-plugin@8.57.2(@typescript-eslint/parser@8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3)(vitest@3.2.4(@types/node@20.19.37)(jiti@2.6.1)(jsdom@26.1.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.1))': dependencies: - '@typescript-eslint/utils': 8.35.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) - eslint: 9.29.0(jiti@2.4.2) + '@typescript-eslint/scope-manager': 8.57.2 + '@typescript-eslint/utils': 8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3) + eslint: 9.39.4(jiti@2.6.1) optionalDependencies: + '@typescript-eslint/eslint-plugin': 8.57.2(@typescript-eslint/parser@8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3) typescript: 5.8.3 - vitest: 3.2.4(@types/node@20.19.1)(jiti@2.4.2)(jsdom@26.1.0)(sass-embedded@1.89.0)(sass@1.89.2)(terser@5.43.1) + vitest: 3.2.4(@types/node@20.19.37)(jiti@2.6.1)(jsdom@26.1.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.1) transitivePeerDependencies: - supports-color '@vitest/expect@3.2.4': dependencies: - '@types/chai': 5.2.2 + '@types/chai': 5.2.3 '@vitest/spy': 3.2.4 '@vitest/utils': 3.2.4 - chai: 5.2.0 + chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@7.0.0(@types/node@20.19.1)(jiti@2.4.2)(sass-embedded@1.89.0)(sass@1.89.2)(terser@5.43.1))': + '@vitest/mocker@3.2.4(vite@7.3.1(@types/node@20.19.37)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.1))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 - magic-string: 0.30.17 + magic-string: 0.30.21 optionalDependencies: - vite: 7.0.0(@types/node@20.19.1)(jiti@2.4.2)(sass-embedded@1.89.0)(sass@1.89.2)(terser@5.43.1) + vite: 7.3.1(@types/node@20.19.37)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.1) '@vitest/pretty-format@3.2.4': dependencies: @@ -3488,22 +4151,22 @@ snapshots: dependencies: '@vitest/utils': 3.2.4 pathe: 2.0.3 - strip-literal: 3.0.0 + strip-literal: 3.1.0 '@vitest/snapshot@3.2.4': dependencies: '@vitest/pretty-format': 3.2.4 - magic-string: 0.30.17 + magic-string: 0.30.21 pathe: 2.0.3 '@vitest/spy@3.2.4': dependencies: - tinyspy: 4.0.3 + tinyspy: 4.0.4 '@vitest/utils@3.2.4': dependencies: '@vitest/pretty-format': 3.2.4 - loupe: 3.1.4 + loupe: 3.2.1 tinyrainbow: 2.0.0 '@volar/language-core@2.4.15': @@ -3518,35 +4181,35 @@ snapshots: path-browserify: 1.0.1 vscode-uri: 3.1.0 - '@vue/compiler-core@3.5.17': + '@vue/compiler-core@3.5.30': dependencies: - '@babel/parser': 7.27.7 - '@vue/shared': 3.5.17 - entities: 4.5.0 + '@babel/parser': 7.29.2 + '@vue/shared': 3.5.30 + entities: 7.0.1 estree-walker: 2.0.2 source-map-js: 1.2.1 - '@vue/compiler-dom@3.5.17': + '@vue/compiler-dom@3.5.30': dependencies: - '@vue/compiler-core': 3.5.17 - '@vue/shared': 3.5.17 + '@vue/compiler-core': 3.5.30 + '@vue/shared': 3.5.30 - '@vue/compiler-sfc@3.5.17': + '@vue/compiler-sfc@3.5.30': dependencies: - '@babel/parser': 7.27.7 - '@vue/compiler-core': 3.5.17 - '@vue/compiler-dom': 3.5.17 - '@vue/compiler-ssr': 3.5.17 - '@vue/shared': 3.5.17 + '@babel/parser': 7.29.2 + '@vue/compiler-core': 3.5.30 + '@vue/compiler-dom': 3.5.30 + '@vue/compiler-ssr': 3.5.30 + '@vue/shared': 3.5.30 estree-walker: 2.0.2 - magic-string: 0.30.17 - postcss: 8.5.6 + magic-string: 0.30.21 + postcss: 8.5.8 source-map-js: 1.2.1 - '@vue/compiler-ssr@3.5.17': + '@vue/compiler-ssr@3.5.30': dependencies: - '@vue/compiler-dom': 3.5.17 - '@vue/shared': 3.5.17 + '@vue/compiler-dom': 3.5.30 + '@vue/shared': 3.5.30 '@vue/compiler-vue2@2.7.16': dependencies: @@ -3573,74 +4236,74 @@ snapshots: dependencies: rfdc: 1.4.1 - '@vue/eslint-config-prettier@10.2.0(@types/eslint@9.6.1)(eslint@9.29.0(jiti@2.4.2))(prettier@3.6.2)': + '@vue/eslint-config-prettier@10.2.0(@types/eslint@9.6.1)(eslint@9.39.4(jiti@2.6.1))(prettier@3.8.1)': dependencies: - eslint: 9.29.0(jiti@2.4.2) - eslint-config-prettier: 10.1.5(eslint@9.29.0(jiti@2.4.2)) - eslint-plugin-prettier: 5.5.1(@types/eslint@9.6.1)(eslint-config-prettier@10.1.5(eslint@9.29.0(jiti@2.4.2)))(eslint@9.29.0(jiti@2.4.2))(prettier@3.6.2) - prettier: 3.6.2 + eslint: 9.39.4(jiti@2.6.1) + eslint-config-prettier: 10.1.8(eslint@9.39.4(jiti@2.6.1)) + eslint-plugin-prettier: 5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1))(prettier@3.8.1) + prettier: 3.8.1 transitivePeerDependencies: - '@types/eslint' - '@vue/eslint-config-typescript@14.5.1(eslint-plugin-vue@10.0.1(eslint@9.29.0(jiti@2.4.2))(vue-eslint-parser@10.1.4(eslint@9.29.0(jiti@2.4.2))))(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)': + '@vue/eslint-config-typescript@14.7.0(eslint-plugin-vue@10.0.1(eslint@9.39.4(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@9.39.4(jiti@2.6.1))))(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3)': dependencies: - '@typescript-eslint/utils': 8.35.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) - eslint: 9.29.0(jiti@2.4.2) - eslint-plugin-vue: 10.0.1(eslint@9.29.0(jiti@2.4.2))(vue-eslint-parser@10.1.4(eslint@9.29.0(jiti@2.4.2))) + '@typescript-eslint/utils': 8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3) + eslint: 9.39.4(jiti@2.6.1) + eslint-plugin-vue: 10.0.1(eslint@9.39.4(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@9.39.4(jiti@2.6.1))) fast-glob: 3.3.3 - typescript-eslint: 8.35.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) - vue-eslint-parser: 10.1.4(eslint@9.29.0(jiti@2.4.2)) + typescript-eslint: 8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3) + vue-eslint-parser: 10.4.0(eslint@9.39.4(jiti@2.6.1)) optionalDependencies: typescript: 5.8.3 transitivePeerDependencies: - supports-color - '@vue/language-core@2.2.10(typescript@5.8.3)': + '@vue/language-core@2.2.12(typescript@5.8.3)': dependencies: '@volar/language-core': 2.4.15 - '@vue/compiler-dom': 3.5.17 + '@vue/compiler-dom': 3.5.30 '@vue/compiler-vue2': 2.7.16 - '@vue/shared': 3.5.17 + '@vue/shared': 3.5.30 alien-signals: 1.0.13 - minimatch: 9.0.5 + minimatch: 9.0.9 muggle-string: 0.4.1 path-browserify: 1.0.1 optionalDependencies: typescript: 5.8.3 - '@vue/reactivity@3.5.17': + '@vue/reactivity@3.5.30': dependencies: - '@vue/shared': 3.5.17 + '@vue/shared': 3.5.30 - '@vue/runtime-core@3.5.17': + '@vue/runtime-core@3.5.30': dependencies: - '@vue/reactivity': 3.5.17 - '@vue/shared': 3.5.17 + '@vue/reactivity': 3.5.30 + '@vue/shared': 3.5.30 - '@vue/runtime-dom@3.5.17': + '@vue/runtime-dom@3.5.30': dependencies: - '@vue/reactivity': 3.5.17 - '@vue/runtime-core': 3.5.17 - '@vue/shared': 3.5.17 - csstype: 3.1.3 + '@vue/reactivity': 3.5.30 + '@vue/runtime-core': 3.5.30 + '@vue/shared': 3.5.30 + csstype: 3.2.3 - '@vue/server-renderer@3.5.17(vue@3.5.17(typescript@5.8.3))': + '@vue/server-renderer@3.5.30(vue@3.5.30(typescript@5.8.3))': dependencies: - '@vue/compiler-ssr': 3.5.17 - '@vue/shared': 3.5.17 - vue: 3.5.17(typescript@5.8.3) + '@vue/compiler-ssr': 3.5.30 + '@vue/shared': 3.5.30 + vue: 3.5.30(typescript@5.8.3) - '@vue/shared@3.5.17': {} + '@vue/shared@3.5.30': {} '@vue/test-utils@2.4.6': dependencies: js-beautify: 1.15.4 - vue-component-type-helpers: 2.2.10 + vue-component-type-helpers: 2.2.12 - '@vue/tsconfig@0.7.0(typescript@5.8.3)(vue@3.5.17(typescript@5.8.3))': + '@vue/tsconfig@0.7.0(typescript@5.8.3)(vue@3.5.30(typescript@5.8.3))': optionalDependencies: typescript: 5.8.3 - vue: 3.5.17(typescript@5.8.3) + vue: 3.5.30(typescript@5.8.3) '@webassemblyjs/ast@1.14.1': dependencies: @@ -3724,34 +4387,38 @@ snapshots: abbrev@2.0.0: {} - acorn-jsx@5.3.2(acorn@8.15.0): + acorn-import-phases@1.0.4(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn-jsx@5.3.2(acorn@8.16.0): dependencies: - acorn: 8.15.0 + acorn: 8.16.0 - acorn@8.15.0: {} + acorn@8.16.0: {} - agent-base@7.1.3: {} + agent-base@7.1.4: {} - ajv-formats@2.1.1(ajv@8.17.1): + ajv-formats@2.1.1(ajv@8.18.0): optionalDependencies: - ajv: 8.17.1 + ajv: 8.18.0 - ajv-keywords@5.1.0(ajv@8.17.1): + ajv-keywords@5.1.0(ajv@8.18.0): dependencies: - ajv: 8.17.1 + ajv: 8.18.0 fast-deep-equal: 3.1.3 - ajv@6.12.6: + ajv@6.14.0: dependencies: fast-deep-equal: 3.1.3 fast-json-stable-stringify: 2.1.0 json-schema-traverse: 0.4.1 uri-js: 4.4.1 - ajv@8.17.1: + ajv@8.18.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.0.6 + fast-uri: 3.1.0 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -3759,13 +4426,13 @@ snapshots: ansi-regex@5.0.1: {} - ansi-regex@6.1.0: {} + ansi-regex@6.2.2: {} ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 - ansi-styles@6.2.1: {} + ansi-styles@6.2.3: {} argparse@2.0.1: {} @@ -3773,16 +4440,20 @@ snapshots: asynckit@0.4.0: {} - axios@1.10.0: + axios@1.13.6: dependencies: - follow-redirects: 1.15.9 - form-data: 4.0.3 + follow-redirects: 1.15.11 + form-data: 4.0.5 proxy-from-env: 1.1.0 transitivePeerDependencies: - debug balanced-match@1.0.2: {} + balanced-match@4.0.4: {} + + baseline-browser-mapping@2.10.10: {} + big.js@5.2.2: {} birpc@2.9.0: {} @@ -3798,18 +4469,21 @@ snapshots: dependencies: balanced-match: 1.0.2 + brace-expansion@5.0.5: + dependencies: + balanced-match: 4.0.4 + braces@3.0.3: dependencies: fill-range: 7.1.1 - browserslist@4.25.1: + browserslist@4.28.1: dependencies: - caniuse-lite: 1.0.30001726 - electron-to-chromium: 1.5.177 - node-releases: 2.0.19 - update-browserslist-db: 1.1.3(browserslist@4.25.1) - - buffer-builder@0.2.0: {} + baseline-browser-mapping: 2.10.10 + caniuse-lite: 1.0.30001781 + electron-to-chromium: 1.5.322 + node-releases: 2.0.36 + update-browserslist-db: 1.2.3(browserslist@4.28.1) buffer-from@1.1.2: {} @@ -3827,16 +4501,16 @@ snapshots: callsites@3.1.0: {} - caniuse-lite@1.0.30001726: {} + caniuse-lite@1.0.30001781: {} - canvas-confetti@1.9.3: {} + canvas-confetti@1.9.4: {} - chai@5.2.0: + chai@5.3.3: dependencies: assertion-error: 2.0.1 - check-error: 2.1.1 + check-error: 2.1.3 deep-eql: 5.0.2 - loupe: 3.1.4 + loupe: 3.2.1 pathval: 2.0.1 chalk@4.1.2: @@ -3844,7 +4518,7 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 - check-error@2.1.1: {} + check-error@2.1.3: {} chokidar@4.0.3: dependencies: @@ -3868,11 +4542,13 @@ snapshots: commander@2.20.3: {} + compute-scroll-into-view@3.1.1: {} + concat-map@0.0.1: {} confbox@0.1.8: {} - confbox@0.2.2: {} + confbox@0.2.4: {} config-chain@1.1.13: dependencies: @@ -3883,7 +4559,9 @@ snapshots: dependencies: is-what: 5.5.0 - core-js@3.43.0: {} + core-js@3.47.0: {} + + crelt@1.0.6: {} cross-spawn@7.0.6: dependencies: @@ -3898,7 +4576,7 @@ snapshots: '@asamuzakjp/css-color': 3.2.0 rrweb-cssom: 0.8.0 - csstype@3.1.3: {} + csstype@3.2.3: {} data-urls@5.0.0: dependencies: @@ -3907,11 +4585,11 @@ snapshots: de-indent@1.0.2: {} - debug@4.4.1: + debug@4.4.3: dependencies: ms: 2.1.3 - decimal.js@10.5.0: {} + decimal.js@10.6.0: {} deep-eql@5.0.2: {} @@ -3921,7 +4599,7 @@ snapshots: delayed-stream@1.0.0: {} - detect-libc@1.0.3: + detect-libc@2.1.2: optional: true dunder-proto@1.0.1: @@ -3932,14 +4610,14 @@ snapshots: eastasianwidth@0.2.0: {} - editorconfig@1.0.4: + editorconfig@1.0.7: dependencies: '@one-ini/wasm': 0.1.1 commander: 10.0.1 - minimatch: 9.0.1 - semver: 7.7.2 + minimatch: 9.0.9 + semver: 7.7.4 - electron-to-chromium@1.5.177: {} + electron-to-chromium@1.5.322: {} emoji-regex@8.0.0: {} @@ -3947,21 +4625,25 @@ snapshots: emojis-list@3.0.0: {} - enhanced-resolve@5.18.2: + enhanced-resolve@5.20.1: dependencies: graceful-fs: 4.2.11 - tapable: 2.2.2 + tapable: 2.3.2 entities@4.5.0: {} entities@6.0.1: {} + entities@7.0.1: {} + es-define-property@1.0.1: {} es-errors@1.3.0: {} es-module-lexer@1.7.0: {} + es-module-lexer@2.0.0: {} + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 @@ -3973,65 +4655,66 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 - esbuild@0.25.5: + esbuild@0.27.4: optionalDependencies: - '@esbuild/aix-ppc64': 0.25.5 - '@esbuild/android-arm': 0.25.5 - '@esbuild/android-arm64': 0.25.5 - '@esbuild/android-x64': 0.25.5 - '@esbuild/darwin-arm64': 0.25.5 - '@esbuild/darwin-x64': 0.25.5 - '@esbuild/freebsd-arm64': 0.25.5 - '@esbuild/freebsd-x64': 0.25.5 - '@esbuild/linux-arm': 0.25.5 - '@esbuild/linux-arm64': 0.25.5 - '@esbuild/linux-ia32': 0.25.5 - '@esbuild/linux-loong64': 0.25.5 - '@esbuild/linux-mips64el': 0.25.5 - '@esbuild/linux-ppc64': 0.25.5 - '@esbuild/linux-riscv64': 0.25.5 - '@esbuild/linux-s390x': 0.25.5 - '@esbuild/linux-x64': 0.25.5 - '@esbuild/netbsd-arm64': 0.25.5 - '@esbuild/netbsd-x64': 0.25.5 - '@esbuild/openbsd-arm64': 0.25.5 - '@esbuild/openbsd-x64': 0.25.5 - '@esbuild/sunos-x64': 0.25.5 - '@esbuild/win32-arm64': 0.25.5 - '@esbuild/win32-ia32': 0.25.5 - '@esbuild/win32-x64': 0.25.5 + '@esbuild/aix-ppc64': 0.27.4 + '@esbuild/android-arm': 0.27.4 + '@esbuild/android-arm64': 0.27.4 + '@esbuild/android-x64': 0.27.4 + '@esbuild/darwin-arm64': 0.27.4 + '@esbuild/darwin-x64': 0.27.4 + '@esbuild/freebsd-arm64': 0.27.4 + '@esbuild/freebsd-x64': 0.27.4 + '@esbuild/linux-arm': 0.27.4 + '@esbuild/linux-arm64': 0.27.4 + '@esbuild/linux-ia32': 0.27.4 + '@esbuild/linux-loong64': 0.27.4 + '@esbuild/linux-mips64el': 0.27.4 + '@esbuild/linux-ppc64': 0.27.4 + '@esbuild/linux-riscv64': 0.27.4 + '@esbuild/linux-s390x': 0.27.4 + '@esbuild/linux-x64': 0.27.4 + '@esbuild/netbsd-arm64': 0.27.4 + '@esbuild/netbsd-x64': 0.27.4 + '@esbuild/openbsd-arm64': 0.27.4 + '@esbuild/openbsd-x64': 0.27.4 + '@esbuild/openharmony-arm64': 0.27.4 + '@esbuild/sunos-x64': 0.27.4 + '@esbuild/win32-arm64': 0.27.4 + '@esbuild/win32-ia32': 0.27.4 + '@esbuild/win32-x64': 0.27.4 escalade@3.2.0: {} escape-string-regexp@4.0.0: {} - eslint-config-prettier@10.1.5(eslint@9.29.0(jiti@2.4.2)): + eslint-config-prettier@10.1.8(eslint@9.39.4(jiti@2.6.1)): dependencies: - eslint: 9.29.0(jiti@2.4.2) + eslint: 9.39.4(jiti@2.6.1) eslint-plugin-oxlint@0.16.12: dependencies: jsonc-parser: 3.3.1 - eslint-plugin-prettier@5.5.1(@types/eslint@9.6.1)(eslint-config-prettier@10.1.5(eslint@9.29.0(jiti@2.4.2)))(eslint@9.29.0(jiti@2.4.2))(prettier@3.6.2): + eslint-plugin-prettier@5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1))(prettier@3.8.1): dependencies: - eslint: 9.29.0(jiti@2.4.2) - prettier: 3.6.2 - prettier-linter-helpers: 1.0.0 - synckit: 0.11.8 + eslint: 9.39.4(jiti@2.6.1) + prettier: 3.8.1 + prettier-linter-helpers: 1.0.1 + synckit: 0.11.12 optionalDependencies: '@types/eslint': 9.6.1 - eslint-config-prettier: 10.1.5(eslint@9.29.0(jiti@2.4.2)) + eslint-config-prettier: 10.1.8(eslint@9.39.4(jiti@2.6.1)) - eslint-plugin-vue@10.0.1(eslint@9.29.0(jiti@2.4.2))(vue-eslint-parser@10.1.4(eslint@9.29.0(jiti@2.4.2))): + eslint-plugin-vue@10.0.1(eslint@9.39.4(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@9.39.4(jiti@2.6.1))): dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.29.0(jiti@2.4.2)) - eslint: 9.29.0(jiti@2.4.2) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) + eslint: 9.39.4(jiti@2.6.1) natural-compare: 1.4.0 nth-check: 2.1.1 postcss-selector-parser: 6.1.2 - semver: 7.7.2 - vue-eslint-parser: 10.1.4(eslint@9.29.0(jiti@2.4.2)) + semver: 7.7.4 + vue-eslint-parser: 10.4.0(eslint@9.39.4(jiti@2.6.1)) xml-name-validator: 4.0.0 eslint-scope@5.1.1: @@ -4044,34 +4727,42 @@ snapshots: esrecurse: 4.3.0 estraverse: 5.3.0 + eslint-scope@9.1.2: + dependencies: + '@types/esrecurse': 4.3.1 + '@types/estree': 1.0.8 + esrecurse: 4.3.0 + estraverse: 5.3.0 + eslint-visitor-keys@3.4.3: {} eslint-visitor-keys@4.2.1: {} - eslint@9.29.0(jiti@2.4.2): - dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.29.0(jiti@2.4.2)) - '@eslint-community/regexpp': 4.12.1 - '@eslint/config-array': 0.20.1 - '@eslint/config-helpers': 0.2.3 - '@eslint/core': 0.14.0 - '@eslint/eslintrc': 3.3.1 - '@eslint/js': 9.29.0 - '@eslint/plugin-kit': 0.3.3 - '@humanfs/node': 0.16.6 + eslint-visitor-keys@5.0.1: {} + + eslint@9.39.4(jiti@2.6.1): + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.2 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.5 + '@eslint/js': 9.39.4 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.7 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 '@types/estree': 1.0.8 - '@types/json-schema': 7.0.15 - ajv: 6.12.6 + ajv: 6.14.0 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.1 + debug: 4.4.3 escape-string-regexp: 4.0.0 eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 espree: 10.4.0 - esquery: 1.6.0 + esquery: 1.7.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 file-entry-cache: 8.0.0 @@ -4082,21 +4773,27 @@ snapshots: is-glob: 4.0.3 json-stable-stringify-without-jsonify: 1.0.1 lodash.merge: 4.6.2 - minimatch: 3.1.2 + minimatch: 3.1.5 natural-compare: 1.4.0 optionator: 0.9.4 optionalDependencies: - jiti: 2.4.2 + jiti: 2.6.1 transitivePeerDependencies: - supports-color espree@10.4.0: dependencies: - acorn: 8.15.0 - acorn-jsx: 5.3.2(acorn@8.15.0) + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) eslint-visitor-keys: 4.2.1 - esquery@1.6.0: + espree@11.2.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 5.0.1 + + esquery@1.7.0: dependencies: estraverse: 5.3.0 @@ -4118,9 +4815,9 @@ snapshots: events@3.3.0: {} - expect-type@1.2.1: {} + expect-type@1.3.0: {} - exsolve@1.0.7: {} + exsolve@1.0.8: {} fast-deep-equal@3.1.3: {} @@ -4138,15 +4835,15 @@ snapshots: fast-levenshtein@2.0.6: {} - fast-uri@3.0.6: {} + fast-uri@3.1.0: {} - fastq@1.19.1: + fastq@1.20.1: dependencies: reusify: 1.1.0 - fdir@6.4.6(picomatch@4.0.2): + fdir@6.5.0(picomatch@4.0.4): optionalDependencies: - picomatch: 4.0.2 + picomatch: 4.0.4 file-entry-cache@8.0.0: dependencies: @@ -4163,25 +4860,25 @@ snapshots: flat-cache@4.0.1: dependencies: - flatted: 3.3.3 + flatted: 3.4.2 keyv: 4.5.4 - flatted@3.3.3: {} + flatted@3.4.2: {} - floating-vue@5.2.2(vue@3.5.17(typescript@5.8.3)): + floating-vue@5.2.2(vue@3.5.30(typescript@5.8.3)): dependencies: '@floating-ui/dom': 1.1.1 - vue: 3.5.17(typescript@5.8.3) - vue-resize: 2.0.0-alpha.1(vue@3.5.17(typescript@5.8.3)) + vue: 3.5.30(typescript@5.8.3) + vue-resize: 2.0.0-alpha.1(vue@3.5.30(typescript@5.8.3)) - follow-redirects@1.15.9: {} + follow-redirects@1.15.11: {} foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 signal-exit: 4.1.0 - form-data@4.0.3: + form-data@4.0.5: dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 @@ -4212,6 +4909,8 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 + github-markdown-css@5.9.0: {} + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -4222,25 +4921,21 @@ snapshots: glob-to-regexp@0.4.1: {} - glob@10.4.5: + glob@10.5.0: dependencies: foreground-child: 3.3.1 jackspeak: 3.4.3 - minimatch: 9.0.5 - minipass: 7.1.2 + minimatch: 9.0.9 + minipass: 7.1.3 package-json-from-dist: 1.0.1 path-scurry: 1.11.1 globals@14.0.0: {} - globals@15.15.0: {} - gopd@1.2.0: {} graceful-fs@4.2.11: {} - graphemer@1.4.0: {} - has-flag@4.0.0: {} has-symbols@1.1.0: {} @@ -4265,15 +4960,15 @@ snapshots: http-proxy-agent@7.0.2: dependencies: - agent-base: 7.1.3 - debug: 4.4.1 + agent-base: 7.1.4 + debug: 4.4.3 transitivePeerDependencies: - supports-color https-proxy-agent@7.0.6: dependencies: - agent-base: 7.1.3 - debug: 4.4.1 + agent-base: 7.1.4 + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -4285,7 +4980,7 @@ snapshots: ignore@7.0.5: {} - immutable@5.1.3: {} + immutable@5.1.5: {} import-fresh@3.3.1: dependencies: @@ -4312,7 +5007,9 @@ snapshots: isexe@2.0.0: {} - isexe@3.1.1: {} + isexe@3.1.5: {} + + isomorphic.js@0.2.5: {} jackspeak@3.4.3: dependencies: @@ -4322,17 +5019,17 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 20.19.1 + '@types/node': 20.19.37 merge-stream: 2.0.0 supports-color: 8.1.1 - jiti@2.4.2: {} + jiti@2.6.1: {} js-beautify@1.15.4: dependencies: config-chain: 1.1.13 - editorconfig: 1.0.4 - glob: 10.4.5 + editorconfig: 1.0.7 + glob: 10.5.0 js-cookie: 3.0.5 nopt: 7.2.1 @@ -4340,10 +5037,6 @@ snapshots: js-tokens@9.0.1: {} - js-yaml@4.1.0: - dependencies: - argparse: 2.0.1 - js-yaml@4.1.1: dependencies: argparse: 2.0.1 @@ -4352,12 +5045,12 @@ snapshots: dependencies: cssstyle: 4.6.0 data-urls: 5.0.0 - decimal.js: 10.5.0 + decimal.js: 10.6.0 html-encoding-sniffer: 4.0.0 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 is-potential-custom-element-name: 1.0.1 - nwsapi: 2.2.20 + nwsapi: 2.2.23 parse5: 7.3.0 rrweb-cssom: 0.8.0 saxes: 6.0.0 @@ -4368,7 +5061,7 @@ snapshots: whatwg-encoding: 3.1.1 whatwg-mimetype: 4.0.0 whatwg-url: 14.2.0 - ws: 8.18.2 + ws: 8.20.0 xml-name-validator: 5.0.0 transitivePeerDependencies: - bufferutil @@ -4395,14 +5088,22 @@ snapshots: dependencies: json-buffer: 3.0.1 - kolorist@1.8.0: {} - levn@0.4.1: dependencies: prelude-ls: 1.2.1 type-check: 0.4.0 - loader-runner@4.3.0: {} + lib0@0.2.117: + dependencies: + isomorphic.js: 0.2.5 + + linkify-it@5.0.0: + dependencies: + uc.micro: 2.1.0 + + linkifyjs@4.3.2: {} + + loader-runner@4.3.1: {} loader-utils@2.0.4: dependencies: @@ -4410,11 +5111,11 @@ snapshots: emojis-list: 3.0.0 json5: 2.2.3 - local-pkg@1.1.1: + local-pkg@1.1.2: dependencies: - mlly: 1.7.4 - pkg-types: 2.1.1 - quansync: 0.2.10 + mlly: 1.8.2 + pkg-types: 2.3.0 + quansync: 0.2.11 locate-path@6.0.0: dependencies: @@ -4422,18 +5123,27 @@ snapshots: lodash.merge@4.6.2: {} - lodash@4.17.21: {} - - loupe@3.1.4: {} + loupe@3.2.1: {} lru-cache@10.4.3: {} - magic-string@0.30.17: + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + markdown-it@14.1.1: dependencies: - '@jridgewell/sourcemap-codec': 1.5.0 + argparse: 2.0.1 + entities: 4.5.0 + linkify-it: 5.0.0 + mdurl: 2.0.0 + punycode.js: 2.3.1 + uc.micro: 2.1.0 math-intrinsics@1.1.0: {} + mdurl@2.0.0: {} + memorystream@0.3.1: {} merge-stream@2.0.0: {} @@ -4443,7 +5153,7 @@ snapshots: micromatch@4.0.8: dependencies: braces: 3.0.3 - picomatch: 2.3.1 + picomatch: 2.3.2 mime-db@1.52.0: {} @@ -4451,28 +5161,28 @@ snapshots: dependencies: mime-db: 1.52.0 - minimatch@3.1.2: + minimatch@10.2.4: dependencies: - brace-expansion: 1.1.12 + brace-expansion: 5.0.5 - minimatch@9.0.1: + minimatch@3.1.5: dependencies: - brace-expansion: 2.0.2 + brace-expansion: 1.1.12 - minimatch@9.0.5: + minimatch@9.0.9: dependencies: brace-expansion: 2.0.2 - minipass@7.1.2: {} + minipass@7.1.3: {} mitt@3.0.1: {} - mlly@1.7.4: + mlly@1.8.2: dependencies: - acorn: 8.15.0 + acorn: 8.16.0 pathe: 2.0.3 pkg-types: 1.3.1 - ufo: 1.6.1 + ufo: 1.6.3 ms@2.1.3: {} @@ -4487,7 +5197,7 @@ snapshots: node-addon-api@7.1.1: optional: true - node-releases@2.0.19: {} + node-releases@2.0.36: {} nopt@7.2.1: dependencies: @@ -4497,10 +5207,10 @@ snapshots: npm-run-all2@7.0.2: dependencies: - ansi-styles: 6.2.1 + ansi-styles: 6.2.3 cross-spawn: 7.0.6 memorystream: 0.3.1 - minimatch: 9.0.5 + minimatch: 9.0.9 pidtree: 0.6.0 read-package-json-fast: 4.0.0 shell-quote: 1.8.3 @@ -4510,7 +5220,7 @@ snapshots: dependencies: boolbase: 1.0.0 - nwsapi@2.2.20: {} + nwsapi@2.2.23: {} object-inspect@1.13.4: {} @@ -4523,6 +5233,8 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + orderedmap@2.1.1: {} + oxlint@0.16.12: optionalDependencies: '@oxlint/darwin-arm64': 0.16.12 @@ -4544,7 +5256,7 @@ snapshots: package-json-from-dist@1.0.1: {} - package-manager-detector@1.3.0: {} + package-manager-detector@1.6.0: {} parent-module@1.0.1: dependencies: @@ -4563,9 +5275,7 @@ snapshots: path-scurry@1.11.1: dependencies: lru-cache: 10.4.3 - minipass: 7.1.2 - - pathe@1.1.2: {} + minipass: 7.1.3 pathe@2.0.3: {} @@ -4575,29 +5285,29 @@ snapshots: picocolors@1.1.1: {} - picomatch@2.3.1: {} + picomatch@2.3.2: {} - picomatch@4.0.2: {} + picomatch@4.0.4: {} pidtree@0.6.0: {} - pinia@3.0.4(typescript@5.8.3)(vue@3.5.17(typescript@5.8.3)): + pinia@3.0.4(typescript@5.8.3)(vue@3.5.30(typescript@5.8.3)): dependencies: '@vue/devtools-api': 7.7.9 - vue: 3.5.17(typescript@5.8.3) + vue: 3.5.30(typescript@5.8.3) optionalDependencies: typescript: 5.8.3 pkg-types@1.3.1: dependencies: confbox: 0.1.8 - mlly: 1.7.4 + mlly: 1.8.2 pathe: 2.0.3 - pkg-types@2.1.1: + pkg-types@2.3.0: dependencies: - confbox: 0.2.2 - exsolve: 1.0.7 + confbox: 0.2.4 + exsolve: 1.0.8 pathe: 2.0.3 postcss-selector-parser@6.1.2: @@ -4605,7 +5315,7 @@ snapshots: cssesc: 3.0.0 util-deprecate: 1.0.2 - postcss@8.5.6: + postcss@8.5.8: dependencies: nanoid: 3.3.11 picocolors: 1.1.1 @@ -4613,30 +5323,131 @@ snapshots: prelude-ls@1.2.1: {} - prettier-linter-helpers@1.0.0: + prettier-linter-helpers@1.0.1: dependencies: fast-diff: 1.3.0 - prettier@3.6.2: {} + prettier@3.8.1: {} + + prosemirror-changeset@2.4.0: + dependencies: + prosemirror-transform: 1.11.0 + + prosemirror-collab@1.3.1: + dependencies: + prosemirror-state: 1.4.4 + + prosemirror-commands@1.7.1: + dependencies: + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + + prosemirror-dropcursor@1.8.2: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + prosemirror-view: 1.41.7 + + prosemirror-gapcursor@1.4.1: + dependencies: + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-view: 1.41.7 + + prosemirror-history@1.5.0: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + prosemirror-view: 1.41.7 + rope-sequence: 1.3.4 + + prosemirror-inputrules@1.5.1: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + + prosemirror-keymap@1.2.3: + dependencies: + prosemirror-state: 1.4.4 + w3c-keyname: 2.2.8 + + prosemirror-markdown@1.13.4: + dependencies: + '@types/markdown-it': 14.1.2 + markdown-it: 14.1.1 + prosemirror-model: 1.25.4 + + prosemirror-menu@1.3.0: + dependencies: + crelt: 1.0.6 + prosemirror-commands: 1.7.1 + prosemirror-history: 1.5.0 + prosemirror-state: 1.4.4 + + prosemirror-model@1.25.4: + dependencies: + orderedmap: 2.1.1 + + prosemirror-schema-basic@1.2.4: + dependencies: + prosemirror-model: 1.25.4 + + prosemirror-schema-list@1.5.1: + dependencies: + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + + prosemirror-state@1.4.4: + dependencies: + prosemirror-model: 1.25.4 + prosemirror-transform: 1.11.0 + prosemirror-view: 1.41.7 + + prosemirror-tables@1.8.5: + dependencies: + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 + prosemirror-view: 1.41.7 + + prosemirror-trailing-node@3.0.0(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.7): + dependencies: + '@remirror/core-constants': 3.0.0 + escape-string-regexp: 4.0.0 + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-view: 1.41.7 + + prosemirror-transform@1.11.0: + dependencies: + prosemirror-model: 1.25.4 + + prosemirror-view@1.41.7: + dependencies: + prosemirror-model: 1.25.4 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.11.0 proto-list@1.2.4: {} proxy-from-env@1.1.0: {} + punycode.js@2.3.1: {} + punycode@2.3.1: {} - qs@6.14.0: + qs@6.15.0: dependencies: side-channel: 1.1.0 - quansync@0.2.10: {} + quansync@0.2.11: {} queue-microtask@1.2.3: {} - randombytes@2.1.0: - dependencies: - safe-buffer: 5.2.1 - read-package-json-fast@4.0.0: dependencies: json-parse-even-better-errors: 4.0.0 @@ -4644,7 +5455,7 @@ snapshots: readdirp@4.1.2: {} - reduce-configs@1.1.0: {} + reduce-configs@1.1.1: {} require-from-string@2.0.2: {} @@ -4654,32 +5465,39 @@ snapshots: rfdc@1.4.1: {} - rollup@4.44.1: + rollup@4.60.0: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.44.1 - '@rollup/rollup-android-arm64': 4.44.1 - '@rollup/rollup-darwin-arm64': 4.44.1 - '@rollup/rollup-darwin-x64': 4.44.1 - '@rollup/rollup-freebsd-arm64': 4.44.1 - '@rollup/rollup-freebsd-x64': 4.44.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.44.1 - '@rollup/rollup-linux-arm-musleabihf': 4.44.1 - '@rollup/rollup-linux-arm64-gnu': 4.44.1 - '@rollup/rollup-linux-arm64-musl': 4.44.1 - '@rollup/rollup-linux-loongarch64-gnu': 4.44.1 - '@rollup/rollup-linux-powerpc64le-gnu': 4.44.1 - '@rollup/rollup-linux-riscv64-gnu': 4.44.1 - '@rollup/rollup-linux-riscv64-musl': 4.44.1 - '@rollup/rollup-linux-s390x-gnu': 4.44.1 - '@rollup/rollup-linux-x64-gnu': 4.44.1 - '@rollup/rollup-linux-x64-musl': 4.44.1 - '@rollup/rollup-win32-arm64-msvc': 4.44.1 - '@rollup/rollup-win32-ia32-msvc': 4.44.1 - '@rollup/rollup-win32-x64-msvc': 4.44.1 + '@rollup/rollup-android-arm-eabi': 4.60.0 + '@rollup/rollup-android-arm64': 4.60.0 + '@rollup/rollup-darwin-arm64': 4.60.0 + '@rollup/rollup-darwin-x64': 4.60.0 + '@rollup/rollup-freebsd-arm64': 4.60.0 + '@rollup/rollup-freebsd-x64': 4.60.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.60.0 + '@rollup/rollup-linux-arm-musleabihf': 4.60.0 + '@rollup/rollup-linux-arm64-gnu': 4.60.0 + '@rollup/rollup-linux-arm64-musl': 4.60.0 + '@rollup/rollup-linux-loong64-gnu': 4.60.0 + '@rollup/rollup-linux-loong64-musl': 4.60.0 + '@rollup/rollup-linux-ppc64-gnu': 4.60.0 + '@rollup/rollup-linux-ppc64-musl': 4.60.0 + '@rollup/rollup-linux-riscv64-gnu': 4.60.0 + '@rollup/rollup-linux-riscv64-musl': 4.60.0 + '@rollup/rollup-linux-s390x-gnu': 4.60.0 + '@rollup/rollup-linux-x64-gnu': 4.60.0 + '@rollup/rollup-linux-x64-musl': 4.60.0 + '@rollup/rollup-openbsd-x64': 4.60.0 + '@rollup/rollup-openharmony-arm64': 4.60.0 + '@rollup/rollup-win32-arm64-msvc': 4.60.0 + '@rollup/rollup-win32-ia32-msvc': 4.60.0 + '@rollup/rollup-win32-x64-gnu': 4.60.0 + '@rollup/rollup-win32-x64-msvc': 4.60.0 fsevents: 2.3.3 + rope-sequence@1.3.4: {} + rrweb-cssom@0.8.0: {} run-parallel@1.2.0: @@ -4690,126 +5508,119 @@ snapshots: dependencies: tslib: 2.8.1 - safe-buffer@5.2.1: {} - safer-buffer@2.1.2: {} - sass-embedded-android-arm64@1.89.0: - optional: true - - sass-embedded-android-arm@1.89.0: - optional: true - - sass-embedded-android-ia32@1.89.0: + sass-embedded-all-unknown@1.98.0: + dependencies: + sass: 1.98.0 optional: true - sass-embedded-android-riscv64@1.89.0: + sass-embedded-android-arm64@1.98.0: optional: true - sass-embedded-android-x64@1.89.0: + sass-embedded-android-arm@1.98.0: optional: true - sass-embedded-darwin-arm64@1.89.0: + sass-embedded-android-riscv64@1.98.0: optional: true - sass-embedded-darwin-x64@1.89.0: + sass-embedded-android-x64@1.98.0: optional: true - sass-embedded-linux-arm64@1.89.0: + sass-embedded-darwin-arm64@1.98.0: optional: true - sass-embedded-linux-arm@1.89.0: + sass-embedded-darwin-x64@1.98.0: optional: true - sass-embedded-linux-ia32@1.89.0: + sass-embedded-linux-arm64@1.98.0: optional: true - sass-embedded-linux-musl-arm64@1.89.0: + sass-embedded-linux-arm@1.98.0: optional: true - sass-embedded-linux-musl-arm@1.89.0: + sass-embedded-linux-musl-arm64@1.98.0: optional: true - sass-embedded-linux-musl-ia32@1.89.0: + sass-embedded-linux-musl-arm@1.98.0: optional: true - sass-embedded-linux-musl-riscv64@1.89.0: + sass-embedded-linux-musl-riscv64@1.98.0: optional: true - sass-embedded-linux-musl-x64@1.89.0: + sass-embedded-linux-musl-x64@1.98.0: optional: true - sass-embedded-linux-riscv64@1.89.0: + sass-embedded-linux-riscv64@1.98.0: optional: true - sass-embedded-linux-x64@1.89.0: + sass-embedded-linux-x64@1.98.0: optional: true - sass-embedded-win32-arm64@1.89.0: + sass-embedded-unknown-all@1.98.0: + dependencies: + sass: 1.98.0 optional: true - sass-embedded-win32-ia32@1.89.0: + sass-embedded-win32-arm64@1.98.0: optional: true - sass-embedded-win32-x64@1.89.0: + sass-embedded-win32-x64@1.98.0: optional: true - sass-embedded@1.89.0: + sass-embedded@1.98.0: dependencies: - '@bufbuild/protobuf': 2.5.2 - buffer-builder: 0.2.0 + '@bufbuild/protobuf': 2.11.0 colorjs.io: 0.5.2 - immutable: 5.1.3 + immutable: 5.1.5 rxjs: 7.8.2 supports-color: 8.1.1 sync-child-process: 1.0.2 varint: 6.0.0 optionalDependencies: - sass-embedded-android-arm: 1.89.0 - sass-embedded-android-arm64: 1.89.0 - sass-embedded-android-ia32: 1.89.0 - sass-embedded-android-riscv64: 1.89.0 - sass-embedded-android-x64: 1.89.0 - sass-embedded-darwin-arm64: 1.89.0 - sass-embedded-darwin-x64: 1.89.0 - sass-embedded-linux-arm: 1.89.0 - sass-embedded-linux-arm64: 1.89.0 - sass-embedded-linux-ia32: 1.89.0 - sass-embedded-linux-musl-arm: 1.89.0 - sass-embedded-linux-musl-arm64: 1.89.0 - sass-embedded-linux-musl-ia32: 1.89.0 - sass-embedded-linux-musl-riscv64: 1.89.0 - sass-embedded-linux-musl-x64: 1.89.0 - sass-embedded-linux-riscv64: 1.89.0 - sass-embedded-linux-x64: 1.89.0 - sass-embedded-win32-arm64: 1.89.0 - sass-embedded-win32-ia32: 1.89.0 - sass-embedded-win32-x64: 1.89.0 - - sass@1.89.2: + sass-embedded-all-unknown: 1.98.0 + sass-embedded-android-arm: 1.98.0 + sass-embedded-android-arm64: 1.98.0 + sass-embedded-android-riscv64: 1.98.0 + sass-embedded-android-x64: 1.98.0 + sass-embedded-darwin-arm64: 1.98.0 + sass-embedded-darwin-x64: 1.98.0 + sass-embedded-linux-arm: 1.98.0 + sass-embedded-linux-arm64: 1.98.0 + sass-embedded-linux-musl-arm: 1.98.0 + sass-embedded-linux-musl-arm64: 1.98.0 + sass-embedded-linux-musl-riscv64: 1.98.0 + sass-embedded-linux-musl-x64: 1.98.0 + sass-embedded-linux-riscv64: 1.98.0 + sass-embedded-linux-x64: 1.98.0 + sass-embedded-unknown-all: 1.98.0 + sass-embedded-win32-arm64: 1.98.0 + sass-embedded-win32-x64: 1.98.0 + + sass@1.98.0: dependencies: chokidar: 4.0.3 - immutable: 5.1.3 + immutable: 5.1.5 source-map-js: 1.2.1 optionalDependencies: - '@parcel/watcher': 2.5.1 + '@parcel/watcher': 2.5.6 saxes@6.0.0: dependencies: xmlchars: 2.2.0 - schema-utils@4.3.2: + schema-utils@4.3.3: dependencies: '@types/json-schema': 7.0.15 - ajv: 8.17.1 - ajv-formats: 2.1.1(ajv@8.17.1) - ajv-keywords: 5.1.0(ajv@8.17.1) - - semver@7.7.2: {} + ajv: 8.18.0 + ajv-formats: 2.1.1(ajv@8.18.0) + ajv-keywords: 5.1.0(ajv@8.18.0) - serialize-javascript@6.0.2: + scroll-into-view-if-needed@3.1.0: dependencies: - randombytes: 2.1.0 + compute-scroll-into-view: 3.1.1 + + semver@7.7.4: {} shebang-command@2.0.0: dependencies: @@ -4864,7 +5675,7 @@ snapshots: stackback@0.0.2: {} - std-env@3.9.0: {} + std-env@3.10.0: {} string-width@4.2.3: dependencies: @@ -4876,19 +5687,19 @@ snapshots: dependencies: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 - strip-ansi: 7.1.0 + strip-ansi: 7.2.0 strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 - strip-ansi@7.1.0: + strip-ansi@7.2.0: dependencies: - ansi-regex: 6.1.0 + ansi-regex: 6.2.2 strip-json-comments@3.1.1: {} - strip-literal@3.0.0: + strip-literal@3.1.0: dependencies: js-tokens: 9.0.1 @@ -4908,29 +5719,28 @@ snapshots: sync-child-process@1.0.2: dependencies: - sync-message-port: 1.1.3 + sync-message-port: 1.2.0 - sync-message-port@1.1.3: {} + sync-message-port@1.2.0: {} - synckit@0.11.8: + synckit@0.11.12: dependencies: - '@pkgr/core': 0.2.7 + '@pkgr/core': 0.2.9 - tapable@2.2.2: {} + tapable@2.3.2: {} - terser-webpack-plugin@5.3.14(webpack@5.99.9): + terser-webpack-plugin@5.4.0(webpack@5.105.4): dependencies: - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 - schema-utils: 4.3.2 - serialize-javascript: 6.0.2 - terser: 5.43.1 - webpack: 5.99.9 + schema-utils: 4.3.3 + terser: 5.46.1 + webpack: 5.105.4 - terser@5.43.1: + terser@5.46.1: dependencies: - '@jridgewell/source-map': 0.3.6 - acorn: 8.15.0 + '@jridgewell/source-map': 0.3.11 + acorn: 8.16.0 commander: 2.20.3 source-map-support: 0.5.21 @@ -4938,18 +5748,18 @@ snapshots: tinyexec@0.3.2: {} - tinyexec@1.0.1: {} + tinyexec@1.0.4: {} - tinyglobby@0.2.14: + tinyglobby@0.2.15: dependencies: - fdir: 6.4.6(picomatch@4.0.2) - picomatch: 4.0.2 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 tinypool@1.1.1: {} tinyrainbow@2.0.0: {} - tinyspy@4.0.3: {} + tinyspy@4.0.4: {} tldts-core@6.1.86: {} @@ -4969,7 +5779,7 @@ snapshots: dependencies: punycode: 2.3.1 - ts-api-utils@2.1.0(typescript@5.8.3): + ts-api-utils@2.5.0(typescript@5.8.3): dependencies: typescript: 5.8.3 @@ -4979,43 +5789,47 @@ snapshots: dependencies: prelude-ls: 1.2.1 - typescript-eslint@8.35.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3): + typescript-eslint@8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.35.0(@typescript-eslint/parser@8.35.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/parser': 8.35.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/utils': 8.35.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) - eslint: 9.29.0(jiti@2.4.2) + '@typescript-eslint/eslint-plugin': 8.57.2(@typescript-eslint/parser@8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3) + '@typescript-eslint/parser': 8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3) + '@typescript-eslint/typescript-estree': 8.57.2(typescript@5.8.3) + '@typescript-eslint/utils': 8.57.2(eslint@9.39.4(jiti@2.6.1))(typescript@5.8.3) + eslint: 9.39.4(jiti@2.6.1) typescript: 5.8.3 transitivePeerDependencies: - supports-color typescript@5.8.3: {} - ufo@1.6.1: {} + uc.micro@2.1.0: {} + + ufo@1.6.3: {} undici-types@6.21.0: {} - unplugin-icons@22.1.0(@vue/compiler-sfc@3.5.17): + unplugin-icons@22.5.0(@vue/compiler-sfc@3.5.30): dependencies: '@antfu/install-pkg': 1.1.0 - '@iconify/utils': 2.3.0 - debug: 4.4.1 - local-pkg: 1.1.1 - unplugin: 2.3.5 + '@iconify/utils': 3.1.0 + debug: 4.4.3 + local-pkg: 1.1.2 + unplugin: 2.3.11 optionalDependencies: - '@vue/compiler-sfc': 3.5.17 + '@vue/compiler-sfc': 3.5.30 transitivePeerDependencies: - supports-color - unplugin@2.3.5: + unplugin@2.3.11: dependencies: - acorn: 8.15.0 - picomatch: 4.0.2 + '@jridgewell/remapping': 2.3.5 + acorn: 8.16.0 + picomatch: 4.0.4 webpack-virtual-modules: 0.6.2 - update-browserslist-db@1.1.3(browserslist@4.25.1): + update-browserslist-db@1.2.3(browserslist@4.28.1): dependencies: - browserslist: 4.25.1 + browserslist: 4.28.1 escalade: 3.2.0 picocolors: 1.1.1 @@ -5027,13 +5841,13 @@ snapshots: varint@6.0.0: {} - vite-node@3.2.4(@types/node@20.19.1)(jiti@2.4.2)(sass-embedded@1.89.0)(sass@1.89.2)(terser@5.43.1): + vite-node@3.2.4(@types/node@20.19.37)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.1): dependencies: cac: 6.7.14 - debug: 4.4.1 + debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.0.0(@types/node@20.19.1)(jiti@2.4.2)(sass-embedded@1.89.0)(sass@1.89.2)(terser@5.43.1) + vite: 7.3.1(@types/node@20.19.37)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.1) transitivePeerDependencies: - '@types/node' - jiti @@ -5048,49 +5862,49 @@ snapshots: - tsx - yaml - vite@7.0.0(@types/node@20.19.1)(jiti@2.4.2)(sass-embedded@1.89.0)(sass@1.89.2)(terser@5.43.1): + vite@7.3.1(@types/node@20.19.37)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.1): dependencies: - esbuild: 0.25.5 - fdir: 6.4.6(picomatch@4.0.2) - picomatch: 4.0.2 - postcss: 8.5.6 - rollup: 4.44.1 - tinyglobby: 0.2.14 + esbuild: 0.27.4 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + postcss: 8.5.8 + rollup: 4.60.0 + tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 20.19.1 + '@types/node': 20.19.37 fsevents: 2.3.3 - jiti: 2.4.2 - sass: 1.89.2 - sass-embedded: 1.89.0 - terser: 5.43.1 + jiti: 2.6.1 + sass: 1.98.0 + sass-embedded: 1.98.0 + terser: 5.46.1 - vitest@3.2.4(@types/node@20.19.1)(jiti@2.4.2)(jsdom@26.1.0)(sass-embedded@1.89.0)(sass@1.89.2)(terser@5.43.1): + vitest@3.2.4(@types/node@20.19.37)(jiti@2.6.1)(jsdom@26.1.0)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.1): dependencies: - '@types/chai': 5.2.2 + '@types/chai': 5.2.3 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.0.0(@types/node@20.19.1)(jiti@2.4.2)(sass-embedded@1.89.0)(sass@1.89.2)(terser@5.43.1)) + '@vitest/mocker': 3.2.4(vite@7.3.1(@types/node@20.19.37)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.1)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 '@vitest/spy': 3.2.4 '@vitest/utils': 3.2.4 - chai: 5.2.0 - debug: 4.4.1 - expect-type: 1.2.1 - magic-string: 0.30.17 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.3.0 + magic-string: 0.30.21 pathe: 2.0.3 - picomatch: 4.0.2 - std-env: 3.9.0 + picomatch: 4.0.4 + std-env: 3.10.0 tinybench: 2.9.0 tinyexec: 0.3.2 - tinyglobby: 0.2.14 + tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.0.0(@types/node@20.19.1)(jiti@2.4.2)(sass-embedded@1.89.0)(sass@1.89.2)(terser@5.43.1) - vite-node: 3.2.4(@types/node@20.19.1)(jiti@2.4.2)(sass-embedded@1.89.0)(sass@1.89.2)(terser@5.43.1) + vite: 7.3.1(@types/node@20.19.37)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.1) + vite-node: 3.2.4(@types/node@20.19.37)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.46.1) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 20.19.1 + '@types/node': 20.19.37 jsdom: 26.1.0 transitivePeerDependencies: - jiti @@ -5108,72 +5922,73 @@ snapshots: vscode-uri@3.1.0: {} - vue-component-type-helpers@2.2.10: {} + vue-component-type-helpers@2.2.12: {} - vue-eslint-parser@10.1.4(eslint@9.29.0(jiti@2.4.2)): + vue-eslint-parser@10.4.0(eslint@9.39.4(jiti@2.6.1)): dependencies: - debug: 4.4.1 - eslint: 9.29.0(jiti@2.4.2) - eslint-scope: 8.4.0 - eslint-visitor-keys: 4.2.1 - espree: 10.4.0 - esquery: 1.6.0 - lodash: 4.17.21 - semver: 7.7.2 + debug: 4.4.3 + eslint: 9.39.4(jiti@2.6.1) + eslint-scope: 9.1.2 + eslint-visitor-keys: 5.0.1 + espree: 11.2.0 + esquery: 1.7.0 + semver: 7.7.4 transitivePeerDependencies: - supports-color - vue-loader@17.4.2(@vue/compiler-sfc@3.5.17)(vue@3.5.17(typescript@5.8.3))(webpack@5.99.9): + vue-loader@17.4.2(@vue/compiler-sfc@3.5.30)(vue@3.5.30(typescript@5.8.3))(webpack@5.105.4): dependencies: chalk: 4.1.2 hash-sum: 2.0.0 - watchpack: 2.4.4 - webpack: 5.99.9 + watchpack: 2.5.1 + webpack: 5.105.4 optionalDependencies: - '@vue/compiler-sfc': 3.5.17 - vue: 3.5.17(typescript@5.8.3) + '@vue/compiler-sfc': 3.5.30 + vue: 3.5.30(typescript@5.8.3) - vue-resize@2.0.0-alpha.1(vue@3.5.17(typescript@5.8.3)): + vue-resize@2.0.0-alpha.1(vue@3.5.30(typescript@5.8.3)): dependencies: - vue: 3.5.17(typescript@5.8.3) + vue: 3.5.30(typescript@5.8.3) - vue-router@4.6.4(vue@3.5.17(typescript@5.8.3)): + vue-router@4.6.4(vue@3.5.30(typescript@5.8.3)): dependencies: '@vue/devtools-api': 6.6.4 - vue: 3.5.17(typescript@5.8.3) + vue: 3.5.30(typescript@5.8.3) - vue-tsc@2.2.10(typescript@5.8.3): + vue-tsc@2.2.12(typescript@5.8.3): dependencies: '@volar/typescript': 2.4.15 - '@vue/language-core': 2.2.10(typescript@5.8.3) + '@vue/language-core': 2.2.12(typescript@5.8.3) typescript: 5.8.3 - vue@3.5.17(typescript@5.8.3): + vue@3.5.30(typescript@5.8.3): dependencies: - '@vue/compiler-dom': 3.5.17 - '@vue/compiler-sfc': 3.5.17 - '@vue/runtime-dom': 3.5.17 - '@vue/server-renderer': 3.5.17(vue@3.5.17(typescript@5.8.3)) - '@vue/shared': 3.5.17 + '@vue/compiler-dom': 3.5.30 + '@vue/compiler-sfc': 3.5.30 + '@vue/runtime-dom': 3.5.30 + '@vue/server-renderer': 3.5.30(vue@3.5.30(typescript@5.8.3)) + '@vue/shared': 3.5.30 optionalDependencies: typescript: 5.8.3 + w3c-keyname@2.2.8: {} + w3c-xmlserializer@5.0.0: dependencies: xml-name-validator: 5.0.0 - watchpack@2.4.4: + watchpack@2.5.1: dependencies: glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 webidl-conversions@7.0.0: {} - webpack-sources@3.3.3: {} + webpack-sources@3.3.4: {} webpack-virtual-modules@0.6.2: {} - webpack@5.99.9: + webpack@5.105.4: dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.8 @@ -5181,24 +5996,25 @@ snapshots: '@webassemblyjs/ast': 1.14.1 '@webassemblyjs/wasm-edit': 1.14.1 '@webassemblyjs/wasm-parser': 1.14.1 - acorn: 8.15.0 - browserslist: 4.25.1 + acorn: 8.16.0 + acorn-import-phases: 1.0.4(acorn@8.16.0) + browserslist: 4.28.1 chrome-trace-event: 1.0.4 - enhanced-resolve: 5.18.2 - es-module-lexer: 1.7.0 + enhanced-resolve: 5.20.1 + es-module-lexer: 2.0.0 eslint-scope: 5.1.1 events: 3.3.0 glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 json-parse-even-better-errors: 2.3.1 - loader-runner: 4.3.0 + loader-runner: 4.3.1 mime-types: 2.1.35 neo-async: 2.6.2 - schema-utils: 4.3.2 - tapable: 2.2.2 - terser-webpack-plugin: 5.3.14(webpack@5.99.9) - watchpack: 2.4.4 - webpack-sources: 3.3.3 + schema-utils: 4.3.3 + tapable: 2.3.2 + terser-webpack-plugin: 5.4.0(webpack@5.105.4) + watchpack: 2.5.1 + webpack-sources: 3.3.4 transitivePeerDependencies: - '@swc/core' - esbuild @@ -5221,7 +6037,7 @@ snapshots: which@5.0.0: dependencies: - isexe: 3.1.1 + isexe: 3.1.5 why-is-node-running@2.3.0: dependencies: @@ -5238,11 +6054,11 @@ snapshots: wrap-ansi@8.1.0: dependencies: - ansi-styles: 6.2.1 + ansi-styles: 6.2.3 string-width: 5.1.2 - strip-ansi: 7.1.0 + strip-ansi: 7.2.0 - ws@8.18.2: {} + ws@8.20.0: {} xml-name-validator@4.0.0: {} @@ -5250,4 +6066,13 @@ snapshots: xmlchars@2.2.0: {} + y-protocols@1.0.7(yjs@13.6.30): + dependencies: + lib0: 0.2.117 + yjs: 13.6.30 + + yjs@13.6.30: + dependencies: + lib0: 0.2.117 + yocto-queue@0.1.0: {} From 9ac3fdf71bcfa474537c1339a71ec5128cb79366 Mon Sep 17 00:00:00 2001 From: HowieHz Date: Wed, 25 Mar 2026 09:43:11 +0800 Subject: [PATCH 07/19] Refactor tag SEO processing; update README --- README.md | 22 ++++++++++--------- .../timefactor/process/TimeFactorProcess.java | 12 +++------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 8dbc5e6..f85e86d 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,8 @@ - **演示站点**:[https://www.lik.cc/](https://www.lik.cc/) - **文档**:[https://docs.lik.cc/](https://docs.lik.cc/) -- **QQ 交流群**:[![QQ群](https://www.lik.cc/upload/iShot_2025-03-03_16.03.00.png)](https://www.lik.cc/upload/iShot_2025-03-03_16.03.00.png) +- **QQ 交流群**: + - [![QQ群](https://www.lik.cc/upload/iShot_2025-03-03_16.03.00.png)](https://www.lik.cc/upload/iShot_2025-03-03_16.03.00.png) ## 功能特性 @@ -49,10 +50,11 @@ | 分类列表页 | `categories` | 站点信息 | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/categories) | | 分类详情页 | `category` | Category | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/category) | | 标签列表页 | `tags` | 站点信息 | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/tags) | -| 标签详情页 | `tag` | Tag | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/tag) | | 归档页 | `archives` | 站点信息 | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/archives) | | 作者页 | `author` | User | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/author) | +> **暂不支持**:标签详情页(`tag`)——Halo 的标签路由未注入 `_templateId` 上下文变量,插件无法识别该页面类型。 + ### 第三方插件页面 | 页面类型 | 模板 ID | 数据来源 | 插件 | @@ -68,14 +70,14 @@ 并非所有页面都拥有完整的 SEO 字段。当某个字段为空时,对应的 meta/script 标签会被**自动省略**(不输出空值标签),以保证结构化数据的有效性。 -| 页面类型 | `og:type` | Schema.org `@type` | 发布/更新时间 | 作者 | 关键词 | 说明 | -|--------|-----------|--------------------|:-------:|:--:|:-------:|----------------------------| -| 文章详情页 | `article` | `BlogPosting` | ✅ | ✅ | ✅ 文章标签 | 字段最完整的页面类型 | -| 独立页面 | `article` | `BlogPosting` | ✅ | ✅ | ✅ 站点关键词 | 独立页面无标签,关键词回退到站点级 | -| 分类详情页 | `website` | `WebPage` | ❌ | ❌ | ✅ 分类名 | 分类无发布时间和作者;描述取自分类描述字段 | -| 标签详情页 | `website` | `WebPage` | ❌ | ❌ | ✅ 标签名 | 标签无发布时间和作者;描述取自标签描述字段 | -| 作者页 | `profile` | `ProfilePage` | ❌ | ✅ | ✅ 作者名 | 作者页无发布时间 | -| 列表/聚合页 | `website` | `WebPage` | ❌ | ❌ | ✅ 站点关键词 | 首页、分类列表、标签列表、归档页、所有第三方插件页面 | +| 页面类型 | `og:type` | Schema.org `@type` | 发布/更新时间 | 作者 | 关键词 | 说明 | +|--------|-----------|--------------------|:-------:|:--:|:-------:|--------------------------------| +| 文章详情页 | `article` | `BlogPosting` | ✅ | ✅ | ✅ 文章标签 | 字段最完整的页面类型 | +| 独立页面 | `article` | `BlogPosting` | ✅ | ✅ | ✅ 站点关键词 | 独立页面无标签,关键词回退到站点级 | +| 分类详情页 | `website` | `WebPage` | ❌ | ❌ | ✅ 分类名 | 分类无发布时间和作者;描述取自分类描述字段 | +| 标签详情页 | `website` | `WebPage` | ❌ | ❌ | ✅ 标签名 | (⚠️ 暂不支持)标签无发布时间和作者;描述取自标签描述字段 | +| 作者页 | `profile` | `ProfilePage` | ❌ | ✅ | ✅ 作者名 | 作者页无发布时间 | +| 列表/聚合页 | `website` | `WebPage` | ❌ | ❌ | ✅ 站点关键词 | 首页、分类列表、标签列表、归档页、所有第三方插件页面 | > **省略规则**:当发布/更新时间为空时,`og:release_date`、`og:modified_time`、`bytedance:published_time`、 `bytedance:updated_time` 不输出;百度时间因子 `\n"); return sb.toString(); @@ -970,10 +1010,12 @@ private String genSchemaOrgScript(SeoData seoData) { .append(JsonEscape.escapeJson(seoData.description())).append("\""); } if (hasValue(seoData.googlePubDate())) { - sb.append(",\n \"datePublished\": \"").append(seoData.googlePubDate()).append("\""); + sb.append(",\n \"datePublished\": \"") + .append(JsonEscape.escapeJson(seoData.googlePubDate())).append("\""); } if (hasValue(seoData.googleUpdDate())) { - sb.append(",\n \"dateModified\": \"").append(seoData.googleUpdDate()).append("\""); + sb.append(",\n \"dateModified\": \"") + .append(JsonEscape.escapeJson(seoData.googleUpdDate())).append("\""); } if (hasValue(seoData.author())) { sb.append(",\n \"author\": {\n"); @@ -1114,7 +1156,7 @@ private String formatDateTime(Instant instant, DateTimeFormatter formatter, Zone */ private String getNameVariable(ITemplateContext context) { return Optional.ofNullable(context.getVariable("name")).map(Object::toString) - .filter(s -> !s.isEmpty()).orElse(null); + .filter(s -> !s.isBlank()).orElse(null); } /** @@ -1132,8 +1174,8 @@ private String getNameVariable(ITemplateContext context) { private String getMetadataNameFromVo(ITemplateContext context, String voVariableName) { var vo = context.getVariable(voVariableName); if (vo instanceof ExtensionVoOperator evo) { - return Optional.of(evo.getMetadata()).map(MetadataOperator::getName) - .filter(s -> !s.isEmpty()).orElse(null); + return Optional.ofNullable(evo.getMetadata()).map(MetadataOperator::getName) + .filter(s -> !s.isBlank()).orElse(null); } return null; } diff --git a/src/main/java/cc/lik/timefactor/service/SettingConfigGetter.java b/src/main/java/cc/lik/timefactor/service/SettingConfigGetter.java index f082916..181652e 100644 --- a/src/main/java/cc/lik/timefactor/service/SettingConfigGetter.java +++ b/src/main/java/cc/lik/timefactor/service/SettingConfigGetter.java @@ -31,5 +31,6 @@ class BasicConfig { private String twitterCreatorUsername; private String twitterCreatorUserId; private String defaultImage; + private String defaultAuthor; } } diff --git a/src/main/resources/extensions/settings.yaml b/src/main/resources/extensions/settings.yaml index 673ebfa..346de2b 100644 --- a/src/main/resources/extensions/settings.yaml +++ b/src/main/resources/extensions/settings.yaml @@ -154,4 +154,11 @@ spec: name: defaultImage id: defaultImage key: defaultImage - help: "当没有设置封面图时使用" \ No newline at end of file + help: "当没有设置封面图时使用" + - $formkit: text + label: 默认作者 + name: defaultAuthor + id: defaultAuthor + key: defaultAuthor + value: "" + help: "列表页、分类页、标签页等无具体作者的页面使用;留空时回退到站点名称" \ No newline at end of file From efd07b4fc31d621a256d1c4f0b1be42864a18f05 Mon Sep 17 00:00:00 2001 From: HowieHz Date: Wed, 25 Mar 2026 13:15:31 +0800 Subject: [PATCH 09/19] Use Tag description fallback; bump platform --- build.gradle | 2 +- .../cc/lik/timefactor/process/TimeFactorProcess.java | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index f4a15f2..da64c35 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ repositories { } dependencies { - implementation platform('run.halo.tools.platform:plugin:2.23.0') + implementation platform('run.halo.tools.platform:plugin:2.23.1') compileOnly 'run.halo.app:api' testImplementation 'run.halo.app:api' diff --git a/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java b/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java index 9754b22..4be572a 100644 --- a/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java +++ b/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java @@ -139,6 +139,7 @@ public class TimeFactorProcess implements TemplateHeadProcessor { private static final String PAGE_DESC_DOUBAN = "豆瓣内容页面"; private static final String PAGE_LABEL_BANGUMI = "番剧"; private static final String PAGE_DESC_BANGUMI = "番剧数据页面"; + private static final String TAG_DESC_PREFIX = "标签: "; private static final String CATEGORY_DESC_PREFIX = "分类: "; private static final String AUTHOR_DESC_PREFIX = "作者: "; @@ -734,9 +735,9 @@ private Mono buildSeoDataForTag(Tag tag) { var displayName = tag.getSpec().getDisplayName(); var siteName = systemInfo.getTitle(); var title = hasValue(siteName) ? displayName + " - " + siteName : displayName; - // 描述:优先 Tag 自身的 description,回退到 "标签: displayName"(等待 API 升级 2.23.1) - // var description = - // firstNonBlank(tag.getSpec().getDescription, "标签: " + displayName); + // 描述:优先 Tag 自身的 description,回退到 "标签: displayName" + var description = + firstNonBlank(tag.getSpec().getDescription(), TAG_DESC_PREFIX + displayName); // status 字段在 Reconciler 首次处理前为 null,使用 getStatusOrDefault() 安全获取 var pageUrl = processPermalink(tag.getStatusOrDefault().getPermalink()); @@ -747,8 +748,8 @@ private Mono buildSeoDataForTag(Tag tag) { // 标签没有独立的作者和发布时间;作者优先使用默认作者,回退到站点名称;pageType 为 website var author = firstNonBlank(config.getDefaultAuthor(), siteName); - return new SeoData(title, null, coverUrl, pageUrl, author, null, null, null, null, - siteName, siteLogo, displayName, "website"); + return new SeoData(title, description, coverUrl, pageUrl, author, null, null, null, + null, siteName, siteLogo, displayName, "website"); }); } From 255fc090b4b67f4473dba8692da86459eee67ade Mon Sep 17 00:00:00 2001 From: HowieHz Date: Wed, 25 Mar 2026 13:28:31 +0800 Subject: [PATCH 10/19] Add title format setting and formatter --- .../timefactor/process/TimeFactorProcess.java | 39 ++++++++++++++----- .../service/SettingConfigGetter.java | 1 + src/main/resources/extensions/settings.yaml | 9 ++++- 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java b/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java index 4be572a..5de9b57 100644 --- a/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java +++ b/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java @@ -1,11 +1,6 @@ package cc.lik.timefactor.process; import cc.lik.timefactor.service.SettingConfigGetter; -import java.time.Instant; -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; -import java.util.Arrays; -import java.util.Optional; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -34,6 +29,11 @@ import run.halo.app.theme.dialect.TemplateHeadProcessor; import run.halo.app.theme.finders.vo.ExtensionVoOperator; import run.halo.app.theme.router.ModelConst; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.Arrays; +import java.util.Optional; /** * 模板 ID 枚举,映射 Halo 路由系统中的 {@code _templateId} 到具体页面类型。 @@ -508,8 +508,7 @@ private Mono buildListPageSeoData(ITemplateContext context, IModel model, var siteName = systemInfo.getTitle(); var siteLogo = processPermalink(systemInfo.getLogo()); - // 标题格式:pageTitle - siteName - var title = hasValue(siteName) ? pageTitle + " - " + siteName : pageTitle; + var title = formatTitle(pageTitle, siteName, config.getTitleFormat()); // 描述优先级:页面描述 > 站点 SEO 描述 > 页面标题 var siteDesc = @@ -689,7 +688,7 @@ private Mono buildSeoDataForCategory(Category category) { var displayName = category.getSpec().getDisplayName(); var siteName = systemInfo.getTitle(); - var title = hasValue(siteName) ? displayName + " - " + siteName : displayName; + var title = formatTitle(displayName, siteName, config.getTitleFormat()); // 分类有 description 字段,回退到 "分类: displayName" var description = firstNonBlank(category.getSpec().getDescription(), @@ -734,7 +733,7 @@ private Mono buildSeoDataForTag(Tag tag) { var displayName = tag.getSpec().getDisplayName(); var siteName = systemInfo.getTitle(); - var title = hasValue(siteName) ? displayName + " - " + siteName : displayName; + var title = formatTitle(displayName, siteName, config.getTitleFormat()); // 描述:优先 Tag 自身的 description,回退到 "标签: displayName" var description = firstNonBlank(tag.getSpec().getDescription(), TAG_DESC_PREFIX + displayName); @@ -779,7 +778,7 @@ private Mono buildSeoDataForAuthor(User user) { var displayName = user.getSpec().getDisplayName(); var siteName = systemInfo.getTitle(); - var title = hasValue(siteName) ? displayName + " - " + siteName : displayName; + var title = formatTitle(displayName, siteName, config.getTitleFormat()); var description = firstNonBlank(user.getSpec().getBio(), AUTHOR_DESC_PREFIX + displayName); @@ -1181,6 +1180,26 @@ private String getMetadataNameFromVo(ITemplateContext context, String voVariable return null; } + /** + * 根据配置模板格式化页面标题。 + * + *

    用 {@code %TITLE%} 替换页面标题,{@code %SITENAME%} 替换站点名称。 + * 当站点名称为空时,直接返回 title,避免出现 "标题 - " 这类残缺输出。 + * 当 format 为空时同样退回到 title。 + * + * @param title 页面标题 + * @param siteName 站点名称 + * @param format 配置的标题格式模板,如 {@code "%TITLE% - %SITENAME%"} + * @return 格式化后的标题 + */ + private String formatTitle(String title, String siteName, String format) { + var t = hasValue(title) ? title : ""; + if (!hasValue(format) || !hasValue(siteName)) { + return t; + } + return format.replace("%TITLE%", t).replace("%SITENAME%", siteName); + } + /** * 判断字符串是否有实际内容(非 null 且非空白)。 * 用于输出阶段决定是否生成对应的 SEO 标签:无值则跳过,避免输出空标签。 diff --git a/src/main/java/cc/lik/timefactor/service/SettingConfigGetter.java b/src/main/java/cc/lik/timefactor/service/SettingConfigGetter.java index 181652e..e16e007 100644 --- a/src/main/java/cc/lik/timefactor/service/SettingConfigGetter.java +++ b/src/main/java/cc/lik/timefactor/service/SettingConfigGetter.java @@ -32,5 +32,6 @@ class BasicConfig { private String twitterCreatorUserId; private String defaultImage; private String defaultAuthor; + private String titleFormat; } } diff --git a/src/main/resources/extensions/settings.yaml b/src/main/resources/extensions/settings.yaml index 346de2b..d6fd7eb 100644 --- a/src/main/resources/extensions/settings.yaml +++ b/src/main/resources/extensions/settings.yaml @@ -161,4 +161,11 @@ spec: id: defaultAuthor key: defaultAuthor value: "" - help: "列表页、分类页、标签页等无具体作者的页面使用;留空时回退到站点名称" \ No newline at end of file + help: "列表页、分类页、标签页等无具体作者的页面使用;留空时回退到站点名称" + - $formkit: text + label: 标题格式 + name: titleFormat + id: titleFormat + key: titleFormat + value: "%TITLE% - %SITENAME%" + help: "列表页、分类页、标签页、作者页的标题格式模板;%TITLE% 为页面标题,%SITENAME% 为站点名称;站点名称为空时自动退回仅显示 %TITLE%" \ No newline at end of file From 942acca1a4695c9127e214115600b6bbc1e314d3 Mon Sep 17 00:00:00 2001 From: HowieHz Date: Wed, 25 Mar 2026 13:59:09 +0800 Subject: [PATCH 11/19] Use theme route rules for list page URLs --- .../timefactor/process/TimeFactorProcess.java | 143 +++++++++--------- 1 file changed, 74 insertions(+), 69 deletions(-) diff --git a/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java b/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java index 5de9b57..97c05b1 100644 --- a/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java +++ b/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java @@ -1,6 +1,12 @@ package cc.lik.timefactor.process; import cc.lik.timefactor.service.SettingConfigGetter; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.Arrays; +import java.util.Optional; +import java.util.function.Function; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -18,6 +24,7 @@ import run.halo.app.core.extension.content.Post; import run.halo.app.core.extension.content.SinglePage; import run.halo.app.core.extension.content.Tag; +import run.halo.app.extension.ConfigMap; import run.halo.app.extension.ListOptions; import run.halo.app.extension.MetadataOperator; import run.halo.app.extension.ReactiveExtensionClient; @@ -26,14 +33,10 @@ import run.halo.app.infra.ExternalLinkProcessor; import run.halo.app.infra.SystemInfo; import run.halo.app.infra.SystemInfoGetter; +import run.halo.app.infra.SystemSetting; import run.halo.app.theme.dialect.TemplateHeadProcessor; import run.halo.app.theme.finders.vo.ExtensionVoOperator; import run.halo.app.theme.router.ModelConst; -import java.time.Instant; -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; -import java.util.Arrays; -import java.util.Optional; /** * 模板 ID 枚举,映射 Halo 路由系统中的 {@code _templateId} 到具体页面类型。 @@ -123,22 +126,13 @@ public class TimeFactorProcess implements TemplateHeadProcessor { // ======================== 页面标签常量 ======================== private static final String PAGE_LABEL_INDEX = "首页"; private static final String PAGE_LABEL_CATEGORIES = "分类"; - private static final String PAGE_DESC_CATEGORIES = "分类导航页面"; private static final String PAGE_LABEL_ARCHIVES = "归档"; - private static final String PAGE_DESC_ARCHIVES = "内容归档页面"; private static final String PAGE_LABEL_TAGS = "标签"; - private static final String PAGE_DESC_TAGS = "标签导航页面"; private static final String PAGE_LABEL_MOMENTS = "瞬间"; - private static final String PAGE_DESC_MOMENTS = "瞬间列表页面"; - private static final String PAGE_DESC_MOMENT = "瞬间详情页面"; private static final String PAGE_LABEL_PHOTOS = "图库"; - private static final String PAGE_DESC_PHOTOS = "图库页面"; private static final String PAGE_LABEL_FRIENDS = "朋友圈"; - private static final String PAGE_DESC_FRIENDS = "朋友圈页面"; private static final String PAGE_LABEL_DOUBAN = "豆瓣"; - private static final String PAGE_DESC_DOUBAN = "豆瓣内容页面"; private static final String PAGE_LABEL_BANGUMI = "番剧"; - private static final String PAGE_DESC_BANGUMI = "番剧数据页面"; private static final String TAG_DESC_PREFIX = "标签: "; private static final String CATEGORY_DESC_PREFIX = "分类: "; private static final String AUTHOR_DESC_PREFIX = "作者: "; @@ -212,7 +206,7 @@ public Mono process(ITemplateContext context, IModel model, * 首页模板变量 */ private Mono processIndexSeoData(ITemplateContext context, IModel model) { - return buildListPageSeoData(context, model, PAGE_LABEL_INDEX, "", "/"); + return buildListPageSeoData(context, model, PAGE_LABEL_INDEX, rules -> "/"); } /** @@ -225,8 +219,8 @@ private Mono processIndexSeoData(ITemplateContext context, IModel model) { * 分类列表模板变量 */ private Mono processCategoriesSeoData(ITemplateContext context, IModel model) { - return buildListPageSeoData(context, model, PAGE_LABEL_CATEGORIES, PAGE_DESC_CATEGORIES, - "/categories"); + return buildListPageSeoData(context, model, PAGE_LABEL_CATEGORIES, + rules -> Optional.ofNullable(rules.getCategories()).orElse("/categories")); } /** @@ -239,8 +233,8 @@ private Mono processCategoriesSeoData(ITemplateContext context, IModel mod * 归档模板变量 */ private Mono processArchivesSeoData(ITemplateContext context, IModel model) { - return buildListPageSeoData(context, model, PAGE_LABEL_ARCHIVES, PAGE_DESC_ARCHIVES, - "/archives"); + return buildListPageSeoData(context, model, PAGE_LABEL_ARCHIVES, + rules -> Optional.ofNullable(rules.getArchives()).orElse("/archives")); } /** @@ -253,7 +247,8 @@ private Mono processArchivesSeoData(ITemplateContext context, IModel model * 标签列表模板变量 */ private Mono processTagsSeoData(ITemplateContext context, IModel model) { - return buildListPageSeoData(context, model, PAGE_LABEL_TAGS, PAGE_DESC_TAGS, "/tags"); + return buildListPageSeoData(context, model, PAGE_LABEL_TAGS, + rules -> Optional.ofNullable(rules.getTags()).orElse("/tags")); } /** @@ -266,8 +261,7 @@ private Mono processTagsSeoData(ITemplateContext context, IModel model) { * @see plugin-moments */ private Mono processMomentsSeoData(ITemplateContext context, IModel model) { - return buildListPageSeoData(context, model, PAGE_LABEL_MOMENTS, PAGE_DESC_MOMENTS, - "/moments"); + return buildListPageSeoData(context, model, PAGE_LABEL_MOMENTS, rules -> "/moments"); } /** @@ -279,8 +273,7 @@ private Mono processMomentsSeoData(ITemplateContext context, IModel model) */ private Mono processMomentSeoData(ITemplateContext context, IModel model) { // MomentVo 在上下文中是延迟加载的 Mono 对象,无法直接获取 metadata.name,降级使用站点信息 - return buildListPageSeoData(context, model, PAGE_LABEL_MOMENTS, PAGE_DESC_MOMENT, - "/moments"); + return buildListPageSeoData(context, model, PAGE_LABEL_MOMENTS, rules -> "/moments"); } /** @@ -292,7 +285,7 @@ private Mono processMomentSeoData(ITemplateContext context, IModel model) * @see plugin-photos */ private Mono processPhotosSeoData(ITemplateContext context, IModel model) { - return buildListPageSeoData(context, model, PAGE_LABEL_PHOTOS, PAGE_DESC_PHOTOS, "/photos"); + return buildListPageSeoData(context, model, PAGE_LABEL_PHOTOS, rules -> "/photos"); } /** @@ -305,8 +298,7 @@ private Mono processPhotosSeoData(ITemplateContext context, IModel model) * @see plugin-friends-new */ private Mono processFriendsSeoData(ITemplateContext context, IModel model) { - return buildListPageSeoData(context, model, PAGE_LABEL_FRIENDS, PAGE_DESC_FRIENDS, - "/friends"); + return buildListPageSeoData(context, model, PAGE_LABEL_FRIENDS, rules -> "/friends"); } /** @@ -323,7 +315,7 @@ private Mono processDoubanSeoData(ITemplateContext context, IModel model) // 豆瓣路由设置了 title 上下文变量(DoubanRouter.getDoubanTitle),优先使用 var doubanTitle = Optional.ofNullable(context.getVariable("title")).map(Object::toString) .filter(s -> !s.isBlank()).orElse(PAGE_LABEL_DOUBAN); - return buildListPageSeoData(context, model, doubanTitle, PAGE_DESC_DOUBAN, "/douban"); + return buildListPageSeoData(context, model, doubanTitle, rules -> "/douban"); } /** @@ -338,8 +330,7 @@ private Mono processDoubanSeoData(ITemplateContext context, IModel model) * halo-plugin-bangumi-data */ private Mono processBangumiSeoData(ITemplateContext context, IModel model) { - return buildListPageSeoData(context, model, PAGE_LABEL_BANGUMI, PAGE_DESC_BANGUMI, - "/bangumi"); + return buildListPageSeoData(context, model, PAGE_LABEL_BANGUMI, rules -> "/bangumi"); } // ======================== 详情页 SEO 处理器 ======================== @@ -493,49 +484,49 @@ private Mono processAuthorSeoData(ITemplateContext context, IModel model) * @param context Thymeleaf 模板上下文 * @param model HTML 输出模型 * @param pageTitle 页面语义标题,如 "首页"、"分类"、"标签" - * @param pageDesc 页面描述,为空时回退到站点 SEO 描述 - * @param pagePath 页面路径,如 "/"、"/categories"、"/tags" + * @param pathProvider 页面路径提供函数,接收 {@link SystemSetting.ThemeRouteRules} 以支持可配置路由前缀 */ private Mono buildListPageSeoData(ITemplateContext context, IModel model, - String pageTitle, String pageDesc, String pagePath) { + String pageTitle, Function pathProvider) { var modelFactory = context.getModelFactory(); - return Mono.zip(settingConfigGetter.getBasicConfig(), systemInfoGetter.get()) - .flatMap(tuple -> { - var config = tuple.getT1(); - var systemInfo = tuple.getT2(); - - var siteName = systemInfo.getTitle(); - var siteLogo = processPermalink(systemInfo.getLogo()); - - var title = formatTitle(pageTitle, siteName, config.getTitleFormat()); - - // 描述优先级:页面描述 > 站点 SEO 描述 > 页面标题 - var siteDesc = - Optional.ofNullable(systemInfo.getSeo()).map(SystemInfo.SeoProp::getDescription) - .orElse(null); - var description = firstNonBlank(pageDesc, siteDesc, pageTitle); - - // 通过 ExternalLinkProcessor 将相对路径转为完整的外部 URL - var pageUrl = externalLinkProcessor.processLink(pagePath); - var coverUrl = - processPermalink(firstNonBlank(config.getDefaultImage(), systemInfo.getLogo())); - var keywords = - Optional.ofNullable(systemInfo.getSeo()).map(SystemInfo.SeoProp::getKeywords) - .orElse(null); - - // 列表页没有具体的发布/更新时间,相关字段留空; - // 作者优先使用配置的默认作者,回退到站点名称;pageType 为 website - var author = firstNonBlank(config.getDefaultAuthor(), siteName); - var seoData = - new SeoData(title, description, coverUrl, pageUrl, author, null, null, null, - null, siteName, siteLogo, keywords, "website"); - - return generateSeoTags(seoData, model, modelFactory); - }).onErrorResume(error -> { - log.warn("Failed to build list page SEO for: {}", pageTitle, error); - return Mono.empty(); - }); + return Mono.zip(settingConfigGetter.getBasicConfig(), systemInfoGetter.get(), + getThemeRouteRules()).flatMap(tuple -> { + var config = tuple.getT1(); + var systemInfo = tuple.getT2(); + var routeRules = tuple.getT3(); + + var siteName = systemInfo.getTitle(); + var siteLogo = processPermalink(systemInfo.getLogo()); + + var title = formatTitle(pageTitle, siteName, config.getTitleFormat()); + + // 描述优先级:站点 SEO 描述 > 页面标题 + var siteDesc = + Optional.ofNullable(systemInfo.getSeo()).map(SystemInfo.SeoProp::getDescription) + .orElse(null); + var description = firstNonBlank(siteDesc, pageTitle); + + // 通过 ExternalLinkProcessor 将相对路径转为完整的外部 URL + var pageUrl = externalLinkProcessor.processLink(pathProvider.apply(routeRules)); + var coverUrl = + processPermalink(firstNonBlank(config.getDefaultImage(), systemInfo.getLogo())); + var keywords = + Optional.ofNullable(systemInfo.getSeo()).map(SystemInfo.SeoProp::getKeywords) + .orElse(null); + + // 列表页没有具体的发布/更新时间,相关字段留空; + // 作者优先使用配置的默认作者,回退到站点名称;pageType 为 website + var author = firstNonBlank(config.getDefaultAuthor(), siteName); + var seoData = + new SeoData(title, description, coverUrl, pageUrl, author, null, null, null, null, + siteName, siteLogo, keywords, "website"); + + return generateSeoTags(seoData, model, modelFactory); + }).onErrorResume(error -> { + log.warn("Failed to build list page SEO for: {}", pageTitle, error); + return Mono.empty(); + }); } /** @@ -1215,6 +1206,20 @@ private String processPermalink(String path) { return hasValue(path) ? externalLinkProcessor.processLink(path) : null; } + /** + * 获取 Halo 系统路由规则配置,读取失败时回退到默认值。 + */ + private Mono getThemeRouteRules() { + return client.fetch(ConfigMap.class, SystemSetting.SYSTEM_CONFIG).mapNotNull(cm -> { + var data = cm.getData(); + if (data == null) { + return null; + } + return SystemSetting.get(data, SystemSetting.ThemeRouteRules.GROUP, + SystemSetting.ThemeRouteRules.class); + }).defaultIfEmpty(SystemSetting.ThemeRouteRules.empty()); + } + /** * 返回第一个非空白(non-blank)的字符串,全部为空时返回空字符串。 * From 110ef246fd0688bdcb5eda622042d8a990989988 Mon Sep 17 00:00:00 2001 From: HowieHz Date: Wed, 25 Mar 2026 17:25:41 +0800 Subject: [PATCH 12/19] Read /<meta> for SEO; drop titleFormat --- README.md | 72 +++--- .../timefactor/process/TimeFactorProcess.java | 230 +++++++++--------- .../service/SettingConfigGetter.java | 1 - src/main/resources/extensions/settings.yaml | 7 - 4 files changed, 162 insertions(+), 148 deletions(-) diff --git a/README.md b/README.md index f85e86d..c9fe54b 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ - **演示站点**:[https://www.lik.cc/](https://www.lik.cc/) - **文档**:[https://docs.lik.cc/](https://docs.lik.cc/) - **QQ 交流群**: - - [![QQ群](https://www.lik.cc/upload/iShot_2025-03-03_16.03.00.png)](https://www.lik.cc/upload/iShot_2025-03-03_16.03.00.png) + - [![QQ群](https://www.lik.cc/upload/iShot_2025-03-03_16.03.00.png)](https://www.lik.cc/upload/iShot_2025-03-03_16.03.00.png) ## 功能特性 @@ -36,48 +36,60 @@ ## 支持注入的页面类型 -> **数据来源说明**:「站点信息」指使用 Halo 后台设置的站点标题、Logo、SEO 关键词和描述等全局配置来生成 SEO -> 标签,适用于没有具体内容实体的列表/聚合页面;其他数据来源(如 Post、Category 等)表示通过 Halo Extension API -> 获取对应的强类型对象,从中提取标题、摘要、封面、作者、发布时间等精确字段。 +### 标题与描述的取值逻辑 + +插件不自己拼接或硬编码任何标题/描述文字,而是直接读取主题已渲染好的 `<head>` 内容,保证 SEO 标签与页面实际展示完全一致: + +**标题**:`<title>` 标签文本 → 站点标题 → 不输出 + +**描述**:`<meta name="description">` 的 `content` → 站点 SEO 描述 → 不输出 + +> 主题负责生成 `<title>` 和 `<meta name="description">`,插件只负责将它们同步到 OG、结构化数据等各类 SEO +> 标签中。若主题未设置这两项,则回退到 Halo 后台「基本设置」中的站点标题和站点描述;若站点级也为空,对应字段不输出(不生成空值标签)。 ### Halo 内置页面 -| 页面类型 | 模板 ID | 数据来源 | 文档 | -|-------|--------------|-------------------|-----------------------------------------------------------------------------------| -| 首页 | `index` | 站点信息 | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/index_) | -| 文章详情页 | `post` | Post + User + Tag | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/post) | -| 独立页面 | `page` | SinglePage + User | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/page) | -| 分类列表页 | `categories` | 站点信息 | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/categories) | -| 分类详情页 | `category` | Category | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/category) | -| 标签列表页 | `tags` | 站点信息 | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/tags) | -| 归档页 | `archives` | 站点信息 | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/archives) | -| 作者页 | `author` | User | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/author) | +| 页面类型 | 模板 ID | 额外数据来源(封面/作者/时间等) | 文档 | +|-------|--------------|------------------------|-----------------------------------------------------------------------------------| +| 首页 | `index` | — | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/index_) | +| 文章详情页 | `post` | Post + User + Tag | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/post) | +| 独立页面 | `page` | SinglePage + User | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/page) | +| 分类列表页 | `categories` | — | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/categories) | +| 分类详情页 | `category` | Category(封面、permalink) | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/category) | +| 标签列表页 | `tags` | — | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/tags) | +| 归档页 | `archives` | — | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/archives) | +| 作者页 | `author` | User(头像、permalink) | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/author) | > **暂不支持**:标签详情页(`tag`)——Halo 的标签路由未注入 `_templateId` 上下文变量,插件无法识别该页面类型。 +> **路由前缀**:分类列表、标签列表、归档页的 canonical URL 会读取 Halo「主题路由规则」配置(`SystemSetting.ThemeRouteRules` +> ),与站点实际路由保持同步,不使用硬编码路径。 + ### 第三方插件页面 -| 页面类型 | 模板 ID | 数据来源 | 插件 | -|------|-----------|--------------------|-----------------------------------------------------------------------------------| -| 瞬间列表 | `moments` | 站点信息 | [plugin-moments](https://github.com/halo-sigs/plugin-moments) | -| 瞬间详情 | `moment` | 站点信息 | [plugin-moments](https://github.com/halo-sigs/plugin-moments) | -| 图库 | `photos` | 站点信息 | [plugin-photos](https://github.com/halo-sigs/plugin-photos) | -| 朋友圈 | `friends` | 站点信息 | [plugin-friends-new](https://github.com/chengzhongxue/plugin-friends-new) | -| 豆瓣 | `douban` | 站点信息 + 路由 title 变量 | [plugin-douban](https://github.com/chengzhongxue/plugin-douban) | -| 番剧 | `bangumi` | 站点信息 | [halo-plugin-bangumi-data](https://github.com/ShiinaKin/halo-plugin-bangumi-data) | +| 页面类型 | 模板 ID | 插件 | +|------|-----------|-----------------------------------------------------------------------------------| +| 瞬间列表 | `moments` | [plugin-moments](https://github.com/halo-sigs/plugin-moments) | +| 瞬间详情 | `moment` | [plugin-moments](https://github.com/halo-sigs/plugin-moments) | +| 图库 | `photos` | [plugin-photos](https://github.com/halo-sigs/plugin-photos) | +| 朋友圈 | `friends` | [plugin-friends-new](https://github.com/chengzhongxue/plugin-friends-new) | +| 豆瓣 | `douban` | [plugin-douban](https://github.com/chengzhongxue/plugin-douban) | +| 番剧 | `bangumi` | [halo-plugin-bangumi-data](https://github.com/ShiinaKin/halo-plugin-bangumi-data) | + +> 第三方插件页面的标题/描述同样来自页面的 `<title>` 和 `<meta name="description">`,由对应插件的主题模板负责设置。 ### 各页面输出字段差异 并非所有页面都拥有完整的 SEO 字段。当某个字段为空时,对应的 meta/script 标签会被**自动省略**(不输出空值标签),以保证结构化数据的有效性。 -| 页面类型 | `og:type` | Schema.org `@type` | 发布/更新时间 | 作者 | 关键词 | 说明 | -|--------|-----------|--------------------|:-------:|:--:|:-------:|--------------------------------| -| 文章详情页 | `article` | `BlogPosting` | ✅ | ✅ | ✅ 文章标签 | 字段最完整的页面类型 | -| 独立页面 | `article` | `BlogPosting` | ✅ | ✅ | ✅ 站点关键词 | 独立页面无标签,关键词回退到站点级 | -| 分类详情页 | `website` | `WebPage` | ❌ | ❌ | ✅ 分类名 | 分类无发布时间和作者;描述取自分类描述字段 | -| 标签详情页 | `website` | `WebPage` | ❌ | ❌ | ✅ 标签名 | (⚠️ 暂不支持)标签无发布时间和作者;描述取自标签描述字段 | -| 作者页 | `profile` | `ProfilePage` | ❌ | ✅ | ✅ 作者名 | 作者页无发布时间 | -| 列表/聚合页 | `website` | `WebPage` | ❌ | ❌ | ✅ 站点关键词 | 首页、分类列表、标签列表、归档页、所有第三方插件页面 | +| 页面类型 | `og:type` | Schema.org `@type` | 发布/更新时间 | 作者 | 关键词 | 说明 | +|--------|-----------|--------------------|:-------:|:--:|:-------:|----------------------------| +| 文章详情页 | `article` | `BlogPosting` | ✅ | ✅ | ✅ 文章标签 | 字段最完整的页面类型 | +| 独立页面 | `article` | `BlogPosting` | ✅ | ✅ | ✅ 站点关键词 | 独立页面无标签,关键词回退到站点级 | +| 分类详情页 | `website` | `WebPage` | ❌ | ❌ | ✅ 分类名 | 分类无发布时间和作者 | +| 标签详情页 | `website` | `WebPage` | ❌ | ❌ | ✅ 标签名 | (⚠️ 暂不支持)标签无发布时间和作者 | +| 作者页 | `profile` | `ProfilePage` | ❌ | ✅ | ✅ 作者名 | 作者页无发布时间 | +| 列表/聚合页 | `website` | `WebPage` | ❌ | ❌ | ✅ 站点关键词 | 首页、分类列表、标签列表、归档页、所有第三方插件页面 | > **省略规则**:当发布/更新时间为空时,`og:release_date`、`og:modified_time`、`bytedance:published_time`、 `bytedance:updated_time` 不输出;百度时间因子 `<script>` 中的 `pubDate`/`upDate` 字段不输出,若两者均为空则整个百度 script diff --git a/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java b/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java index 97c05b1..4b6a165 100644 --- a/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java +++ b/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java @@ -15,6 +15,9 @@ import org.thymeleaf.context.ITemplateContext; import org.thymeleaf.model.IModel; import org.thymeleaf.model.IModelFactory; +import org.thymeleaf.model.IOpenElementTag; +import org.thymeleaf.model.IStandaloneElementTag; +import org.thymeleaf.model.IText; import org.thymeleaf.processor.element.IElementModelStructureHandler; import org.unbescape.html.HtmlEscape; import org.unbescape.json.JsonEscape; @@ -123,19 +126,6 @@ public class TimeFactorProcess implements TemplateHeadProcessor { private static final DateTimeFormatter GOOGLE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXXX"); - // ======================== 页面标签常量 ======================== - private static final String PAGE_LABEL_INDEX = "首页"; - private static final String PAGE_LABEL_CATEGORIES = "分类"; - private static final String PAGE_LABEL_ARCHIVES = "归档"; - private static final String PAGE_LABEL_TAGS = "标签"; - private static final String PAGE_LABEL_MOMENTS = "瞬间"; - private static final String PAGE_LABEL_PHOTOS = "图库"; - private static final String PAGE_LABEL_FRIENDS = "朋友圈"; - private static final String PAGE_LABEL_DOUBAN = "豆瓣"; - private static final String PAGE_LABEL_BANGUMI = "番剧"; - private static final String TAG_DESC_PREFIX = "标签: "; - private static final String CATEGORY_DESC_PREFIX = "分类: "; - private static final String AUTHOR_DESC_PREFIX = "作者: "; private final ReactiveExtensionClient client; private final SettingConfigGetter settingConfigGetter; @@ -206,7 +196,7 @@ public Mono<Void> process(ITemplateContext context, IModel model, * <a href="https://docs.halo.run/developer-guide/theme/template-variables/index_">首页模板变量</a> */ private Mono<Void> processIndexSeoData(ITemplateContext context, IModel model) { - return buildListPageSeoData(context, model, PAGE_LABEL_INDEX, rules -> "/"); + return buildListPageSeoData(context, model, rules -> "/"); } /** @@ -219,7 +209,7 @@ private Mono<Void> processIndexSeoData(ITemplateContext context, IModel model) { * <a href="https://docs.halo.run/developer-guide/theme/template-variables/categories">分类列表模板变量</a> */ private Mono<Void> processCategoriesSeoData(ITemplateContext context, IModel model) { - return buildListPageSeoData(context, model, PAGE_LABEL_CATEGORIES, + return buildListPageSeoData(context, model, rules -> Optional.ofNullable(rules.getCategories()).orElse("/categories")); } @@ -233,7 +223,7 @@ private Mono<Void> processCategoriesSeoData(ITemplateContext context, IModel mod * <a href="https://docs.halo.run/developer-guide/theme/template-variables/archives">归档模板变量</a> */ private Mono<Void> processArchivesSeoData(ITemplateContext context, IModel model) { - return buildListPageSeoData(context, model, PAGE_LABEL_ARCHIVES, + return buildListPageSeoData(context, model, rules -> Optional.ofNullable(rules.getArchives()).orElse("/archives")); } @@ -247,7 +237,7 @@ private Mono<Void> processArchivesSeoData(ITemplateContext context, IModel model * <a href="https://docs.halo.run/developer-guide/theme/template-variables/tags">标签列表模板变量</a> */ private Mono<Void> processTagsSeoData(ITemplateContext context, IModel model) { - return buildListPageSeoData(context, model, PAGE_LABEL_TAGS, + return buildListPageSeoData(context, model, rules -> Optional.ofNullable(rules.getTags()).orElse("/tags")); } @@ -261,7 +251,7 @@ private Mono<Void> processTagsSeoData(ITemplateContext context, IModel model) { * @see <a href="https://github.com/halo-sigs/plugin-moments">plugin-moments</a> */ private Mono<Void> processMomentsSeoData(ITemplateContext context, IModel model) { - return buildListPageSeoData(context, model, PAGE_LABEL_MOMENTS, rules -> "/moments"); + return buildListPageSeoData(context, model, rules -> "/moments"); } /** @@ -273,7 +263,7 @@ private Mono<Void> processMomentsSeoData(ITemplateContext context, IModel model) */ private Mono<Void> processMomentSeoData(ITemplateContext context, IModel model) { // MomentVo 在上下文中是延迟加载的 Mono 对象,无法直接获取 metadata.name,降级使用站点信息 - return buildListPageSeoData(context, model, PAGE_LABEL_MOMENTS, rules -> "/moments"); + return buildListPageSeoData(context, model, rules -> "/moments"); } /** @@ -285,7 +275,7 @@ private Mono<Void> processMomentSeoData(ITemplateContext context, IModel model) * @see <a href="https://github.com/halo-sigs/plugin-photos">plugin-photos</a> */ private Mono<Void> processPhotosSeoData(ITemplateContext context, IModel model) { - return buildListPageSeoData(context, model, PAGE_LABEL_PHOTOS, rules -> "/photos"); + return buildListPageSeoData(context, model, rules -> "/photos"); } /** @@ -298,7 +288,7 @@ private Mono<Void> processPhotosSeoData(ITemplateContext context, IModel model) * @see <a href="https://github.com/chengzhongxue/plugin-friends-new">plugin-friends-new</a> */ private Mono<Void> processFriendsSeoData(ITemplateContext context, IModel model) { - return buildListPageSeoData(context, model, PAGE_LABEL_FRIENDS, rules -> "/friends"); + return buildListPageSeoData(context, model, rules -> "/friends"); } /** @@ -312,10 +302,7 @@ private Mono<Void> processFriendsSeoData(ITemplateContext context, IModel model) * @see <a href="https://github.com/chengzhongxue/plugin-douban">plugin-douban</a> */ private Mono<Void> processDoubanSeoData(ITemplateContext context, IModel model) { - // 豆瓣路由设置了 title 上下文变量(DoubanRouter.getDoubanTitle),优先使用 - var doubanTitle = Optional.ofNullable(context.getVariable("title")).map(Object::toString) - .filter(s -> !s.isBlank()).orElse(PAGE_LABEL_DOUBAN); - return buildListPageSeoData(context, model, doubanTitle, rules -> "/douban"); + return buildListPageSeoData(context, model, rules -> "/douban"); } /** @@ -330,7 +317,7 @@ private Mono<Void> processDoubanSeoData(ITemplateContext context, IModel model) * <a href="https://github.com/ShiinaKin/halo-plugin-bangumi-data">halo-plugin-bangumi-data</a> */ private Mono<Void> processBangumiSeoData(ITemplateContext context, IModel model) { - return buildListPageSeoData(context, model, PAGE_LABEL_BANGUMI, rules -> "/bangumi"); + return buildListPageSeoData(context, model, rules -> "/bangumi"); } // ======================== 详情页 SEO 处理器 ======================== @@ -359,8 +346,11 @@ private Mono<Void> processPostSeoData(ITemplateContext context, IModel model) { if (postName == null) { return Mono.empty(); } - return client.fetch(Post.class, postName).flatMap(post -> buildSeoDataForPost(post).flatMap( - seoData -> generateSeoTags(seoData, model, modelFactory))); + var title = extractTitleFromModel(model); + var description = extractMetaDescriptionFromModel(model); + return client.fetch(Post.class, postName).flatMap( + post -> buildSeoDataForPost(post, title, description).flatMap( + seoData -> generateSeoTags(seoData, model, modelFactory))); } /** @@ -385,8 +375,10 @@ private Mono<Void> processCategorySeoData(ITemplateContext context, IModel model if (categoryName == null) { return Mono.empty(); } + var title = extractTitleFromModel(model); + var description = extractMetaDescriptionFromModel(model); return client.fetch(Category.class, categoryName).flatMap( - category -> buildSeoDataForCategory(category).flatMap( + category -> buildSeoDataForCategory(category, title, description).flatMap( seoData -> generateSeoTags(seoData, model, modelFactory))); } @@ -410,8 +402,11 @@ private Mono<Void> processTagSeoData(ITemplateContext context, IModel model) { if (tagName == null) { return Mono.empty(); } - return client.fetch(Tag.class, tagName).flatMap(tag -> buildSeoDataForTag(tag).flatMap( - seoData -> generateSeoTags(seoData, model, modelFactory))); + var title = extractTitleFromModel(model); + var description = extractMetaDescriptionFromModel(model); + return client.fetch(Tag.class, tagName).flatMap( + tag -> buildSeoDataForTag(tag, title, description).flatMap( + seoData -> generateSeoTags(seoData, model, modelFactory))); } /** @@ -436,8 +431,10 @@ private Mono<Void> processSinglePageSeoData(ITemplateContext context, IModel mod if (pageName == null) { return Mono.empty(); } + var title = extractTitleFromModel(model); + var description = extractMetaDescriptionFromModel(model); return client.fetch(SinglePage.class, pageName).flatMap( - page -> buildSeoDataForSinglePage(page).flatMap( + page -> buildSeoDataForSinglePage(page, title, description).flatMap( seoData -> generateSeoTags(seoData, model, modelFactory))); } @@ -461,8 +458,10 @@ private Mono<Void> processAuthorSeoData(ITemplateContext context, IModel model) if (userName == null) { return Mono.empty(); } + var title = extractTitleFromModel(model); + var description = extractMetaDescriptionFromModel(model); return client.fetch(User.class, userName).flatMap( - user -> buildSeoDataForAuthor(user).flatMap( + user -> buildSeoDataForAuthor(user, title, description).flatMap( seoData -> generateSeoTags(seoData, model, modelFactory))); } @@ -483,12 +482,14 @@ private Mono<Void> processAuthorSeoData(ITemplateContext context, IModel model) * * @param context Thymeleaf 模板上下文 * @param model HTML 输出模型 - * @param pageTitle 页面语义标题,如 "首页"、"分类"、"标签" * @param pathProvider 页面路径提供函数,接收 {@link SystemSetting.ThemeRouteRules} 以支持可配置路由前缀 */ private Mono<Void> buildListPageSeoData(ITemplateContext context, IModel model, - String pageTitle, Function<SystemSetting.ThemeRouteRules, String> pathProvider) { + Function<SystemSetting.ThemeRouteRules, String> pathProvider) { var modelFactory = context.getModelFactory(); + // 从已渲染的 head 中读取,主题负责内容,此处只是复用 + var title = extractTitleFromModel(model); + var description = extractMetaDescriptionFromModel(model); return Mono.zip(settingConfigGetter.getBasicConfig(), systemInfoGetter.get(), getThemeRouteRules()).flatMap(tuple -> { @@ -498,14 +499,14 @@ private Mono<Void> buildListPageSeoData(ITemplateContext context, IModel model, var siteName = systemInfo.getTitle(); var siteLogo = processPermalink(systemInfo.getLogo()); - - var title = formatTitle(pageTitle, siteName, config.getTitleFormat()); - - // 描述优先级:站点 SEO 描述 > 页面标题 var siteDesc = Optional.ofNullable(systemInfo.getSeo()).map(SystemInfo.SeoProp::getDescription) .orElse(null); - var description = firstNonBlank(siteDesc, pageTitle); + + // 标题:页面 <title> → 站点标题 → 空 + var finalTitle = firstNonBlank(title, siteName); + // 描述:页面 <meta name="description"> → 站点描述 → 空 + var finalDesc = firstNonBlank(description, siteDesc); // 通过 ExternalLinkProcessor 将相对路径转为完整的外部 URL var pageUrl = externalLinkProcessor.processLink(pathProvider.apply(routeRules)); @@ -519,12 +520,12 @@ private Mono<Void> buildListPageSeoData(ITemplateContext context, IModel model, // 作者优先使用配置的默认作者,回退到站点名称;pageType 为 website var author = firstNonBlank(config.getDefaultAuthor(), siteName); var seoData = - new SeoData(title, description, coverUrl, pageUrl, author, null, null, null, null, - siteName, siteLogo, keywords, "website"); + new SeoData(finalTitle, finalDesc, coverUrl, pageUrl, author, null, null, null, + null, siteName, siteLogo, keywords, "website"); return generateSeoTags(seoData, model, modelFactory); }).onErrorResume(error -> { - log.warn("Failed to build list page SEO for: {}", pageTitle, error); + log.warn("Failed to build list page SEO", error); return Mono.empty(); }); } @@ -551,7 +552,7 @@ private Mono<Void> buildListPageSeoData(ITemplateContext context, IModel model, * @param post 文章 Extension 对象 * @return 包含完整 SEO 数据的 Mono */ - private Mono<SeoData> buildSeoDataForPost(Post post) { + private Mono<SeoData> buildSeoDataForPost(Post post, String title, String description) { // 并行获取:文章所有者、文章标签、插件配置、系统信息 return Mono.zip(client.fetch(User.class, post.getSpec().getOwner()), findTagForPost(post), settingConfigGetter.getBasicConfig(), systemInfoGetter.get()).map(tuple -> { @@ -567,8 +568,13 @@ private Mono<SeoData> buildSeoDataForPost(Post post) { // status 字段在 Reconciler 首次处理前为 null,使用 getStatusOrDefault() 安全获取 var status = post.getStatusOrDefault(); var postUrl = processPermalink(status.getPermalink()); - var title = post.getSpec().getTitle(); - var description = status.getExcerpt(); + // 标题/描述来自 <title>/<meta description>,回退到站点级 + var siteName = systemInfo.getTitle(); + var siteDesc = + Optional.ofNullable(systemInfo.getSeo()).map(SystemInfo.SeoProp::getDescription) + .orElse(null); + var finalTitle = firstNonBlank(title, siteName); + var finalDesc = firstNonBlank(description, siteDesc); // 封面:优先文章封面,回退到插件默认封面 var coverUrl = processPermalink( firstNonBlank(post.getSpec().getCover(), config.getDefaultImage())); @@ -577,7 +583,6 @@ private Mono<SeoData> buildSeoDataForPost(Post post) { var updateInstant = status.getLastModifyTime(); var zoneId = ZoneId.systemDefault(); - var siteName = systemInfo.getTitle(); var siteLogo = processPermalink(systemInfo.getLogo()); var siteKeywords = Optional.ofNullable(systemInfo.getSeo()).map(SystemInfo.SeoProp::getKeywords) @@ -585,7 +590,7 @@ private Mono<SeoData> buildSeoDataForPost(Post post) { // 关键词:优先文章标签,回退到站点 SEO 关键词 var finalKeywords = hasValue(keywords) ? keywords : siteKeywords; - return new SeoData(title, description, coverUrl, postUrl, author, + return new SeoData(finalTitle, finalDesc, coverUrl, postUrl, author, formatDateTime(publishInstant, BAIDU_FORMATTER, zoneId), formatDateTime(updateInstant, BAIDU_FORMATTER, zoneId), formatDateTime(publishInstant, GOOGLE_FORMATTER, zoneId), @@ -616,8 +621,8 @@ private Mono<SeoData> buildSeoDataForPost(Post post) { * @param page 独立页面 Extension 对象 * @return 包含 SEO 数据的 Mono */ - private Mono<SeoData> buildSeoDataForSinglePage(SinglePage page) { - // 并行获取:页面所有者、插件配置、系统信息 + private Mono<SeoData> buildSeoDataForSinglePage(SinglePage page, String title, + String description) { return Mono.zip(client.fetch(User.class, page.getSpec().getOwner()), settingConfigGetter.getBasicConfig(), systemInfoGetter.get()).map(tuple -> { var user = tuple.getT1(); @@ -627,11 +632,8 @@ private Mono<SeoData> buildSeoDataForSinglePage(SinglePage page) { var author = Optional.of(user).map(User::getSpec).map(User.UserSpec::getDisplayName) .orElse(page.getSpec().getOwner()); - // status 字段在 Reconciler 首次处理前为 null,使用 getStatusOrDefault() 安全获取 var status = page.getStatusOrDefault(); var pageUrl = processPermalink(status.getPermalink()); - var title = page.getSpec().getTitle(); - var description = status.getExcerpt(); var coverUrl = processPermalink( firstNonBlank(page.getSpec().getCover(), config.getDefaultImage())); @@ -641,12 +643,15 @@ private Mono<SeoData> buildSeoDataForSinglePage(SinglePage page) { var siteName = systemInfo.getTitle(); var siteLogo = processPermalink(systemInfo.getLogo()); + var siteDesc = + Optional.ofNullable(systemInfo.getSeo()).map(SystemInfo.SeoProp::getDescription) + .orElse(null); var keywords = Optional.ofNullable(systemInfo.getSeo()).map(SystemInfo.SeoProp::getKeywords) .orElse(null); - return new SeoData(title, description, coverUrl, pageUrl, author, - formatDateTime(publishInstant, BAIDU_FORMATTER, zoneId), + return new SeoData(firstNonBlank(title, siteName), firstNonBlank(description, siteDesc), + coverUrl, pageUrl, author, formatDateTime(publishInstant, BAIDU_FORMATTER, zoneId), formatDateTime(updateInstant, BAIDU_FORMATTER, zoneId), formatDateTime(publishInstant, GOOGLE_FORMATTER, zoneId), formatDateTime(updateInstant, GOOGLE_FORMATTER, zoneId), siteName, siteLogo, @@ -672,30 +677,27 @@ private Mono<SeoData> buildSeoDataForSinglePage(SinglePage page) { * @param category 分类 Extension 对象 * @return 包含 SEO 数据的 Mono */ - private Mono<SeoData> buildSeoDataForCategory(Category category) { + private Mono<SeoData> buildSeoDataForCategory(Category category, String title, + String description) { return Mono.zip(settingConfigGetter.getBasicConfig(), systemInfoGetter.get()).map(tuple -> { var config = tuple.getT1(); var systemInfo = tuple.getT2(); - var displayName = category.getSpec().getDisplayName(); var siteName = systemInfo.getTitle(); - var title = formatTitle(displayName, siteName, config.getTitleFormat()); - - // 分类有 description 字段,回退到 "分类: displayName" - var description = firstNonBlank(category.getSpec().getDescription(), - CATEGORY_DESC_PREFIX + displayName); - - // status 字段在 Reconciler 首次处理前为 null,使用 getStatusOrDefault() 安全获取 + var siteDesc = + Optional.ofNullable(systemInfo.getSeo()).map(SystemInfo.SeoProp::getDescription) + .orElse(null); var pageUrl = processPermalink(category.getStatusOrDefault().getPermalink()); var coverUrl = processPermalink( firstNonBlank(category.getSpec().getCover(), config.getDefaultImage(), systemInfo.getLogo())); var siteLogo = processPermalink(systemInfo.getLogo()); - - // 分类没有独立的作者和发布时间,作者优先使用默认作者,回退到站点名称;pageType 为 website var author = firstNonBlank(config.getDefaultAuthor(), siteName); - return new SeoData(title, description, coverUrl, pageUrl, author, null, null, null, - null, siteName, siteLogo, displayName, "website"); + var displayName = category.getSpec().getDisplayName(); + + return new SeoData(firstNonBlank(title, siteName), firstNonBlank(description, siteDesc), + coverUrl, pageUrl, author, null, null, null, null, siteName, siteLogo, displayName, + "website"); }); } @@ -717,29 +719,26 @@ private Mono<SeoData> buildSeoDataForCategory(Category category) { * @param tag 标签 Extension 对象 * @return 包含 SEO 数据的 Mono */ - private Mono<SeoData> buildSeoDataForTag(Tag tag) { + private Mono<SeoData> buildSeoDataForTag(Tag tag, String title, String description) { return Mono.zip(settingConfigGetter.getBasicConfig(), systemInfoGetter.get()).map(tuple -> { var config = tuple.getT1(); var systemInfo = tuple.getT2(); - var displayName = tag.getSpec().getDisplayName(); var siteName = systemInfo.getTitle(); - var title = formatTitle(displayName, siteName, config.getTitleFormat()); - // 描述:优先 Tag 自身的 description,回退到 "标签: displayName" - var description = - firstNonBlank(tag.getSpec().getDescription(), TAG_DESC_PREFIX + displayName); - - // status 字段在 Reconciler 首次处理前为 null,使用 getStatusOrDefault() 安全获取 + var siteDesc = + Optional.ofNullable(systemInfo.getSeo()).map(SystemInfo.SeoProp::getDescription) + .orElse(null); var pageUrl = processPermalink(tag.getStatusOrDefault().getPermalink()); var coverUrl = processPermalink( firstNonBlank(tag.getSpec().getCover(), config.getDefaultImage(), systemInfo.getLogo())); var siteLogo = processPermalink(systemInfo.getLogo()); - - // 标签没有独立的作者和发布时间;作者优先使用默认作者,回退到站点名称;pageType 为 website var author = firstNonBlank(config.getDefaultAuthor(), siteName); - return new SeoData(title, description, coverUrl, pageUrl, author, null, null, null, - null, siteName, siteLogo, displayName, "website"); + var displayName = tag.getSpec().getDisplayName(); + + return new SeoData(firstNonBlank(title, siteName), firstNonBlank(description, siteDesc), + coverUrl, pageUrl, author, null, null, null, null, siteName, siteLogo, displayName, + "website"); }); } @@ -762,18 +761,16 @@ private Mono<SeoData> buildSeoDataForTag(Tag tag) { * @param user 用户 Extension 对象 * @return 包含 SEO 数据的 Mono */ - private Mono<SeoData> buildSeoDataForAuthor(User user) { + private Mono<SeoData> buildSeoDataForAuthor(User user, String title, String description) { return Mono.zip(settingConfigGetter.getBasicConfig(), systemInfoGetter.get()).map(tuple -> { var config = tuple.getT1(); var systemInfo = tuple.getT2(); - var displayName = user.getSpec().getDisplayName(); var siteName = systemInfo.getTitle(); - var title = formatTitle(displayName, siteName, config.getTitleFormat()); - var description = - firstNonBlank(user.getSpec().getBio(), AUTHOR_DESC_PREFIX + displayName); - - // User 没有 status.permalink,手动构造作者归档页 URL + var siteDesc = + Optional.ofNullable(systemInfo.getSeo()).map(SystemInfo.SeoProp::getDescription) + .orElse(null); + var displayName = user.getSpec().getDisplayName(); var pageUrl = externalLinkProcessor.processLink("/authors/" + user.getMetadata().getName()); var coverUrl = processPermalink( @@ -781,9 +778,9 @@ private Mono<SeoData> buildSeoDataForAuthor(User user) { systemInfo.getLogo())); var siteLogo = processPermalink(systemInfo.getLogo()); - // 作者页的 author 字段即为用户自身;pageType 为 profile - return new SeoData(title, description, coverUrl, pageUrl, displayName, null, null, null, - null, siteName, siteLogo, displayName, "profile"); + return new SeoData(firstNonBlank(title, siteName), firstNonBlank(description, siteDesc), + coverUrl, pageUrl, displayName, null, null, null, null, siteName, siteLogo, + displayName, "profile"); }); } @@ -1171,26 +1168,6 @@ private String getMetadataNameFromVo(ITemplateContext context, String voVariable return null; } - /** - * 根据配置模板格式化页面标题。 - * - * <p>用 {@code %TITLE%} 替换页面标题,{@code %SITENAME%} 替换站点名称。 - * 当站点名称为空时,直接返回 title,避免出现 "标题 - " 这类残缺输出。 - * 当 format 为空时同样退回到 title。 - * - * @param title 页面标题 - * @param siteName 站点名称 - * @param format 配置的标题格式模板,如 {@code "%TITLE% - %SITENAME%"} - * @return 格式化后的标题 - */ - private String formatTitle(String title, String siteName, String format) { - var t = hasValue(title) ? title : ""; - if (!hasValue(format) || !hasValue(siteName)) { - return t; - } - return format.replace("%TITLE%", t).replace("%SITENAME%", siteName); - } - /** * 判断字符串是否有实际内容(非 null 且非空白)。 * 用于输出阶段决定是否生成对应的 SEO 标签:无值则跳过,避免输出空标签。 @@ -1235,6 +1212,39 @@ private String firstNonBlank(String... values) { return ""; } + /** + * 从 head 模型中提取 {@code <title>} 标签的文本内容。 + * + * @return 标题文本,不存在时返回 null + */ + private String extractTitleFromModel(IModel model) { + for (int i = 0; i < model.size() - 1; i++) { + if (model.get(i) instanceof IOpenElementTag tag && "title".equalsIgnoreCase( + tag.getElementCompleteName()) && model.get(i + 1) instanceof IText text) { + var t = HtmlEscape.unescapeHtml(text.getText()).strip(); + return t.isBlank() ? null : t; + } + } + return null; + } + + /** + * 从 head 模型中提取 {@code <meta name="description">} 的 content 属性值。 + * + * @return 描述文本,不存在时返回 null + */ + private String extractMetaDescriptionFromModel(IModel model) { + for (int i = 0; i < model.size(); i++) { + if (model.get(i) instanceof IStandaloneElementTag tag && "meta".equalsIgnoreCase( + tag.getElementCompleteName()) && "description".equalsIgnoreCase( + tag.getAttributeValue("name"))) { + var content = tag.getAttributeValue("content"); + return (content != null && !content.isBlank()) ? content : null; + } + } + return null; + } + // ======================== 数据结构 ======================== /** diff --git a/src/main/java/cc/lik/timefactor/service/SettingConfigGetter.java b/src/main/java/cc/lik/timefactor/service/SettingConfigGetter.java index e16e007..181652e 100644 --- a/src/main/java/cc/lik/timefactor/service/SettingConfigGetter.java +++ b/src/main/java/cc/lik/timefactor/service/SettingConfigGetter.java @@ -32,6 +32,5 @@ class BasicConfig { private String twitterCreatorUserId; private String defaultImage; private String defaultAuthor; - private String titleFormat; } } diff --git a/src/main/resources/extensions/settings.yaml b/src/main/resources/extensions/settings.yaml index d6fd7eb..7db1c82 100644 --- a/src/main/resources/extensions/settings.yaml +++ b/src/main/resources/extensions/settings.yaml @@ -162,10 +162,3 @@ spec: key: defaultAuthor value: "" help: "列表页、分类页、标签页等无具体作者的页面使用;留空时回退到站点名称" - - $formkit: text - label: 标题格式 - name: titleFormat - id: titleFormat - key: titleFormat - value: "%TITLE% - %SITENAME%" - help: "列表页、分类页、标签页、作者页的标题格式模板;%TITLE% 为页面标题,%SITENAME% 为站点名称;站点名称为空时自动退回仅显示 %TITLE%" \ No newline at end of file From b5edfdee32b35307642a67b2e48df2a9873b72df Mon Sep 17 00:00:00 2001 From: HowieHz <howie_hzgo@outlook.com> Date: Thu, 26 Mar 2026 06:51:21 +0800 Subject: [PATCH 13/19] Add entity-level SEO fallbacks --- README.md | 24 +++++++--- .../timefactor/process/TimeFactorProcess.java | 46 ++++++++++++------- 2 files changed, 47 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index c9fe54b..5dfa52a 100644 --- a/README.md +++ b/README.md @@ -38,14 +38,25 @@ ### 标题与描述的取值逻辑 -插件不自己拼接或硬编码任何标题/描述文字,而是直接读取主题已渲染好的 `<head>` 内容,保证 SEO 标签与页面实际展示完全一致: +插件不自己拼接或硬编码任何标题/描述文字,而是优先读取主题已渲染好的 `<head>` 内容,保证 SEO 标签与页面实际展示完全一致: -**标题**:`<title>` 标签文本 → 站点标题 → 不输出 +**标题**:`<title>` 标签文本 → 实体固定值 → 站点标题 → 不输出 -**描述**:`<meta name="description">` 的 `content` → 站点 SEO 描述 → 不输出 +**描述**:`<meta name="description">` 的 `content` → 实体固定值 → 站点 SEO 描述 → 不输出 -> 主题负责生成 `<title>` 和 `<meta name="description">`,插件只负责将它们同步到 OG、结构化数据等各类 SEO -> 标签中。若主题未设置这两项,则回退到 Halo 后台「基本设置」中的站点标题和站点描述;若站点级也为空,对应字段不输出(不生成空值标签)。 +其中「实体固定值」因页面类型而异: + +| 页面类型 | 标题实体值 | 描述实体值 | +|--------|-----------------------------|-----------------------------| +| 文章详情页 | `post.spec.title` | `post.status.excerpt` | +| 独立页面 | `singlePage.spec.title` | `singlePage.status.excerpt` | +| 分类详情页 | `category.spec.displayName` | `category.spec.description` | +| 标签详情页 | `tag.spec.displayName` | `tag.spec.description` | +| 作者页 | `user.spec.displayName` | `user.spec.bio` | +| 列表/聚合页 | — | — | + +> 主题负责生成 `<title>` 和 `<meta name="description">`,插件优先使用这两项;若主题未设置,则回退到实体级固定值;实体级也为空时回退到 +> Halo 后台「基本设置」中的站点标题和站点描述;若站点级也为空,对应字段不输出(不生成空值标签)。 ### Halo 内置页面 @@ -76,7 +87,8 @@ | 豆瓣 | `douban` | [plugin-douban](https://github.com/chengzhongxue/plugin-douban) | | 番剧 | `bangumi` | [halo-plugin-bangumi-data](https://github.com/ShiinaKin/halo-plugin-bangumi-data) | -> 第三方插件页面的标题/描述同样来自页面的 `<title>` 和 `<meta name="description">`,由对应插件的主题模板负责设置。 +> 第三方插件页面的标题/描述同样来自页面的 `<title>` 和 `<meta name="description">`,由对应插件的主题模板负责设置。豆瓣插件额外支持上下文 +`title` 变量作为中间回退(`<title>` 提取失败时使用)。 ### 各页面输出字段差异 diff --git a/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java b/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java index 4b6a165..8a9629a 100644 --- a/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java +++ b/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java @@ -302,7 +302,10 @@ private Mono<Void> processFriendsSeoData(ITemplateContext context, IModel model) * @see <a href="https://github.com/chengzhongxue/plugin-douban">plugin-douban</a> */ private Mono<Void> processDoubanSeoData(ITemplateContext context, IModel model) { - return buildListPageSeoData(context, model, rules -> "/douban"); + // 豆瓣路由在上下文中注入了 title 变量,作为 <title> 提取失败时的中间回退 + var contextTitle = Optional.ofNullable(context.getVariable("title")).map(Object::toString) + .filter(s -> !s.isBlank()).orElse(null); + return buildListPageSeoData(context, model, rules -> "/douban", contextTitle, null); } /** @@ -486,6 +489,12 @@ private Mono<Void> processAuthorSeoData(ITemplateContext context, IModel model) */ private Mono<Void> buildListPageSeoData(ITemplateContext context, IModel model, Function<SystemSetting.ThemeRouteRules, String> pathProvider) { + return buildListPageSeoData(context, model, pathProvider, null, null); + } + + private Mono<Void> buildListPageSeoData(ITemplateContext context, IModel model, + Function<SystemSetting.ThemeRouteRules, String> pathProvider, String intermediateTitle, + String intermediateDesc) { var modelFactory = context.getModelFactory(); // 从已渲染的 head 中读取,主题负责内容,此处只是复用 var title = extractTitleFromModel(model); @@ -503,10 +512,10 @@ private Mono<Void> buildListPageSeoData(ITemplateContext context, IModel model, Optional.ofNullable(systemInfo.getSeo()).map(SystemInfo.SeoProp::getDescription) .orElse(null); - // 标题:页面 <title> → 站点标题 → 空 - var finalTitle = firstNonBlank(title, siteName); - // 描述:页面 <meta name="description"> → 站点描述 → 空 - var finalDesc = firstNonBlank(description, siteDesc); + // 标题:页面 <title> → 中间值(实体/插件上下文变量) → 站点标题 → 空 + var finalTitle = firstNonBlank(title, intermediateTitle, siteName); + // 描述:页面 <meta name="description"> → 中间值 → 站点描述 → 空 + var finalDesc = firstNonBlank(description, intermediateDesc, siteDesc); // 通过 ExternalLinkProcessor 将相对路径转为完整的外部 URL var pageUrl = externalLinkProcessor.processLink(pathProvider.apply(routeRules)); @@ -568,13 +577,13 @@ private Mono<SeoData> buildSeoDataForPost(Post post, String title, String descri // status 字段在 Reconciler 首次处理前为 null,使用 getStatusOrDefault() 安全获取 var status = post.getStatusOrDefault(); var postUrl = processPermalink(status.getPermalink()); - // 标题/描述来自 <title>/<meta description>,回退到站点级 + // 标题/描述:页面 <title>/<meta description> → 实体固定值 → 站点级 var siteName = systemInfo.getTitle(); var siteDesc = Optional.ofNullable(systemInfo.getSeo()).map(SystemInfo.SeoProp::getDescription) .orElse(null); - var finalTitle = firstNonBlank(title, siteName); - var finalDesc = firstNonBlank(description, siteDesc); + var finalTitle = firstNonBlank(title, post.getSpec().getTitle(), siteName); + var finalDesc = firstNonBlank(description, status.getExcerpt(), siteDesc); // 封面:优先文章封面,回退到插件默认封面 var coverUrl = processPermalink( firstNonBlank(post.getSpec().getCover(), config.getDefaultImage())); @@ -650,8 +659,9 @@ private Mono<SeoData> buildSeoDataForSinglePage(SinglePage page, String title, Optional.ofNullable(systemInfo.getSeo()).map(SystemInfo.SeoProp::getKeywords) .orElse(null); - return new SeoData(firstNonBlank(title, siteName), firstNonBlank(description, siteDesc), - coverUrl, pageUrl, author, formatDateTime(publishInstant, BAIDU_FORMATTER, zoneId), + return new SeoData(firstNonBlank(title, page.getSpec().getTitle(), siteName), + firstNonBlank(description, status.getExcerpt(), siteDesc), coverUrl, pageUrl, + author, formatDateTime(publishInstant, BAIDU_FORMATTER, zoneId), formatDateTime(updateInstant, BAIDU_FORMATTER, zoneId), formatDateTime(publishInstant, GOOGLE_FORMATTER, zoneId), formatDateTime(updateInstant, GOOGLE_FORMATTER, zoneId), siteName, siteLogo, @@ -695,8 +705,9 @@ private Mono<SeoData> buildSeoDataForCategory(Category category, String title, var author = firstNonBlank(config.getDefaultAuthor(), siteName); var displayName = category.getSpec().getDisplayName(); - return new SeoData(firstNonBlank(title, siteName), firstNonBlank(description, siteDesc), - coverUrl, pageUrl, author, null, null, null, null, siteName, siteLogo, displayName, + return new SeoData(firstNonBlank(title, category.getSpec().getDisplayName(), siteName), + firstNonBlank(description, category.getSpec().getDescription(), siteDesc), coverUrl, + pageUrl, author, null, null, null, null, siteName, siteLogo, displayName, "website"); }); } @@ -736,8 +747,9 @@ private Mono<SeoData> buildSeoDataForTag(Tag tag, String title, String descripti var author = firstNonBlank(config.getDefaultAuthor(), siteName); var displayName = tag.getSpec().getDisplayName(); - return new SeoData(firstNonBlank(title, siteName), firstNonBlank(description, siteDesc), - coverUrl, pageUrl, author, null, null, null, null, siteName, siteLogo, displayName, + return new SeoData(firstNonBlank(title, tag.getSpec().getDisplayName(), siteName), + firstNonBlank(description, tag.getSpec().getDescription(), siteDesc), coverUrl, + pageUrl, author, null, null, null, null, siteName, siteLogo, displayName, "website"); }); } @@ -778,9 +790,9 @@ private Mono<SeoData> buildSeoDataForAuthor(User user, String title, String desc systemInfo.getLogo())); var siteLogo = processPermalink(systemInfo.getLogo()); - return new SeoData(firstNonBlank(title, siteName), firstNonBlank(description, siteDesc), - coverUrl, pageUrl, displayName, null, null, null, null, siteName, siteLogo, - displayName, "profile"); + return new SeoData(firstNonBlank(title, user.getSpec().getDisplayName(), siteName), + firstNonBlank(description, user.getSpec().getBio(), siteDesc), coverUrl, pageUrl, + displayName, null, null, null, null, siteName, siteLogo, displayName, "profile"); }); } From e912ab7a9b87d3f8f4967f07bf1dd337c5860d49 Mon Sep 17 00:00:00 2001 From: HowieHz <howie_hzgo@outlook.com> Date: Thu, 26 Mar 2026 06:56:52 +0800 Subject: [PATCH 14/19] Clarify README wording and fallback logic --- README.md | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 5dfa52a..7f74b72 100644 --- a/README.md +++ b/README.md @@ -38,13 +38,13 @@ ### 标题与描述的取值逻辑 -插件不自己拼接或硬编码任何标题/描述文字,而是优先读取主题已渲染好的 `<head>` 内容,保证 SEO 标签与页面实际展示完全一致: +插件不自己拼接或硬编码任何标题/描述文字,而是优先读取已渲染好的 `<head>` 内容,保证 SEO 标签与页面实际展示完全一致: -**标题**:`<title>` 标签文本 → 实体固定值 → 站点标题 → 不输出 +**标题**:`<title>` 标签文本 → 实体固定值 → 站点标题(设置 / 基本设置) → 不输出 -**描述**:`<meta name="description">` 的 `content` → 实体固定值 → 站点 SEO 描述 → 不输出 +**描述**:`<meta name="description">` 的 `content` → 实体固定值 → 站点描述(设置 / SEO 设置) → 不输出 -其中「实体固定值」因页面类型而异: +其中“实体固定值”因页面类型而异: | 页面类型 | 标题实体值 | 描述实体值 | |--------|-----------------------------|-----------------------------| @@ -55,9 +55,6 @@ | 作者页 | `user.spec.displayName` | `user.spec.bio` | | 列表/聚合页 | — | — | -> 主题负责生成 `<title>` 和 `<meta name="description">`,插件优先使用这两项;若主题未设置,则回退到实体级固定值;实体级也为空时回退到 -> Halo 后台「基本设置」中的站点标题和站点描述;若站点级也为空,对应字段不输出(不生成空值标签)。 - ### Halo 内置页面 | 页面类型 | 模板 ID | 额外数据来源(封面/作者/时间等) | 文档 | @@ -73,8 +70,7 @@ > **暂不支持**:标签详情页(`tag`)——Halo 的标签路由未注入 `_templateId` 上下文变量,插件无法识别该页面类型。 -> **路由前缀**:分类列表、标签列表、归档页的 canonical URL 会读取 Halo「主题路由规则」配置(`SystemSetting.ThemeRouteRules` -> ),与站点实际路由保持同步,不使用硬编码路径。 +> **路由前缀**:分类列表、标签列表、归档页的 canonical URL 会读取 Halo CMS 的“主题路由设置”(设置 / 主题路由设置)配置,与站点实际路由保持同步,不使用硬编码路径。 ### 第三方插件页面 @@ -87,8 +83,8 @@ | 豆瓣 | `douban` | [plugin-douban](https://github.com/chengzhongxue/plugin-douban) | | 番剧 | `bangumi` | [halo-plugin-bangumi-data](https://github.com/ShiinaKin/halo-plugin-bangumi-data) | -> 第三方插件页面的标题/描述同样来自页面的 `<title>` 和 `<meta name="description">`,由对应插件的主题模板负责设置。豆瓣插件额外支持上下文 -`title` 变量作为中间回退(`<title>` 提取失败时使用)。 +> 第三方插件页面的标题/描述同样来自页面的 `<title>` 和 `<meta name="description">`,由对应插件的主题模板负责设置。 +> 豆瓣插件额外支持上下文 `title` 变量作为实体固定值回退。 ### 各页面输出字段差异 From a263185ad1396a6218eadd74af7f100c8727df15 Mon Sep 17 00:00:00 2001 From: HowieHz <howie_hzgo@outlook.com> Date: Thu, 26 Mar 2026 09:17:35 +0800 Subject: [PATCH 15/19] Add per-template SEO override support --- README.md | 22 +- .../timefactor/process/TimeFactorProcess.java | 260 ++++++++++++------ .../service/SettingConfigGetter.java | 15 + .../service/impl/SettingConfigGetterImpl.java | 10 +- src/main/resources/extensions/settings.yaml | 55 ++++ 5 files changed, 267 insertions(+), 95 deletions(-) diff --git a/README.md b/README.md index 7f74b72..714c59e 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ - 支持启用/禁用搜索引擎优化功能 - 可配置默认封面图片 - 自动获取站点信息(标题、Logo、关键词等) +- 支持按模板 ID 手动覆盖标题/描述,优先级最高 ### 🛡️ 性能优化 @@ -40,9 +41,15 @@ 插件不自己拼接或硬编码任何标题/描述文字,而是优先读取已渲染好的 `<head>` 内容,保证 SEO 标签与页面实际展示完全一致: -**标题**:`<title>` 标签文本 → 实体固定值 → 站点标题(设置 / 基本设置) → 不输出 +**标题**:SEO 覆盖配置(插件设置 / SEO 覆盖) → `<title>` 标签文本 → 实体固定值 → 站点标题(设置 / 基本设置) → 不输出 -**描述**:`<meta name="description">` 的 `content` → 实体固定值 → 站点描述(设置 / SEO 设置) → 不输出 +**描述**:SEO 覆盖配置(插件设置 / SEO 覆盖) → `<meta name="description">` 的 `content` → 实体固定值 → 站点描述(设置 / SEO +设置) → 不输出 + +> **局限性说明**:插件读取的 `<title>` 和 `<meta name="description">` 来自 Halo 在模板渲染前注入到内部模型(`IModel`)的值,由 +> Halo 核心框架写入,**与主题模板无关**。主题通过 Thymeleaf 模板动态渲染的标题/描述(如 `th:text` +> 表达式的计算结果)在此阶段尚未执行,因此无法被读取。如果主题依赖模板表达式定制标题格式,建议通过「SEO 覆盖」手动指定,以确保 +> SEO 标签内容与预期一致。 其中“实体固定值”因页面类型而异: @@ -62,15 +69,16 @@ | 首页 | `index` | — | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/index_) | | 文章详情页 | `post` | Post + User + Tag | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/post) | | 独立页面 | `page` | SinglePage + User | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/page) | -| 分类列表页 | `categories` | — | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/categories) | +| 分类集合页 | `categories` | — | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/categories) | | 分类详情页 | `category` | Category(封面、permalink) | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/category) | -| 标签列表页 | `tags` | — | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/tags) | -| 归档页 | `archives` | — | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/archives) | +| 标签集合页 | `tags` | — | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/tags) | +| 文章归档页 | `archives` | — | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/archives) | | 作者页 | `author` | User(头像、permalink) | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/author) | > **暂不支持**:标签详情页(`tag`)——Halo 的标签路由未注入 `_templateId` 上下文变量,插件无法识别该页面类型。 -> **路由前缀**:分类列表、标签列表、归档页的 canonical URL 会读取 Halo CMS 的“主题路由设置”(设置 / 主题路由设置)配置,与站点实际路由保持同步,不使用硬编码路径。 +> **路由前缀**:分类集合、标签集合、归档页的 canonical URL 会读取 Halo CMS 的“主题路由设置”(设置 / +> 主题路由设置)配置,与站点实际路由保持同步,不使用硬编码路径。 ### 第三方插件页面 @@ -97,7 +105,7 @@ | 分类详情页 | `website` | `WebPage` | ❌ | ❌ | ✅ 分类名 | 分类无发布时间和作者 | | 标签详情页 | `website` | `WebPage` | ❌ | ❌ | ✅ 标签名 | (⚠️ 暂不支持)标签无发布时间和作者 | | 作者页 | `profile` | `ProfilePage` | ❌ | ✅ | ✅ 作者名 | 作者页无发布时间 | -| 列表/聚合页 | `website` | `WebPage` | ❌ | ❌ | ✅ 站点关键词 | 首页、分类列表、标签列表、归档页、所有第三方插件页面 | +| 列表/聚合页 | `website` | `WebPage` | ❌ | ❌ | ✅ 站点关键词 | 首页、分类集合、标签集合、文章归档页、所有第三方插件页面 | > **省略规则**:当发布/更新时间为空时,`og:release_date`、`og:modified_time`、`bytedance:published_time`、 `bytedance:updated_time` 不输出;百度时间因子 `<script>` 中的 `pubDate`/`upDate` 字段不输出,若两者均为空则整个百度 script diff --git a/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java b/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java index 8a9629a..4525e38 100644 --- a/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java +++ b/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java @@ -154,30 +154,50 @@ public Mono<Void> process(ITemplateContext context, IModel model, var template = TemplateEnum.fromTemplateId(templateId); log.debug("Processing SEO for templateId: {}", templateId); - // 按模板类型分发,全局捕获异常避免影响页面渲染 - Mono<Void> result = switch (template) { - // ---- 官方页面 ---- - case INDEX -> processIndexSeoData(context, model); - case POST -> processPostSeoData(context, model); - case SINGLE_PAGE -> processSinglePageSeoData(context, model); - case CATEGORIES -> processCategoriesSeoData(context, model); - case CATEGORY -> processCategorySeoData(context, model); - case ARCHIVES -> processArchivesSeoData(context, model); - case TAGS -> processTagsSeoData(context, model); - case TAG -> processTagSeoData(context, model); - case AUTHOR -> processAuthorSeoData(context, model); - // ---- 第三方插件页面 ---- - case MOMENTS -> processMomentsSeoData(context, model); - case MOMENT -> processMomentSeoData(context, model); - case PHOTOS -> processPhotosSeoData(context, model); - case FRIENDS -> processFriendsSeoData(context, model); - case DOUBAN -> processDoubanSeoData(context, model); - case BANGUMI -> processBangumiSeoData(context, model); - case UNKNOWN -> Mono.empty(); - }; - - return result.onErrorResume(error -> { - log.warn("Failed to process SEO for templateId: {}", templateId, error); + return settingConfigGetter.getSeoOverrideConfig().flatMap(overrideCfg -> { + // 查找与当前模板 ID 匹配的 SEO 覆盖条目 + var overrides = overrideCfg.getSeoOverrides(); + var override = SeoOverride.NONE; + if (overrides != null && templateId != null) { + for (var entry : overrides) { + if (templateId.equals(entry.getTemplateId())) { + override = + new SeoOverride(hasValue(entry.getTitle()) ? entry.getTitle() : null, + hasValue(entry.getDescription()) ? entry.getDescription() : null); + break; + } + } + } + final var seoOverride = override; + + // 按模板类型分发,全局捕获异常避免影响页面渲染 + Mono<Void> result = switch (template) { + // ---- 官方页面 ---- + case INDEX -> processIndexSeoData(context, model, seoOverride); + case POST -> processPostSeoData(context, model, seoOverride); + case SINGLE_PAGE -> processSinglePageSeoData(context, model, seoOverride); + case CATEGORIES -> processCategoriesSeoData(context, model, seoOverride); + case CATEGORY -> processCategorySeoData(context, model, seoOverride); + case ARCHIVES -> processArchivesSeoData(context, model, seoOverride); + case TAGS -> processTagsSeoData(context, model, seoOverride); + case TAG -> processTagSeoData(context, model, seoOverride); + case AUTHOR -> processAuthorSeoData(context, model, seoOverride); + // ---- 第三方插件页面 ---- + case MOMENTS -> processMomentsSeoData(context, model, seoOverride); + case MOMENT -> processMomentSeoData(context, model, seoOverride); + case PHOTOS -> processPhotosSeoData(context, model, seoOverride); + case FRIENDS -> processFriendsSeoData(context, model, seoOverride); + case DOUBAN -> processDoubanSeoData(context, model, seoOverride); + case BANGUMI -> processBangumiSeoData(context, model, seoOverride); + case UNKNOWN -> Mono.empty(); + }; + + return result.onErrorResume(error -> { + log.warn("Failed to process SEO for templateId: {}", templateId, error); + return Mono.empty(); + }); + }).onErrorResume(error -> { + log.warn("Failed to fetch SEO override config for templateId: {}", templateId, error); return Mono.empty(); }); } @@ -195,26 +215,28 @@ public Mono<Void> process(ITemplateContext context, IModel model, * @see * <a href="https://docs.halo.run/developer-guide/theme/template-variables/index_">首页模板变量</a> */ - private Mono<Void> processIndexSeoData(ITemplateContext context, IModel model) { - return buildListPageSeoData(context, model, rules -> "/"); + private Mono<Void> processIndexSeoData(ITemplateContext context, IModel model, + SeoOverride override) { + return buildListPageSeoData(context, model, rules -> "/", override); } /** - * 分类列表页 SEO 注入。 + * 分类集合页 SEO 注入。 * * <p>展示所有分类的导航页面。 * 模板变量:{@code categories} (List<CategoryTreeVo>) * * @see - * <a href="https://docs.halo.run/developer-guide/theme/template-variables/categories">分类列表模板变量</a> + * <a href="https://docs.halo.run/developer-guide/theme/template-variables/categories">分类集合模板变量</a> */ - private Mono<Void> processCategoriesSeoData(ITemplateContext context, IModel model) { + private Mono<Void> processCategoriesSeoData(ITemplateContext context, IModel model, + SeoOverride override) { return buildListPageSeoData(context, model, - rules -> Optional.ofNullable(rules.getCategories()).orElse("/categories")); + rules -> Optional.ofNullable(rules.getCategories()).orElse("/categories"), override); } /** - * 归档页 SEO 注入。 + * 文章归档页 SEO 注入。 * * <p>按时间归档的文章列表页面。 * 模板变量:{@code archives} (UrlContextListResult<PostArchiveVo>) @@ -222,23 +244,25 @@ private Mono<Void> processCategoriesSeoData(ITemplateContext context, IModel mod * @see * <a href="https://docs.halo.run/developer-guide/theme/template-variables/archives">归档模板变量</a> */ - private Mono<Void> processArchivesSeoData(ITemplateContext context, IModel model) { + private Mono<Void> processArchivesSeoData(ITemplateContext context, IModel model, + SeoOverride override) { return buildListPageSeoData(context, model, - rules -> Optional.ofNullable(rules.getArchives()).orElse("/archives")); + rules -> Optional.ofNullable(rules.getArchives()).orElse("/archives"), override); } /** - * 标签列表页 SEO 注入。 + * 标签集合页 SEO 注入。 * * <p>展示所有标签的导航页面。 * 模板变量:{@code tags} (List<TagVo>) * * @see - * <a href="https://docs.halo.run/developer-guide/theme/template-variables/tags">标签列表模板变量</a> + * <a href="https://docs.halo.run/developer-guide/theme/template-variables/tags">标签集合模板变量</a> */ - private Mono<Void> processTagsSeoData(ITemplateContext context, IModel model) { + private Mono<Void> processTagsSeoData(ITemplateContext context, IModel model, + SeoOverride override) { return buildListPageSeoData(context, model, - rules -> Optional.ofNullable(rules.getTags()).orElse("/tags")); + rules -> Optional.ofNullable(rules.getTags()).orElse("/tags"), override); } /** @@ -250,8 +274,9 @@ private Mono<Void> processTagsSeoData(ITemplateContext context, IModel model) { * * @see <a href="https://github.com/halo-sigs/plugin-moments">plugin-moments</a> */ - private Mono<Void> processMomentsSeoData(ITemplateContext context, IModel model) { - return buildListPageSeoData(context, model, rules -> "/moments"); + private Mono<Void> processMomentsSeoData(ITemplateContext context, IModel model, + SeoOverride override) { + return buildListPageSeoData(context, model, rules -> "/moments", override); } /** @@ -261,9 +286,10 @@ private Mono<Void> processMomentsSeoData(ITemplateContext context, IModel model) * 瞬间详情页通常为短内容社交帖子,站点级 SEO 已满足基本需求。 * 如需更精确的 SEO 数据,可在未来版本通过 Finder API 增强。 */ - private Mono<Void> processMomentSeoData(ITemplateContext context, IModel model) { + private Mono<Void> processMomentSeoData(ITemplateContext context, IModel model, + SeoOverride override) { // MomentVo 在上下文中是延迟加载的 Mono 对象,无法直接获取 metadata.name,降级使用站点信息 - return buildListPageSeoData(context, model, rules -> "/moments"); + return buildListPageSeoData(context, model, rules -> "/moments", override); } /** @@ -274,8 +300,9 @@ private Mono<Void> processMomentSeoData(ITemplateContext context, IModel model) * * @see <a href="https://github.com/halo-sigs/plugin-photos">plugin-photos</a> */ - private Mono<Void> processPhotosSeoData(ITemplateContext context, IModel model) { - return buildListPageSeoData(context, model, rules -> "/photos"); + private Mono<Void> processPhotosSeoData(ITemplateContext context, IModel model, + SeoOverride override) { + return buildListPageSeoData(context, model, rules -> "/photos", override); } /** @@ -287,8 +314,9 @@ private Mono<Void> processPhotosSeoData(ITemplateContext context, IModel model) * * @see <a href="https://github.com/chengzhongxue/plugin-friends-new">plugin-friends-new</a> */ - private Mono<Void> processFriendsSeoData(ITemplateContext context, IModel model) { - return buildListPageSeoData(context, model, rules -> "/friends"); + private Mono<Void> processFriendsSeoData(ITemplateContext context, IModel model, + SeoOverride override) { + return buildListPageSeoData(context, model, rules -> "/friends", override); } /** @@ -301,11 +329,13 @@ private Mono<Void> processFriendsSeoData(ITemplateContext context, IModel model) * * @see <a href="https://github.com/chengzhongxue/plugin-douban">plugin-douban</a> */ - private Mono<Void> processDoubanSeoData(ITemplateContext context, IModel model) { + private Mono<Void> processDoubanSeoData(ITemplateContext context, IModel model, + SeoOverride override) { // 豆瓣路由在上下文中注入了 title 变量,作为 <title> 提取失败时的中间回退 var contextTitle = Optional.ofNullable(context.getVariable("title")).map(Object::toString) .filter(s -> !s.isBlank()).orElse(null); - return buildListPageSeoData(context, model, rules -> "/douban", contextTitle, null); + return buildListPageSeoData(context, model, rules -> "/douban", override, contextTitle, + null); } /** @@ -319,8 +349,9 @@ private Mono<Void> processDoubanSeoData(ITemplateContext context, IModel model) * @see * <a href="https://github.com/ShiinaKin/halo-plugin-bangumi-data">halo-plugin-bangumi-data</a> */ - private Mono<Void> processBangumiSeoData(ITemplateContext context, IModel model) { - return buildListPageSeoData(context, model, rules -> "/bangumi"); + private Mono<Void> processBangumiSeoData(ITemplateContext context, IModel model, + SeoOverride override) { + return buildListPageSeoData(context, model, rules -> "/bangumi", override); } // ======================== 详情页 SEO 处理器 ======================== @@ -343,14 +374,16 @@ private Mono<Void> processBangumiSeoData(ITemplateContext context, IModel model) * * @see <a href="https://docs.halo.run/developer-guide/theme/template-variables/post">文章模板变量</a> */ - private Mono<Void> processPostSeoData(ITemplateContext context, IModel model) { + private Mono<Void> processPostSeoData(ITemplateContext context, IModel model, + SeoOverride override) { var modelFactory = context.getModelFactory(); var postName = getNameVariable(context); if (postName == null) { return Mono.empty(); } - var title = extractTitleFromModel(model); - var description = extractMetaDescriptionFromModel(model); + var title = hasValue(override.title()) ? override.title() : extractTitleFromModel(model); + var description = hasValue(override.description()) ? override.description() + : extractMetaDescriptionFromModel(model); return client.fetch(Post.class, postName).flatMap( post -> buildSeoDataForPost(post, title, description).flatMap( seoData -> generateSeoTags(seoData, model, modelFactory))); @@ -371,15 +404,17 @@ private Mono<Void> processPostSeoData(ITemplateContext context, IModel model) { * @see * <a href="https://docs.halo.run/developer-guide/theme/template-variables/category">分类详情模板变量</a> */ - private Mono<Void> processCategorySeoData(ITemplateContext context, IModel model) { + private Mono<Void> processCategorySeoData(ITemplateContext context, IModel model, + SeoOverride override) { var modelFactory = context.getModelFactory(); // 分类详情页不注入 name 变量,从上下文的 CategoryVo 对象取 metadata.name var categoryName = getMetadataNameFromVo(context, "category"); if (categoryName == null) { return Mono.empty(); } - var title = extractTitleFromModel(model); - var description = extractMetaDescriptionFromModel(model); + var title = hasValue(override.title()) ? override.title() : extractTitleFromModel(model); + var description = hasValue(override.description()) ? override.description() + : extractMetaDescriptionFromModel(model); return client.fetch(Category.class, categoryName).flatMap( category -> buildSeoDataForCategory(category, title, description).flatMap( seoData -> generateSeoTags(seoData, model, modelFactory))); @@ -399,14 +434,16 @@ private Mono<Void> processCategorySeoData(ITemplateContext context, IModel model * @see * <a href="https://docs.halo.run/developer-guide/theme/template-variables/tag">标签详情模板变量</a> */ - private Mono<Void> processTagSeoData(ITemplateContext context, IModel model) { + private Mono<Void> processTagSeoData(ITemplateContext context, IModel model, + SeoOverride override) { var modelFactory = context.getModelFactory(); var tagName = getNameVariable(context); if (tagName == null) { return Mono.empty(); } - var title = extractTitleFromModel(model); - var description = extractMetaDescriptionFromModel(model); + var title = hasValue(override.title()) ? override.title() : extractTitleFromModel(model); + var description = hasValue(override.description()) ? override.description() + : extractMetaDescriptionFromModel(model); return client.fetch(Tag.class, tagName).flatMap( tag -> buildSeoDataForTag(tag, title, description).flatMap( seoData -> generateSeoTags(seoData, model, modelFactory))); @@ -428,14 +465,16 @@ private Mono<Void> processTagSeoData(ITemplateContext context, IModel model) { * @see * <a href="https://docs.halo.run/developer-guide/theme/template-variables/page">独立页面模板变量</a> */ - private Mono<Void> processSinglePageSeoData(ITemplateContext context, IModel model) { + private Mono<Void> processSinglePageSeoData(ITemplateContext context, IModel model, + SeoOverride override) { var modelFactory = context.getModelFactory(); var pageName = getNameVariable(context); if (pageName == null) { return Mono.empty(); } - var title = extractTitleFromModel(model); - var description = extractMetaDescriptionFromModel(model); + var title = hasValue(override.title()) ? override.title() : extractTitleFromModel(model); + var description = hasValue(override.description()) ? override.description() + : extractMetaDescriptionFromModel(model); return client.fetch(SinglePage.class, pageName).flatMap( page -> buildSeoDataForSinglePage(page, title, description).flatMap( seoData -> generateSeoTags(seoData, model, modelFactory))); @@ -455,14 +494,16 @@ private Mono<Void> processSinglePageSeoData(ITemplateContext context, IModel mod * @see * <a href="https://docs.halo.run/developer-guide/theme/template-variables/author">作者模板变量</a> */ - private Mono<Void> processAuthorSeoData(ITemplateContext context, IModel model) { + private Mono<Void> processAuthorSeoData(ITemplateContext context, IModel model, + SeoOverride override) { var modelFactory = context.getModelFactory(); var userName = getNameVariable(context); if (userName == null) { return Mono.empty(); } - var title = extractTitleFromModel(model); - var description = extractMetaDescriptionFromModel(model); + var title = hasValue(override.title()) ? override.title() : extractTitleFromModel(model); + var description = hasValue(override.description()) ? override.description() + : extractMetaDescriptionFromModel(model); return client.fetch(User.class, userName).flatMap( user -> buildSeoDataForAuthor(user, title, description).flatMap( seoData -> generateSeoTags(seoData, model, modelFactory))); @@ -473,7 +514,7 @@ private Mono<Void> processAuthorSeoData(ITemplateContext context, IModel model) /** * 列表页通用 SEO 数据构建。 * - * <p>设计思路:列表页(首页、分类列表、标签列表、归档页、第三方插件列表页) + * <p>设计思路:列表页(首页、分类集合、标签集合、文章归档页、第三方插件列表页) * 没有具体的内容实体对象,使用站点级信息构建 SEO 数据。 * <ul> * <li>标题格式:"pageTitle - siteName"(站点名称为空时仅 pageTitle)</li> @@ -488,17 +529,18 @@ private Mono<Void> processAuthorSeoData(ITemplateContext context, IModel model) * @param pathProvider 页面路径提供函数,接收 {@link SystemSetting.ThemeRouteRules} 以支持可配置路由前缀 */ private Mono<Void> buildListPageSeoData(ITemplateContext context, IModel model, - Function<SystemSetting.ThemeRouteRules, String> pathProvider) { - return buildListPageSeoData(context, model, pathProvider, null, null); + Function<SystemSetting.ThemeRouteRules, String> pathProvider, SeoOverride override) { + return buildListPageSeoData(context, model, pathProvider, override, null, null); } private Mono<Void> buildListPageSeoData(ITemplateContext context, IModel model, - Function<SystemSetting.ThemeRouteRules, String> pathProvider, String intermediateTitle, - String intermediateDesc) { + Function<SystemSetting.ThemeRouteRules, String> pathProvider, SeoOverride override, + String intermediateTitle, String intermediateDesc) { var modelFactory = context.getModelFactory(); - // 从已渲染的 head 中读取,主题负责内容,此处只是复用 - var title = extractTitleFromModel(model); - var description = extractMetaDescriptionFromModel(model); + // 覆盖值优先;无覆盖时从已渲染的 head 中读取 + var title = hasValue(override.title()) ? override.title() : extractTitleFromModel(model); + var description = hasValue(override.description()) ? override.description() + : extractMetaDescriptionFromModel(model); return Mono.zip(settingConfigGetter.getBasicConfig(), systemInfoGetter.get(), getThemeRouteRules()).flatMap(tuple -> { @@ -512,10 +554,12 @@ private Mono<Void> buildListPageSeoData(ITemplateContext context, IModel model, Optional.ofNullable(systemInfo.getSeo()).map(SystemInfo.SeoProp::getDescription) .orElse(null); - // 标题:页面 <title> → 中间值(实体/插件上下文变量) → 站点标题 → 空 - var finalTitle = firstNonBlank(title, intermediateTitle, siteName); - // 描述:页面 <meta name="description"> → 中间值 → 站点描述 → 空 - var finalDesc = firstNonBlank(description, intermediateDesc, siteDesc); + // 标题:覆盖值(占位符已替换) → 中间值(实体/插件上下文变量) → 站点标题 → 空 + var finalTitle = firstNonBlank(applyPlaceholders(title, systemInfo), intermediateTitle, + siteName); + // 描述:覆盖值(占位符已替换) → 中间值 → 站点描述 → 空 + var finalDesc = firstNonBlank(applyPlaceholders(description, systemInfo), intermediateDesc, + siteDesc); // 通过 ExternalLinkProcessor 将相对路径转为完整的外部 URL var pageUrl = externalLinkProcessor.processLink(pathProvider.apply(routeRules)); @@ -582,8 +626,10 @@ private Mono<SeoData> buildSeoDataForPost(Post post, String title, String descri var siteDesc = Optional.ofNullable(systemInfo.getSeo()).map(SystemInfo.SeoProp::getDescription) .orElse(null); - var finalTitle = firstNonBlank(title, post.getSpec().getTitle(), siteName); - var finalDesc = firstNonBlank(description, status.getExcerpt(), siteDesc); + var finalTitle = firstNonBlank(applyPlaceholders(title, systemInfo), + post.getSpec().getTitle(), siteName); + var finalDesc = firstNonBlank(applyPlaceholders(description, systemInfo), + status.getExcerpt(), siteDesc); // 封面:优先文章封面,回退到插件默认封面 var coverUrl = processPermalink( firstNonBlank(post.getSpec().getCover(), config.getDefaultImage())); @@ -659,8 +705,11 @@ private Mono<SeoData> buildSeoDataForSinglePage(SinglePage page, String title, Optional.ofNullable(systemInfo.getSeo()).map(SystemInfo.SeoProp::getKeywords) .orElse(null); - return new SeoData(firstNonBlank(title, page.getSpec().getTitle(), siteName), - firstNonBlank(description, status.getExcerpt(), siteDesc), coverUrl, pageUrl, + return new SeoData( + firstNonBlank(applyPlaceholders(title, systemInfo), page.getSpec().getTitle(), + siteName), + firstNonBlank(applyPlaceholders(description, systemInfo), status.getExcerpt(), + siteDesc), coverUrl, pageUrl, author, formatDateTime(publishInstant, BAIDU_FORMATTER, zoneId), formatDateTime(updateInstant, BAIDU_FORMATTER, zoneId), formatDateTime(publishInstant, GOOGLE_FORMATTER, zoneId), @@ -705,8 +754,11 @@ private Mono<SeoData> buildSeoDataForCategory(Category category, String title, var author = firstNonBlank(config.getDefaultAuthor(), siteName); var displayName = category.getSpec().getDisplayName(); - return new SeoData(firstNonBlank(title, category.getSpec().getDisplayName(), siteName), - firstNonBlank(description, category.getSpec().getDescription(), siteDesc), coverUrl, + return new SeoData( + firstNonBlank(applyPlaceholders(title, systemInfo), + category.getSpec().getDisplayName(), siteName), + firstNonBlank(applyPlaceholders(description, systemInfo), + category.getSpec().getDescription(), siteDesc), coverUrl, pageUrl, author, null, null, null, null, siteName, siteLogo, displayName, "website"); }); @@ -747,8 +799,11 @@ private Mono<SeoData> buildSeoDataForTag(Tag tag, String title, String descripti var author = firstNonBlank(config.getDefaultAuthor(), siteName); var displayName = tag.getSpec().getDisplayName(); - return new SeoData(firstNonBlank(title, tag.getSpec().getDisplayName(), siteName), - firstNonBlank(description, tag.getSpec().getDescription(), siteDesc), coverUrl, + return new SeoData( + firstNonBlank(applyPlaceholders(title, systemInfo), tag.getSpec().getDisplayName(), + siteName), + firstNonBlank(applyPlaceholders(description, systemInfo), + tag.getSpec().getDescription(), siteDesc), coverUrl, pageUrl, author, null, null, null, null, siteName, siteLogo, displayName, "website"); }); @@ -790,8 +845,11 @@ private Mono<SeoData> buildSeoDataForAuthor(User user, String title, String desc systemInfo.getLogo())); var siteLogo = processPermalink(systemInfo.getLogo()); - return new SeoData(firstNonBlank(title, user.getSpec().getDisplayName(), siteName), - firstNonBlank(description, user.getSpec().getBio(), siteDesc), coverUrl, pageUrl, + return new SeoData( + firstNonBlank(applyPlaceholders(title, systemInfo), user.getSpec().getDisplayName(), + siteName), + firstNonBlank(applyPlaceholders(description, systemInfo), user.getSpec().getBio(), + siteDesc), coverUrl, pageUrl, displayName, null, null, null, null, siteName, siteLogo, displayName, "profile"); }); } @@ -1188,6 +1246,28 @@ private boolean hasValue(String value) { return value != null && !value.isBlank(); } + /** + * 将 SEO 覆盖值中的占位符替换为实际的站点信息。 + * + * <p>支持的占位符: + * <ul> + * <li>{@code %SITENAME%} → 站点标题</li> + * <li>{@code %SITEDESC%} → 站点 SEO 描述</li> + * </ul> + * + * @param value 原始字符串,可能包含占位符;null 时原样返回 + * @param systemInfo 站点信息,用于替换占位符 + */ + private String applyPlaceholders(String value, SystemInfo systemInfo) { + if (value == null || value.isBlank()) { + return value; + } + var siteName = Optional.ofNullable(systemInfo.getTitle()).orElse(""); + var siteDesc = Optional.ofNullable(systemInfo.getSeo()) + .map(SystemInfo.SeoProp::getDescription).orElse(""); + return value.replace("%SITENAME%", siteName).replace("%SITEDESC%", siteDesc); + } + /** * 将相对路径或路径片段转为完整外部 URL,null 或空白时返回 null(表示无值)。 */ @@ -1259,6 +1339,14 @@ private String extractMetaDescriptionFromModel(IModel model) { // ======================== 数据结构 ======================== + /** + * SEO 覆盖值,从"SEO 覆盖"设置中读取,优先级高于页面 <title> 和 <meta description>。 + * title/description 为 null 表示未配置,不覆盖。 + */ + private record SeoOverride(String title, String description) { + static final SeoOverride NONE = new SeoOverride(null, null); + } + /** * SEO 数据记录,封装所有 SEO 标签生成所需的字段。 * diff --git a/src/main/java/cc/lik/timefactor/service/SettingConfigGetter.java b/src/main/java/cc/lik/timefactor/service/SettingConfigGetter.java index 181652e..26f8bac 100644 --- a/src/main/java/cc/lik/timefactor/service/SettingConfigGetter.java +++ b/src/main/java/cc/lik/timefactor/service/SettingConfigGetter.java @@ -8,12 +8,27 @@ public interface SettingConfigGetter { Mono<BasicConfig> getBasicConfig(); + Mono<SeoOverrideConfig> getSeoOverrideConfig(); + @Data class alternateLink { private String langCode; private String urlTemplate; } + @Data + class SeoOverrideEntry { + private String templateId; + private String title; + private String description; + } + + @Data + class SeoOverrideConfig { + public static final String GROUP = "seoOverrides"; + private List<SeoOverrideEntry> seoOverrides; + } + @Data class BasicConfig { public static final String GROUP = "basic"; diff --git a/src/main/java/cc/lik/timefactor/service/impl/SettingConfigGetterImpl.java b/src/main/java/cc/lik/timefactor/service/impl/SettingConfigGetterImpl.java index 162673b..24d242d 100644 --- a/src/main/java/cc/lik/timefactor/service/impl/SettingConfigGetterImpl.java +++ b/src/main/java/cc/lik/timefactor/service/impl/SettingConfigGetterImpl.java @@ -14,7 +14,13 @@ public class SettingConfigGetterImpl implements SettingConfigGetter { @Override public Mono<BasicConfig> getBasicConfig() { - return settingFetcher.fetch(BasicConfig.GROUP, BasicConfig.class) - .defaultIfEmpty(new BasicConfig()); + return settingFetcher.fetch(BasicConfig.GROUP, BasicConfig.class) + .defaultIfEmpty(new BasicConfig()); + } + + @Override + public Mono<SeoOverrideConfig> getSeoOverrideConfig() { + return settingFetcher.fetch(SeoOverrideConfig.GROUP, SeoOverrideConfig.class) + .defaultIfEmpty(new SeoOverrideConfig()); } } diff --git a/src/main/resources/extensions/settings.yaml b/src/main/resources/extensions/settings.yaml index 7db1c82..8c74298 100644 --- a/src/main/resources/extensions/settings.yaml +++ b/src/main/resources/extensions/settings.yaml @@ -162,3 +162,58 @@ spec: key: defaultAuthor value: "" help: "列表页、分类页、标签页等无具体作者的页面使用;留空时回退到站点名称" + - group: seoOverrides + label: SEO 覆盖 + formSchema: + - $formkit: array + label: 模板 SEO 覆盖 + name: seoOverrides + id: seoOverrides + key: seoOverrides + help: "按模板 ID 手动指定标题/描述,优先级高于页面 <title> 标签和 <meta name=\"description\">" + value: + - templateId: categories + title: 分类集合 - %SITENAME% + description: 分类集合 - %SITEDESC% + - templateId: tags + title: 标签集合 - %SITENAME% + description: 标签集合 - %SITEDESC% + - templateId: archives + title: 文章归档 - %SITENAME% + description: 文章归档 - %SITEDESC% + - templateId: moments + title: 瞬间 - %SITENAME% + description: 瞬间 - %SITEDESC% + - templateId: moment + title: 瞬间 - %SITENAME% + description: 瞬间 - %SITEDESC% + - templateId: photos + title: 图库 - %SITENAME% + description: 图库 - %SITEDESC% + - templateId: friends + title: 朋友圈 - %SITENAME% + description: 朋友圈 - %SITEDESC% + - templateId: douban + description: 豆瓣记录 - %SITEDESC% + - templateId: bangumi + title: 番剧 - %SITENAME% + description: 番剧 - %SITEDESC% + children: + - $formkit: text + label: 模板 ID + name: templateId + id: templateId + key: templateId + help: "例如:post、page、index、category、moments 等" + - $formkit: text + label: 标题 + name: title + id: title + key: title + help: "留空则不覆盖;支持占位符:%SITENAME%(站点标题)、%SITEDESC%(站点描述)" + - $formkit: text + label: 描述 + name: description + id: description + key: description + help: "留空则不覆盖;支持占位符:%SITENAME%(站点标题)、%SITEDESC%(站点描述)" From 4b2a40a99178e85f1150e8482a749189f1ad1350 Mon Sep 17 00:00:00 2001 From: HowieHz <howie_hzgo@outlook.com> Date: Thu, 26 Mar 2026 09:33:48 +0800 Subject: [PATCH 16/19] README: refine page tables and add template enum --- README.md | 60 +++++++++++++++++++++++++++---------------------------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 714c59e..799b747 100644 --- a/README.md +++ b/README.md @@ -39,11 +39,9 @@ ### 标题与描述的取值逻辑 -插件不自己拼接或硬编码任何标题/描述文字,而是优先读取已渲染好的 `<head>` 内容,保证 SEO 标签与页面实际展示完全一致: +**标题**:SEO 覆盖配置(插件设置 / SEO 覆盖) → 预注入的 `<title>` 标签文本 → 实体固定值 → 站点标题(设置 / 基本设置) → 不输出 -**标题**:SEO 覆盖配置(插件设置 / SEO 覆盖) → `<title>` 标签文本 → 实体固定值 → 站点标题(设置 / 基本设置) → 不输出 - -**描述**:SEO 覆盖配置(插件设置 / SEO 覆盖) → `<meta name="description">` 的 `content` → 实体固定值 → 站点描述(设置 / SEO +**描述**:SEO 覆盖配置(插件设置 / SEO 覆盖) → 预注入的 `<meta name="description">` 的 `content` → 实体固定值 → 站点描述(设置 / SEO 设置) → 不输出 > **局限性说明**:插件读取的 `<title>` 和 `<meta name="description">` 来自 Halo 在模板渲染前注入到内部模型(`IModel`)的值,由 @@ -53,26 +51,29 @@ 其中“实体固定值”因页面类型而异: -| 页面类型 | 标题实体值 | 描述实体值 | -|--------|-----------------------------|-----------------------------| -| 文章详情页 | `post.spec.title` | `post.status.excerpt` | -| 独立页面 | `singlePage.spec.title` | `singlePage.status.excerpt` | -| 分类详情页 | `category.spec.displayName` | `category.spec.description` | -| 标签详情页 | `tag.spec.displayName` | `tag.spec.description` | -| 作者页 | `user.spec.displayName` | `user.spec.bio` | -| 列表/聚合页 | — | — | +| 页面类型 | 标题实体值 | 描述实体值 | +|---------------------------------------------------------------------|-----------------------------|-----------------------------| +| 文章详情页 | `post.spec.title` | `post.status.excerpt` | +| 独立页面 | `singlePage.spec.title` | `singlePage.status.excerpt` | +| 分类详情页 | `category.spec.displayName` | `category.spec.description` | +| 标签详情页 | `tag.spec.displayName` | `tag.spec.description` | +| 作者页 | `user.spec.displayName` | `user.spec.bio` | +| 豆瓣([plugin-douban](https://github.com/chengzhongxue/plugin-douban)) | `title` | 无 | +| 其他页面 | 无 | 无 | + +### 已知模板 ID 枚举 -### Halo 内置页面 +#### Halo 内置页面 -| 页面类型 | 模板 ID | 额外数据来源(封面/作者/时间等) | 文档 | +| 页面类型 | 模板 ID | 数据来源类型 | 文档 | |-------|--------------|------------------------|-----------------------------------------------------------------------------------| -| 首页 | `index` | — | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/index_) | -| 文章详情页 | `post` | Post + User + Tag | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/post) | -| 独立页面 | `page` | SinglePage + User | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/page) | -| 分类集合页 | `categories` | — | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/categories) | +| 首页 | `index` | 无 | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/index_) | +| 文章详情页 | `post` | Post, User, Tag | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/post) | +| 独立页面 | `page` | SinglePage, User | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/page) | +| 分类集合页 | `categories` | 无 | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/categories) | | 分类详情页 | `category` | Category(封面、permalink) | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/category) | -| 标签集合页 | `tags` | — | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/tags) | -| 文章归档页 | `archives` | — | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/archives) | +| 标签集合页 | `tags` | 无 | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/tags) | +| 文章归档页 | `archives` | 无 | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/archives) | | 作者页 | `author` | User(头像、permalink) | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/author) | > **暂不支持**:标签详情页(`tag`)——Halo 的标签路由未注入 `_templateId` 上下文变量,插件无法识别该页面类型。 @@ -80,7 +81,7 @@ > **路由前缀**:分类集合、标签集合、归档页的 canonical URL 会读取 Halo CMS 的“主题路由设置”(设置 / > 主题路由设置)配置,与站点实际路由保持同步,不使用硬编码路径。 -### 第三方插件页面 +#### 第三方插件页面 | 页面类型 | 模板 ID | 插件 | |------|-----------|-----------------------------------------------------------------------------------| @@ -91,20 +92,17 @@ | 豆瓣 | `douban` | [plugin-douban](https://github.com/chengzhongxue/plugin-douban) | | 番剧 | `bangumi` | [halo-plugin-bangumi-data](https://github.com/ShiinaKin/halo-plugin-bangumi-data) | -> 第三方插件页面的标题/描述同样来自页面的 `<title>` 和 `<meta name="description">`,由对应插件的主题模板负责设置。 -> 豆瓣插件额外支持上下文 `title` 变量作为实体固定值回退。 - ### 各页面输出字段差异 并非所有页面都拥有完整的 SEO 字段。当某个字段为空时,对应的 meta/script 标签会被**自动省略**(不输出空值标签),以保证结构化数据的有效性。 -| 页面类型 | `og:type` | Schema.org `@type` | 发布/更新时间 | 作者 | 关键词 | 说明 | -|--------|-----------|--------------------|:-------:|:--:|:-------:|----------------------------| -| 文章详情页 | `article` | `BlogPosting` | ✅ | ✅ | ✅ 文章标签 | 字段最完整的页面类型 | -| 独立页面 | `article` | `BlogPosting` | ✅ | ✅ | ✅ 站点关键词 | 独立页面无标签,关键词回退到站点级 | -| 分类详情页 | `website` | `WebPage` | ❌ | ❌ | ✅ 分类名 | 分类无发布时间和作者 | -| 标签详情页 | `website` | `WebPage` | ❌ | ❌ | ✅ 标签名 | (⚠️ 暂不支持)标签无发布时间和作者 | -| 作者页 | `profile` | `ProfilePage` | ❌ | ✅ | ✅ 作者名 | 作者页无发布时间 | +| 页面类型 | `og:type` | Schema.org `@type` | 发布/更新时间 | 作者 | 关键词 | 说明 | +|--------|-----------|--------------------|:-------:|:--:|:-------:|------------------------------| +| 文章详情页 | `article` | `BlogPosting` | ✅ | ✅ | ✅ 文章标签 | 字段最完整的页面类型 | +| 独立页面 | `article` | `BlogPosting` | ✅ | ✅ | ✅ 站点关键词 | 独立页面无标签,关键词回退到站点级 | +| 分类详情页 | `website` | `WebPage` | ❌ | ❌ | ✅ 分类名 | 分类无发布时间和作者 | +| 标签详情页 | `website` | `WebPage` | ❌ | ❌ | ✅ 标签名 | (⚠️ 暂不支持)标签无发布时间和作者 | +| 作者页 | `profile` | `ProfilePage` | ❌ | ✅ | ✅ 作者名 | 作者页无发布时间 | | 列表/聚合页 | `website` | `WebPage` | ❌ | ❌ | ✅ 站点关键词 | 首页、分类集合、标签集合、文章归档页、所有第三方插件页面 | > **省略规则**:当发布/更新时间为空时,`og:release_date`、`og:modified_time`、`bytedance:published_time`、 From 511459417fc606bdc96f09c2d2beb051efcf1578 Mon Sep 17 00:00:00 2001 From: HowieHz <howie_hzgo@outlook.com> Date: Thu, 26 Mar 2026 09:41:28 +0800 Subject: [PATCH 17/19] Support %TITLE%/%DESC% placeholders in SEO --- .../timefactor/process/TimeFactorProcess.java | 158 ++++++++++-------- src/main/resources/extensions/settings.yaml | 11 +- 2 files changed, 95 insertions(+), 74 deletions(-) diff --git a/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java b/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java index 4525e38..e5c2367 100644 --- a/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java +++ b/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java @@ -381,12 +381,13 @@ private Mono<Void> processPostSeoData(ITemplateContext context, IModel model, if (postName == null) { return Mono.empty(); } - var title = hasValue(override.title()) ? override.title() : extractTitleFromModel(model); - var description = hasValue(override.description()) ? override.description() - : extractMetaDescriptionFromModel(model); + var extractedTitle = extractTitleFromModel(model); + var extractedDesc = extractMetaDescriptionFromModel(model); + var title = hasValue(override.title()) ? override.title() : extractedTitle; + var description = hasValue(override.description()) ? override.description() : extractedDesc; return client.fetch(Post.class, postName).flatMap( - post -> buildSeoDataForPost(post, title, description).flatMap( - seoData -> generateSeoTags(seoData, model, modelFactory))); + post -> buildSeoDataForPost(post, title, description, extractedTitle, + extractedDesc).flatMap(seoData -> generateSeoTags(seoData, model, modelFactory))); } /** @@ -412,12 +413,13 @@ private Mono<Void> processCategorySeoData(ITemplateContext context, IModel model if (categoryName == null) { return Mono.empty(); } - var title = hasValue(override.title()) ? override.title() : extractTitleFromModel(model); - var description = hasValue(override.description()) ? override.description() - : extractMetaDescriptionFromModel(model); + var extractedTitle = extractTitleFromModel(model); + var extractedDesc = extractMetaDescriptionFromModel(model); + var title = hasValue(override.title()) ? override.title() : extractedTitle; + var description = hasValue(override.description()) ? override.description() : extractedDesc; return client.fetch(Category.class, categoryName).flatMap( - category -> buildSeoDataForCategory(category, title, description).flatMap( - seoData -> generateSeoTags(seoData, model, modelFactory))); + category -> buildSeoDataForCategory(category, title, description, extractedTitle, + extractedDesc).flatMap(seoData -> generateSeoTags(seoData, model, modelFactory))); } /** @@ -441,12 +443,13 @@ private Mono<Void> processTagSeoData(ITemplateContext context, IModel model, if (tagName == null) { return Mono.empty(); } - var title = hasValue(override.title()) ? override.title() : extractTitleFromModel(model); - var description = hasValue(override.description()) ? override.description() - : extractMetaDescriptionFromModel(model); + var extractedTitle = extractTitleFromModel(model); + var extractedDesc = extractMetaDescriptionFromModel(model); + var title = hasValue(override.title()) ? override.title() : extractedTitle; + var description = hasValue(override.description()) ? override.description() : extractedDesc; return client.fetch(Tag.class, tagName).flatMap( - tag -> buildSeoDataForTag(tag, title, description).flatMap( - seoData -> generateSeoTags(seoData, model, modelFactory))); + tag -> buildSeoDataForTag(tag, title, description, extractedTitle, + extractedDesc).flatMap(seoData -> generateSeoTags(seoData, model, modelFactory))); } /** @@ -472,12 +475,13 @@ private Mono<Void> processSinglePageSeoData(ITemplateContext context, IModel mod if (pageName == null) { return Mono.empty(); } - var title = hasValue(override.title()) ? override.title() : extractTitleFromModel(model); - var description = hasValue(override.description()) ? override.description() - : extractMetaDescriptionFromModel(model); + var extractedTitle = extractTitleFromModel(model); + var extractedDesc = extractMetaDescriptionFromModel(model); + var title = hasValue(override.title()) ? override.title() : extractedTitle; + var description = hasValue(override.description()) ? override.description() : extractedDesc; return client.fetch(SinglePage.class, pageName).flatMap( - page -> buildSeoDataForSinglePage(page, title, description).flatMap( - seoData -> generateSeoTags(seoData, model, modelFactory))); + page -> buildSeoDataForSinglePage(page, title, description, extractedTitle, + extractedDesc).flatMap(seoData -> generateSeoTags(seoData, model, modelFactory))); } /** @@ -501,12 +505,13 @@ private Mono<Void> processAuthorSeoData(ITemplateContext context, IModel model, if (userName == null) { return Mono.empty(); } - var title = hasValue(override.title()) ? override.title() : extractTitleFromModel(model); - var description = hasValue(override.description()) ? override.description() - : extractMetaDescriptionFromModel(model); + var extractedTitle = extractTitleFromModel(model); + var extractedDesc = extractMetaDescriptionFromModel(model); + var title = hasValue(override.title()) ? override.title() : extractedTitle; + var description = hasValue(override.description()) ? override.description() : extractedDesc; return client.fetch(User.class, userName).flatMap( - user -> buildSeoDataForAuthor(user, title, description).flatMap( - seoData -> generateSeoTags(seoData, model, modelFactory))); + user -> buildSeoDataForAuthor(user, title, description, extractedTitle, + extractedDesc).flatMap(seoData -> generateSeoTags(seoData, model, modelFactory))); } // ======================== SEO 数据构建方法 ======================== @@ -537,10 +542,12 @@ private Mono<Void> buildListPageSeoData(ITemplateContext context, IModel model, Function<SystemSetting.ThemeRouteRules, String> pathProvider, SeoOverride override, String intermediateTitle, String intermediateDesc) { var modelFactory = context.getModelFactory(); - // 覆盖值优先;无覆盖时从已渲染的 head 中读取 - var title = hasValue(override.title()) ? override.title() : extractTitleFromModel(model); - var description = hasValue(override.description()) ? override.description() - : extractMetaDescriptionFromModel(model); + // 始终提取原始值,用于 %TITLE%/%DESC% 占位符展开 + var extractedTitle = extractTitleFromModel(model); + var extractedDesc = extractMetaDescriptionFromModel(model); + // 覆盖值优先;无覆盖时使用提取值 + var title = hasValue(override.title()) ? override.title() : extractedTitle; + var description = hasValue(override.description()) ? override.description() : extractedDesc; return Mono.zip(settingConfigGetter.getBasicConfig(), systemInfoGetter.get(), getThemeRouteRules()).flatMap(tuple -> { @@ -555,11 +562,13 @@ private Mono<Void> buildListPageSeoData(ITemplateContext context, IModel model, .orElse(null); // 标题:覆盖值(占位符已替换) → 中间值(实体/插件上下文变量) → 站点标题 → 空 - var finalTitle = firstNonBlank(applyPlaceholders(title, systemInfo), intermediateTitle, - siteName); + var finalTitle = + firstNonBlank(applyPlaceholders(title, extractedTitle, extractedDesc, systemInfo), + intermediateTitle, siteName); // 描述:覆盖值(占位符已替换) → 中间值 → 站点描述 → 空 - var finalDesc = firstNonBlank(applyPlaceholders(description, systemInfo), intermediateDesc, - siteDesc); + var finalDesc = firstNonBlank( + applyPlaceholders(description, extractedTitle, extractedDesc, systemInfo), + intermediateDesc, siteDesc); // 通过 ExternalLinkProcessor 将相对路径转为完整的外部 URL var pageUrl = externalLinkProcessor.processLink(pathProvider.apply(routeRules)); @@ -605,7 +614,8 @@ private Mono<Void> buildListPageSeoData(ITemplateContext context, IModel model, * @param post 文章 Extension 对象 * @return 包含完整 SEO 数据的 Mono */ - private Mono<SeoData> buildSeoDataForPost(Post post, String title, String description) { + private Mono<SeoData> buildSeoDataForPost(Post post, String title, String description, + String extractedTitle, String extractedDesc) { // 并行获取:文章所有者、文章标签、插件配置、系统信息 return Mono.zip(client.fetch(User.class, post.getSpec().getOwner()), findTagForPost(post), settingConfigGetter.getBasicConfig(), systemInfoGetter.get()).map(tuple -> { @@ -626,9 +636,11 @@ private Mono<SeoData> buildSeoDataForPost(Post post, String title, String descri var siteDesc = Optional.ofNullable(systemInfo.getSeo()).map(SystemInfo.SeoProp::getDescription) .orElse(null); - var finalTitle = firstNonBlank(applyPlaceholders(title, systemInfo), - post.getSpec().getTitle(), siteName); - var finalDesc = firstNonBlank(applyPlaceholders(description, systemInfo), + var finalTitle = + firstNonBlank(applyPlaceholders(title, extractedTitle, extractedDesc, systemInfo), + post.getSpec().getTitle(), siteName); + var finalDesc = firstNonBlank( + applyPlaceholders(description, extractedTitle, extractedDesc, systemInfo), status.getExcerpt(), siteDesc); // 封面:优先文章封面,回退到插件默认封面 var coverUrl = processPermalink( @@ -677,7 +689,7 @@ private Mono<SeoData> buildSeoDataForPost(Post post, String title, String descri * @return 包含 SEO 数据的 Mono */ private Mono<SeoData> buildSeoDataForSinglePage(SinglePage page, String title, - String description) { + String description, String extractedTitle, String extractedDesc) { return Mono.zip(client.fetch(User.class, page.getSpec().getOwner()), settingConfigGetter.getBasicConfig(), systemInfoGetter.get()).map(tuple -> { var user = tuple.getT1(); @@ -706,11 +718,11 @@ private Mono<SeoData> buildSeoDataForSinglePage(SinglePage page, String title, .orElse(null); return new SeoData( - firstNonBlank(applyPlaceholders(title, systemInfo), page.getSpec().getTitle(), - siteName), - firstNonBlank(applyPlaceholders(description, systemInfo), status.getExcerpt(), - siteDesc), coverUrl, pageUrl, - author, formatDateTime(publishInstant, BAIDU_FORMATTER, zoneId), + firstNonBlank(applyPlaceholders(title, extractedTitle, extractedDesc, systemInfo), + page.getSpec().getTitle(), siteName), firstNonBlank( + applyPlaceholders(description, extractedTitle, extractedDesc, systemInfo), + status.getExcerpt(), siteDesc), coverUrl, pageUrl, author, + formatDateTime(publishInstant, BAIDU_FORMATTER, zoneId), formatDateTime(updateInstant, BAIDU_FORMATTER, zoneId), formatDateTime(publishInstant, GOOGLE_FORMATTER, zoneId), formatDateTime(updateInstant, GOOGLE_FORMATTER, zoneId), siteName, siteLogo, @@ -737,7 +749,7 @@ author, formatDateTime(publishInstant, BAIDU_FORMATTER, zoneId), * @return 包含 SEO 数据的 Mono */ private Mono<SeoData> buildSeoDataForCategory(Category category, String title, - String description) { + String description, String extractedTitle, String extractedDesc) { return Mono.zip(settingConfigGetter.getBasicConfig(), systemInfoGetter.get()).map(tuple -> { var config = tuple.getT1(); var systemInfo = tuple.getT2(); @@ -755,12 +767,11 @@ private Mono<SeoData> buildSeoDataForCategory(Category category, String title, var displayName = category.getSpec().getDisplayName(); return new SeoData( - firstNonBlank(applyPlaceholders(title, systemInfo), - category.getSpec().getDisplayName(), siteName), - firstNonBlank(applyPlaceholders(description, systemInfo), - category.getSpec().getDescription(), siteDesc), coverUrl, - pageUrl, author, null, null, null, null, siteName, siteLogo, displayName, - "website"); + firstNonBlank(applyPlaceholders(title, extractedTitle, extractedDesc, systemInfo), + category.getSpec().getDisplayName(), siteName), firstNonBlank( + applyPlaceholders(description, extractedTitle, extractedDesc, systemInfo), + category.getSpec().getDescription(), siteDesc), coverUrl, pageUrl, author, null, + null, null, null, siteName, siteLogo, displayName, "website"); }); } @@ -782,7 +793,8 @@ private Mono<SeoData> buildSeoDataForCategory(Category category, String title, * @param tag 标签 Extension 对象 * @return 包含 SEO 数据的 Mono */ - private Mono<SeoData> buildSeoDataForTag(Tag tag, String title, String description) { + private Mono<SeoData> buildSeoDataForTag(Tag tag, String title, String description, + String extractedTitle, String extractedDesc) { return Mono.zip(settingConfigGetter.getBasicConfig(), systemInfoGetter.get()).map(tuple -> { var config = tuple.getT1(); var systemInfo = tuple.getT2(); @@ -800,12 +812,11 @@ private Mono<SeoData> buildSeoDataForTag(Tag tag, String title, String descripti var displayName = tag.getSpec().getDisplayName(); return new SeoData( - firstNonBlank(applyPlaceholders(title, systemInfo), tag.getSpec().getDisplayName(), - siteName), - firstNonBlank(applyPlaceholders(description, systemInfo), - tag.getSpec().getDescription(), siteDesc), coverUrl, - pageUrl, author, null, null, null, null, siteName, siteLogo, displayName, - "website"); + firstNonBlank(applyPlaceholders(title, extractedTitle, extractedDesc, systemInfo), + tag.getSpec().getDisplayName(), siteName), firstNonBlank( + applyPlaceholders(description, extractedTitle, extractedDesc, systemInfo), + tag.getSpec().getDescription(), siteDesc), coverUrl, pageUrl, author, null, null, + null, null, siteName, siteLogo, displayName, "website"); }); } @@ -828,7 +839,8 @@ private Mono<SeoData> buildSeoDataForTag(Tag tag, String title, String descripti * @param user 用户 Extension 对象 * @return 包含 SEO 数据的 Mono */ - private Mono<SeoData> buildSeoDataForAuthor(User user, String title, String description) { + private Mono<SeoData> buildSeoDataForAuthor(User user, String title, String description, + String extractedTitle, String extractedDesc) { return Mono.zip(settingConfigGetter.getBasicConfig(), systemInfoGetter.get()).map(tuple -> { var config = tuple.getT1(); var systemInfo = tuple.getT2(); @@ -846,11 +858,11 @@ private Mono<SeoData> buildSeoDataForAuthor(User user, String title, String desc var siteLogo = processPermalink(systemInfo.getLogo()); return new SeoData( - firstNonBlank(applyPlaceholders(title, systemInfo), user.getSpec().getDisplayName(), - siteName), - firstNonBlank(applyPlaceholders(description, systemInfo), user.getSpec().getBio(), - siteDesc), coverUrl, pageUrl, - displayName, null, null, null, null, siteName, siteLogo, displayName, "profile"); + firstNonBlank(applyPlaceholders(title, extractedTitle, extractedDesc, systemInfo), + user.getSpec().getDisplayName(), siteName), firstNonBlank( + applyPlaceholders(description, extractedTitle, extractedDesc, systemInfo), + user.getSpec().getBio(), siteDesc), coverUrl, pageUrl, displayName, null, null, + null, null, siteName, siteLogo, displayName, "profile"); }); } @@ -1247,25 +1259,33 @@ private boolean hasValue(String value) { } /** - * 将 SEO 覆盖值中的占位符替换为实际的站点信息。 + * 将 SEO 覆盖值中的占位符替换为实际内容。 * * <p>支持的占位符: * <ul> + * <li>{@code %TITLE%} → 页面预注入的 {@code <title>} 标签文本</li> + * <li>{@code %DESC%} → 页面预注入的 {@code <meta name="description">} content</li> * <li>{@code %SITENAME%} → 站点标题</li> * <li>{@code %SITEDESC%} → 站点 SEO 描述</li> * </ul> * * @param value 原始字符串,可能包含占位符;null 时原样返回 - * @param systemInfo 站点信息,用于替换占位符 + * @param extractedTitle 从 head 模型提取的 <title> 文本,用于替换 {@code %TITLE%} + * @param extractedDesc 从 head 模型提取的 meta description,用于替换 {@code %DESC%} + * @param systemInfo 站点信息,用于替换 {@code %SITENAME%} 和 {@code %SITEDESC%} */ - private String applyPlaceholders(String value, SystemInfo systemInfo) { + private String applyPlaceholders(String value, String extractedTitle, String extractedDesc, + SystemInfo systemInfo) { if (value == null || value.isBlank()) { return value; } var siteName = Optional.ofNullable(systemInfo.getTitle()).orElse(""); - var siteDesc = Optional.ofNullable(systemInfo.getSeo()) - .map(SystemInfo.SeoProp::getDescription).orElse(""); - return value.replace("%SITENAME%", siteName).replace("%SITEDESC%", siteDesc); + var siteDesc = + Optional.ofNullable(systemInfo.getSeo()).map(SystemInfo.SeoProp::getDescription) + .orElse(""); + return value.replace("%TITLE%", extractedTitle != null ? extractedTitle : "") + .replace("%DESC%", extractedDesc != null ? extractedDesc : "") + .replace("%SITENAME%", siteName).replace("%SITEDESC%", siteDesc); } /** diff --git a/src/main/resources/extensions/settings.yaml b/src/main/resources/extensions/settings.yaml index 8c74298..4af5184 100644 --- a/src/main/resources/extensions/settings.yaml +++ b/src/main/resources/extensions/settings.yaml @@ -177,10 +177,10 @@ spec: description: 分类集合 - %SITEDESC% - templateId: tags title: 标签集合 - %SITENAME% - description: 标签集合 - %SITEDESC% + description: 标签集合 - %SITEDESC% - templateId: archives title: 文章归档 - %SITENAME% - description: 文章归档 - %SITEDESC% + description: 文章归档 - %SITEDESC% - templateId: moments title: 瞬间 - %SITENAME% description: 瞬间 - %SITEDESC% @@ -194,7 +194,8 @@ spec: title: 朋友圈 - %SITENAME% description: 朋友圈 - %SITEDESC% - templateId: douban - description: 豆瓣记录 - %SITEDESC% + title: "%TITLE%" + description: "%TITLE% - %SITEDESC%" - templateId: bangumi title: 番剧 - %SITENAME% description: 番剧 - %SITEDESC% @@ -210,10 +211,10 @@ spec: name: title id: title key: title - help: "留空则不覆盖;支持占位符:%SITENAME%(站点标题)、%SITEDESC%(站点描述)" + help: "留空则不覆盖;支持占位符:%TITLE%(页面标题)、%DESC%(页面描述)、%SITENAME%(站点标题)、%SITEDESC%(站点描述)" - $formkit: text label: 描述 name: description id: description key: description - help: "留空则不覆盖;支持占位符:%SITENAME%(站点标题)、%SITEDESC%(站点描述)" + help: "留空则不覆盖;支持占位符:%TITLE%(页面标题)、%DESC%(页面描述)、%SITENAME%(站点标题)、%SITEDESC%(站点描述)" From 7b2cf33f1fc264367716faaad447a1bd83247d2e Mon Sep 17 00:00:00 2001 From: HowieHz <howie_hzgo@outlook.com> Date: Thu, 26 Mar 2026 09:54:56 +0800 Subject: [PATCH 18/19] Use entity fallback for SEO placeholders --- README.md | 16 +++- .../timefactor/process/TimeFactorProcess.java | 91 ++++++++++--------- 2 files changed, 65 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 799b747..29826ff 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,8 @@ **标题**:SEO 覆盖配置(插件设置 / SEO 覆盖) → 预注入的 `<title>` 标签文本 → 实体固定值 → 站点标题(设置 / 基本设置) → 不输出 -**描述**:SEO 覆盖配置(插件设置 / SEO 覆盖) → 预注入的 `<meta name="description">` 的 `content` → 实体固定值 → 站点描述(设置 / SEO +**描述**:SEO 覆盖配置(插件设置 / SEO 覆盖) → 预注入的 `<meta name="description">` 的 `content` → 实体固定值 → +站点描述(设置 / SEO 设置) → 不输出 > **局限性说明**:插件读取的 `<title>` 和 `<meta name="description">` 来自 Halo 在模板渲染前注入到内部模型(`IModel`)的值,由 @@ -49,6 +50,19 @@ > 表达式的计算结果)在此阶段尚未执行,因此无法被读取。如果主题依赖模板表达式定制标题格式,建议通过「SEO 覆盖」手动指定,以确保 > SEO 标签内容与预期一致。 +#### SEO 覆盖支持的占位符 + +标题和描述字段均支持以下占位符,可自由组合: + +| 占位符 | 展开为 | +|--------------|-------------------------------------------------------| +| `%TITLE%` | 预注入的 `<title>` 标签文本 → 实体固定值(见下表) | +| `%DESC%` | 预注入的 `<meta name="description">` content → 实体固定值(见下表) | +| `%SITENAME%` | 站点标题 | +| `%SITEDESC%` | 站点 SEO 描述 | + +示例:描述填写 `%TITLE% - %SITEDESC%`,在文章页会展开为 `文章标题 - 站点描述`。 + 其中“实体固定值”因页面类型而异: | 页面类型 | 标题实体值 | 描述实体值 | diff --git a/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java b/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java index e5c2367..5d485f3 100644 --- a/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java +++ b/src/main/java/cc/lik/timefactor/process/TimeFactorProcess.java @@ -561,14 +561,17 @@ private Mono<Void> buildListPageSeoData(ITemplateContext context, IModel model, Optional.ofNullable(systemInfo.getSeo()).map(SystemInfo.SeoProp::getDescription) .orElse(null); + // %TITLE%/%DESC% 展开时,用 extracted 优先、intermediate 兜底的有效值, + // 避免 extracted 为 null 时占位符展开为空串导致 firstNonBlank 无法正常回退 + var phTitle = firstNonBlank(extractedTitle, intermediateTitle); + var phDesc = firstNonBlank(extractedDesc, intermediateDesc); // 标题:覆盖值(占位符已替换) → 中间值(实体/插件上下文变量) → 站点标题 → 空 - var finalTitle = - firstNonBlank(applyPlaceholders(title, extractedTitle, extractedDesc, systemInfo), - intermediateTitle, siteName); + var finalTitle = firstNonBlank(applyPlaceholders(title, phTitle, phDesc, systemInfo), + intermediateTitle, siteName); // 描述:覆盖值(占位符已替换) → 中间值 → 站点描述 → 空 - var finalDesc = firstNonBlank( - applyPlaceholders(description, extractedTitle, extractedDesc, systemInfo), - intermediateDesc, siteDesc); + var finalDesc = + firstNonBlank(applyPlaceholders(description, phTitle, phDesc, systemInfo), + intermediateDesc, siteDesc); // 通过 ExternalLinkProcessor 将相对路径转为完整的外部 URL var pageUrl = externalLinkProcessor.processLink(pathProvider.apply(routeRules)); @@ -636,12 +639,13 @@ private Mono<SeoData> buildSeoDataForPost(Post post, String title, String descri var siteDesc = Optional.ofNullable(systemInfo.getSeo()).map(SystemInfo.SeoProp::getDescription) .orElse(null); - var finalTitle = - firstNonBlank(applyPlaceholders(title, extractedTitle, extractedDesc, systemInfo), - post.getSpec().getTitle(), siteName); - var finalDesc = firstNonBlank( - applyPlaceholders(description, extractedTitle, extractedDesc, systemInfo), - status.getExcerpt(), siteDesc); + var phTitle = firstNonBlank(extractedTitle, post.getSpec().getTitle()); + var phDesc = firstNonBlank(extractedDesc, status.getExcerpt()); + var finalTitle = firstNonBlank(applyPlaceholders(title, phTitle, phDesc, systemInfo), + post.getSpec().getTitle(), siteName); + var finalDesc = + firstNonBlank(applyPlaceholders(description, phTitle, phDesc, systemInfo), + status.getExcerpt(), siteDesc); // 封面:优先文章封面,回退到插件默认封面 var coverUrl = processPermalink( firstNonBlank(post.getSpec().getCover(), config.getDefaultImage())); @@ -717,11 +721,12 @@ private Mono<SeoData> buildSeoDataForSinglePage(SinglePage page, String title, Optional.ofNullable(systemInfo.getSeo()).map(SystemInfo.SeoProp::getKeywords) .orElse(null); - return new SeoData( - firstNonBlank(applyPlaceholders(title, extractedTitle, extractedDesc, systemInfo), - page.getSpec().getTitle(), siteName), firstNonBlank( - applyPlaceholders(description, extractedTitle, extractedDesc, systemInfo), - status.getExcerpt(), siteDesc), coverUrl, pageUrl, author, + var phTitle = firstNonBlank(extractedTitle, page.getSpec().getTitle()); + var phDesc = firstNonBlank(extractedDesc, status.getExcerpt()); + return new SeoData(firstNonBlank(applyPlaceholders(title, phTitle, phDesc, systemInfo), + page.getSpec().getTitle(), siteName), + firstNonBlank(applyPlaceholders(description, phTitle, phDesc, systemInfo), + status.getExcerpt(), siteDesc), coverUrl, pageUrl, author, formatDateTime(publishInstant, BAIDU_FORMATTER, zoneId), formatDateTime(updateInstant, BAIDU_FORMATTER, zoneId), formatDateTime(publishInstant, GOOGLE_FORMATTER, zoneId), @@ -766,11 +771,12 @@ private Mono<SeoData> buildSeoDataForCategory(Category category, String title, var author = firstNonBlank(config.getDefaultAuthor(), siteName); var displayName = category.getSpec().getDisplayName(); - return new SeoData( - firstNonBlank(applyPlaceholders(title, extractedTitle, extractedDesc, systemInfo), - category.getSpec().getDisplayName(), siteName), firstNonBlank( - applyPlaceholders(description, extractedTitle, extractedDesc, systemInfo), - category.getSpec().getDescription(), siteDesc), coverUrl, pageUrl, author, null, + var phTitle = firstNonBlank(extractedTitle, category.getSpec().getDisplayName()); + var phDesc = firstNonBlank(extractedDesc, category.getSpec().getDescription()); + return new SeoData(firstNonBlank(applyPlaceholders(title, phTitle, phDesc, systemInfo), + category.getSpec().getDisplayName(), siteName), + firstNonBlank(applyPlaceholders(description, phTitle, phDesc, systemInfo), + category.getSpec().getDescription(), siteDesc), coverUrl, pageUrl, author, null, null, null, null, siteName, siteLogo, displayName, "website"); }); } @@ -811,12 +817,13 @@ private Mono<SeoData> buildSeoDataForTag(Tag tag, String title, String descripti var author = firstNonBlank(config.getDefaultAuthor(), siteName); var displayName = tag.getSpec().getDisplayName(); - return new SeoData( - firstNonBlank(applyPlaceholders(title, extractedTitle, extractedDesc, systemInfo), - tag.getSpec().getDisplayName(), siteName), firstNonBlank( - applyPlaceholders(description, extractedTitle, extractedDesc, systemInfo), - tag.getSpec().getDescription(), siteDesc), coverUrl, pageUrl, author, null, null, - null, null, siteName, siteLogo, displayName, "website"); + var phTitle = firstNonBlank(extractedTitle, tag.getSpec().getDisplayName()); + var phDesc = firstNonBlank(extractedDesc, tag.getSpec().getDescription()); + return new SeoData(firstNonBlank(applyPlaceholders(title, phTitle, phDesc, systemInfo), + tag.getSpec().getDisplayName(), siteName), + firstNonBlank(applyPlaceholders(description, phTitle, phDesc, systemInfo), + tag.getSpec().getDescription(), siteDesc), coverUrl, pageUrl, author, null, + null, null, null, siteName, siteLogo, displayName, "website"); }); } @@ -857,11 +864,12 @@ private Mono<SeoData> buildSeoDataForAuthor(User user, String title, String desc systemInfo.getLogo())); var siteLogo = processPermalink(systemInfo.getLogo()); - return new SeoData( - firstNonBlank(applyPlaceholders(title, extractedTitle, extractedDesc, systemInfo), - user.getSpec().getDisplayName(), siteName), firstNonBlank( - applyPlaceholders(description, extractedTitle, extractedDesc, systemInfo), - user.getSpec().getBio(), siteDesc), coverUrl, pageUrl, displayName, null, null, + var phTitle = firstNonBlank(extractedTitle, user.getSpec().getDisplayName()); + var phDesc = firstNonBlank(extractedDesc, user.getSpec().getBio()); + return new SeoData(firstNonBlank(applyPlaceholders(title, phTitle, phDesc, systemInfo), + user.getSpec().getDisplayName(), siteName), + firstNonBlank(applyPlaceholders(description, phTitle, phDesc, systemInfo), + user.getSpec().getBio(), siteDesc), coverUrl, pageUrl, displayName, null, null, null, null, siteName, siteLogo, displayName, "profile"); }); } @@ -1263,18 +1271,19 @@ private boolean hasValue(String value) { * * <p>支持的占位符: * <ul> - * <li>{@code %TITLE%} → 页面预注入的 {@code <title>} 标签文本</li> - * <li>{@code %DESC%} → 页面预注入的 {@code <meta name="description">} content</li> + * <li>{@code %TITLE%} → 预注入 {@code <title>} 文本 → 实体固定值(如 post.spec.title)</li> + * <li>{@code %DESC%} → 预注入 {@code <meta name="description">} content → 实体固定值(如 post + * .status.excerpt)</li> * <li>{@code %SITENAME%} → 站点标题</li> * <li>{@code %SITEDESC%} → 站点 SEO 描述</li> * </ul> * * @param value 原始字符串,可能包含占位符;null 时原样返回 - * @param extractedTitle 从 head 模型提取的 <title> 文本,用于替换 {@code %TITLE%} - * @param extractedDesc 从 head 模型提取的 meta description,用于替换 {@code %DESC%} + * @param phTitle {@code %TITLE%} 的展开值,由调用方按"预注入 → 实体固定值"链取好后传入 + * @param phDesc {@code %DESC%} 的展开值,由调用方按"预注入 → 实体固定值"链取好后传入 * @param systemInfo 站点信息,用于替换 {@code %SITENAME%} 和 {@code %SITEDESC%} */ - private String applyPlaceholders(String value, String extractedTitle, String extractedDesc, + private String applyPlaceholders(String value, String phTitle, String phDesc, SystemInfo systemInfo) { if (value == null || value.isBlank()) { return value; @@ -1283,9 +1292,9 @@ private String applyPlaceholders(String value, String extractedTitle, String ext var siteDesc = Optional.ofNullable(systemInfo.getSeo()).map(SystemInfo.SeoProp::getDescription) .orElse(""); - return value.replace("%TITLE%", extractedTitle != null ? extractedTitle : "") - .replace("%DESC%", extractedDesc != null ? extractedDesc : "") - .replace("%SITENAME%", siteName).replace("%SITEDESC%", siteDesc); + return value.replace("%TITLE%", phTitle != null ? phTitle : "") + .replace("%DESC%", phDesc != null ? phDesc : "").replace("%SITENAME%", siteName) + .replace("%SITEDESC%", siteDesc); } /** From 6bee1fbf0ee2e5118236261c0250b405f6a6e746 Mon Sep 17 00:00:00 2001 From: HowieHz <howie_hzgo@outlook.com> Date: Thu, 26 Mar 2026 12:43:59 +0800 Subject: [PATCH 19/19] Document support for tag detail pages https://github.com/halo-dev/halo/issues/8483 fixed --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 29826ff..3c2c13b 100644 --- a/README.md +++ b/README.md @@ -87,11 +87,10 @@ | 分类集合页 | `categories` | 无 | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/categories) | | 分类详情页 | `category` | Category(封面、permalink) | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/category) | | 标签集合页 | `tags` | 无 | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/tags) | +| 标签详情页 | `tag` | Tag(permalink) | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/tag) | | 文章归档页 | `archives` | 无 | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/archives) | | 作者页 | `author` | User(头像、permalink) | [模板变量](https://docs.halo.run/developer-guide/theme/template-variables/author) | -> **暂不支持**:标签详情页(`tag`)——Halo 的标签路由未注入 `_templateId` 上下文变量,插件无法识别该页面类型。 - > **路由前缀**:分类集合、标签集合、归档页的 canonical URL 会读取 Halo CMS 的“主题路由设置”(设置 / > 主题路由设置)配置,与站点实际路由保持同步,不使用硬编码路径。 @@ -115,7 +114,7 @@ | 文章详情页 | `article` | `BlogPosting` | ✅ | ✅ | ✅ 文章标签 | 字段最完整的页面类型 | | 独立页面 | `article` | `BlogPosting` | ✅ | ✅ | ✅ 站点关键词 | 独立页面无标签,关键词回退到站点级 | | 分类详情页 | `website` | `WebPage` | ❌ | ❌ | ✅ 分类名 | 分类无发布时间和作者 | -| 标签详情页 | `website` | `WebPage` | ❌ | ❌ | ✅ 标签名 | (⚠️ 暂不支持)标签无发布时间和作者 | +| 标签详情页 | `website` | `WebPage` | ❌ | ❌ | ✅ 标签名 | 标签无发布时间和作者 | | 作者页 | `profile` | `ProfilePage` | ❌ | ✅ | ✅ 作者名 | 作者页无发布时间 | | 列表/聚合页 | `website` | `WebPage` | ❌ | ❌ | ✅ 站点关键词 | 首页、分类集合、标签集合、文章归档页、所有第三方插件页面 |