Merge branch 'preview-fixes'

This commit is contained in:
the1812 2023-09-24 20:36:26 +08:00
commit 3d78bf7c9e
154 changed files with 9460 additions and 6410 deletions

View File

@ -46,6 +46,7 @@ module.exports = {
},
],
'vue/multi-word-component-names': 'off',
'vue/max-attributes-per-line': 'off',
'vue/html-self-closing': 'off',
'vue/no-v-html': 'off',

View File

@ -13,7 +13,7 @@
"id": "feature",
"attributes": {
"label": "对应功能",
"description": "(指本脚本的某项具体功能, 不是指 b 站的) 可以在设置中尝试开启 / 关闭某项功能来排查. 最好能附上功能的 Commit 值 (在设置里功能选项的底部).\n<img src=\"https://user-images.githubusercontent.com/26504152/152666017-8a5acabc-2ad1-47ef-b0a2-cd5c54c867ab.jpg\" width=\"400\">\n请注意: 以下功能已不再维护 (不再修复 bug 和添加新功能), 但是你仍然可以为其提供 Pull Request.\n- 直播间自动抽奖\n- 默认播放器模式\n- 播放器置顶\n- 播放器定位\n",
"description": "(指本脚本的某项具体功能, 不是指 b 站的) 可以在设置中尝试开启 / 关闭某项功能来排查. 最好能附上功能的 Commit 值 (在设置里功能选项的底部).\n<img src=\"https://user-images.githubusercontent.com/26504152/152666017-8a5acabc-2ad1-47ef-b0a2-cd5c54c867ab.jpg\" width=\"400\">\n如果组件包含作者的信息 (by @xxx), 请在这里 at 他们\n请注意: 以下功能已不再维护 (不再修复 bug 和添加新功能), 但是你仍然可以为其提供 Pull Request.\n- 直播间自动抽奖\n- 默认播放器模式\n- 播放器置顶\n- 播放器定位\n",
"placeholder": "请输入"
},
"validations": {

View File

@ -13,6 +13,7 @@ body:
description: |
(指本脚本的某项具体功能, 不是指 b 站的) 可以在设置中尝试开启 / 关闭某项功能来排查. 最好能附上功能的 Commit 值 (在设置里功能选项的底部).
<img src="https://user-images.githubusercontent.com/26504152/152666017-8a5acabc-2ad1-47ef-b0a2-cd5c54c867ab.jpg" width="400">
如果组件包含作者的信息 (by @xxx), 请在这里 at 他们
请注意: 以下功能已不再维护 (不再修复 bug 和添加新功能), 但是你仍然可以为其提供 Pull Request.
- 直播间自动抽奖
- 默认播放器模式

View File

@ -1,5 +1,96 @@
# 更新日志
## v2.8.5 / v2.8.5-preview
`2023-09-24`
✨新增
- 新增组件 `自定义字体`. (PR #4406 by [Tinhone](https://github.com/Tinhone))
> 使用组件提供的字体设置覆盖原版的主站字体,并使主站字体可被自定义。字体设置写法请参考 [MDN](https://developer.mozilla.org/zh-CN/docs/Web/CSS/font-family) 、默认设置与设置说明
- `简化直播间` 适配了新版 PK 条. (PR #4439 by [Pencil](https://github.com/pencilqaq))
- `下载视频` 支持配置 DASH 格式下的文件扩展名. (PR #4449 by [小傅Fox](https://github.com/xfoxfu))
<details>
<summary>正式版用户将获得 v2.8.0-preview ~ v2.8.4-preview 的所有新功能, 点击展开查看</summary>
- `清爽首页` 改进:
- 支持自定义板块的布局和可见性. (操作方式和自定义顶栏那个差不多)
- 番剧时间表优化样式, 增加翻页按钮.
<div>
<img height="400" src="https://user-images.githubusercontent.com/26504152/242905984-895cb72c-b344-40c3-91a0-2a6b20d5f783.png">
<img height="400" src="https://user-images.githubusercontent.com/26504152/242905640-cbc948f1-734e-46f2-96a7-d57787b7cf47.png">
</div>
- 新增功能 `首页净化`. (PR #4153 by [RieN](https://github.com/rien7))
> 删除首页特定类型的卡片.
- `自定义顶栏` 增加 `创作中心` 入口, `排行` 新增 `全站音乐榜``短剧榜` 的入口. (#4101, PR #4154 by [星海](https://github.com/lovelyCARDINAL), PR #4155 by [星海](https://github.com/lovelyCARDINAL))
- `播放器置顶(新)` 增加选项 `顶部留白`. (#4152, PR #4165 by [Ziu](https://github.com/ZiuChen))
- `播放器置顶(新)` 的标题改为放置到播放器和点赞中间. (PR #4208 by [LockRim](https://github.com/LockRim))
- `显示视频投稿时间` 支持转义字符. (#4160, PR #4167 by [呼乎户](https://github.com/wisokey))
- `隐藏视频推荐` 支持稍后再看和收藏夹播放页, 并适配 bpx 播放器. (#4174)
- 新增功能 `隐藏记笔记``隐藏稿件投诉`. (#4124)
- 优化了 `自定义顶栏` 中订阅在筛选时的 API 调用. (#4120)
- 优化粗体的显示: 优先调用 Semibold 字重, 若字体不支持则使用 Bold 字重.
- `自定义顶栏` 增加选项:
- 支持自定义消息提醒样式, 可设置为 `数字`, `点状``隐藏`. (#4125)
- 支持自定义搜索栏宽度. (#4112)
- 搜索在纯数字输入时, 增加对 UID 和直播间的搜索. (#677)
- `网址参数清理` 更新对直播间的支持. (#1459)
- `自动点赞` 转为由 [CrazyboyQCD](https://github.com/CrazyboyQCD) 维护, 支持手动控制点赞和黑名单功能. (PR #4343, PR #4358 by [CrazyboyQCD](https://github.com/CrazyboyQCD))
- `简化直播间` 支持隐藏荣耀等级勋章. (PR #4348 by [CrunchyShark](https://github.com/CrunchyShark944))
- 在设置面板中的组件管理等面板中, 搜索框移动至已安装列表的上方, 更符合直觉. (#3806)
- 支持从 Tampermonkey 的菜单中唤起功能和设置面板. (#4170)
- 搜索栏中添加了导入 / 导出设置的操作. (#4170)
- `自定义顶栏` 新增选项 `链接对齐样式`, 可以自定义纯链接弹窗内链接文字的对齐样式. (PR #4365 by [Tinhone](https://github.com/Tinhone))
- `简化评论区` 支持独立控制简化选项, 并支持隐藏粉丝勋章和活动横幅. (仅支持新版评论区) (#2381)
- `清爽首页` 设置为隐藏的版块现在将彻底销毁, 减少资源占用.
- `禁用特殊弹幕样式` 支持禁用大会员弹幕. (#4227)
- `下载视频` 在批量下载番剧时, 支持下载多个不同板块的选集. (#2834)
- 新增功能 `全屏直播礼物简化`. (PR #4306 by [TimmyOVO](https://github.com/TimmyOVO))
> 移除全屏观看直播时的底部礼物栏
- 新增功能 `评论区 IP 属地显示`. (PR #4331, PR #4334 by [Light_Quanta](https://github.com/LightQuanta))
> 在评论区显示评论的IP属地信息
- 新增功能 `直播间网页全屏自适应`. (#4216)
> 在直播网页全屏时, 自动调整侧边栏的宽度, 使得视频区域的比例和视频源相匹配, 达到无黑边的效果. 如果在侧边栏的边缘拖动, 可以自定义侧边栏的固定宽度, 双击边缘可以还原到自动宽度.
>
> - `侧边栏最大宽度 (px)`: 限制侧边栏可被拉伸到的最大宽度. (最小宽度固定为 190px, 再小的话布局就要出问题了)
>
> > 注意, 由于有最大宽度和最小宽度的限制, 部分窗口尺寸下仍然无法做到无黑边.
- 新增功能 `关注时间显示`. (PR #4352 by [Light_Quanta](https://github.com/LightQuanta))
> 在个人空间的粉丝/关注列表显示关注的具体时间
- 新增功能 `自动移出稍后再看`.
> 在稍后再看页面播放结束时, 自动将当前视频移出稍后再看.
> 注意:
> - 一定要播放结束, 快结束时手动切走不算
> - b 站的稍后再看列表不会实时刷新
- 新增功能 `相簿发布时间显示`. (PR #4362 by [Light_Quanta](https://github.com/LightQuanta))
> 在个人空间的相簿界面显示相簿的发布时间
</details>
🐛修复
- 修复 `禁止滚轮调音量` 在网页全屏下失效. (#4381).
- 修复 Feeds API 无法解析新版图文动态的文字内容. (主要是动态过滤器会受影响) (#4409)
- 修复 `选集区域优化` 在部分视频合集页面上无法使用 <kbd>Alt</kbd> 来切换折叠. (#4429)
- 修复被转发图文动态的 `详情 >` 功能失效. (PR #4452 by [sunfkny](https://github.com/sunfkny))
## v2.8.4-preview
`2023-09-07`

View File

@ -21,6 +21,8 @@ pnpm install
- [配置 VS Code 插件](https://code.visualstudio.com/docs/editor/extension-marketplace):
- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint), 用于格式化 TypeScript 和 Vue 文件.
- [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode), 用于格式化 Scss 和其他文件.
- [Vue Language Features (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.volar), 为 *.vue 文件提供支持.
- [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin), 让 TS Server 识别 *.vue 文件.(建议启用性能更好的 [Takeover 模式](https://vuejs.org/guide/typescript/overview.html#volar-takeover-mode))
### 本体
需要说明的是, 脚本本体和功能是分开的两个项目. 本体的代码在 `src/` 下, 开发时产生 `dist/bilibili-evolved.dev.user.js` 文件. 功能的代码位于 `registry/` 下, 开发时在 `registry/dist/` 下产生文件.
@ -153,9 +155,7 @@ pnpm install
### 全局
全局变量, 无需 `import` 就可以直接使用. (Tampermonkey API 这里不再列出了, 可根据代码提示使用)
- `Vue`: Vue 库的主对象, 在创建 `.vue` 组件时, 其中的 `<script>` 可以直接使用 `Vue.extend()`
> 出于历史原因, 项目中用的还是 Vue 2, 由于其糟糕的 TypeScript 支持, 在 VS Code + Vetur 的环境下浏览 `.vue` 文件可能会报各种奇奇怪怪的类型错误, 无视就好. (类型是否正确以 `pnpm run type` 的结果为准)
- `Vue`: Vue 2 提供的主要对象. 不再推荐使用. 如果需要以选项式方式定义 Vue 组件, 请使用 `defineComponent` 而非 `Vue.extend``new Vue`.
- `lodash`: 包含所有 Lodash 库提供的方法
- `dq` / `dqa`: `document.querySelector``document.querySelectorAll` 的简写, `dqa` 会返回真实数组
> 在 `bwp-video` 出现后, 这两个查询函数还会自动将对 `video` 的查询扩展到 `bwp-video`
@ -241,6 +241,11 @@ pnpm install
- `ui/VLoading.vue`: 表示数据加载中, 界面可被插件更改
- `ui/AsyncButton.vue`: `click` 事件为异步函数时, 执行期间自动使 `Button` 禁用, 其他和 `Button` 相同.
## 代码类型检查
提交 Pull Request 前, 请确保代码通过类型检查. 类型检查以 VS Code 任务: `生产:类型检查 prod:type` 为准.
> 项目有[计划](https://github.com/the1812/Bilibili-Evolved/discussions/3939)从 Vue 2 迁移到 Vue 3, 因此虽然我们[启用](https://github.com/the1812/Bilibili-Evolved/pull/4337)了 Volar 对 *.vue 文件进行类型检查, 却未完全修复 Volar 报告的类型错误. 因此,开发时 VS Code 报错属正常现象. Pull Request 的类型检查标准仍以上述内容为准.
## 代码风格检查
项目中含有 ESLint, 不通过 ESLint 是无法进行 Pull Request 的.

View File

@ -28,6 +28,7 @@ https://afdian.net/@the1812?tab=sponsor
| 时间 | 用户名 | 单号后4位 | 金额 |
| ------------------- | --------------------- | --------- | ------- |
| 2023.09.22 04:41:59 | S*y | 7595 | ¥2.00 |
| 2023.08.29 12:38:40 | 匿名 | 3584 | ¥2.33 |
| 2023.08.27 05:41:58 | 匿名 | 3000 | ¥20.00 |
| 2023.08.27 05:41:35 | 匿名 | 2195 | ¥20.00 |

View File

@ -91,10 +91,18 @@
"type": "component",
"name": "badgeHelper",
"displayName": "直播勋章快速更换",
"description": "在直播区中, 可从功能面板中直接切换勋章和头衔. 默认显示 256 个 (同时也是上限), 可在选项中修改.",
"description": "在直播区中, 可从功能面板中直接切换勋章和头衔. 默认显示 256 个 (同时也是上限), 可在选项中修改.\r\n",
"fullRelativePath": "../../registry/dist/components/live/badge-helper.js",
"fullAbsolutePath": "registry/dist/components/live/badge-helper.js"
},
{
"type": "component",
"name": "liveChatPanelFit",
"displayName": "直播间网页全屏自适应",
"description": "在直播网页全屏时, 自动调整侧边栏的宽度, 使得视频区域的比例和视频源相匹配, 达到无黑边的效果.\r\n如果在侧边栏的边缘拖动, 可以自定义侧边栏的固定宽度, 双击边缘可以还原到自动宽度.\r\n\r\n- `侧边栏最大宽度 (px)`: 限制侧边栏可被拉伸到的最大宽度. (最小宽度固定为 190px, 再小的话布局就要出问题了)\r\n\r\n> 注意, 由于有最大宽度和最小宽度的限制, 部分窗口尺寸下仍然无法做到无黑边.\r\n",
"fullRelativePath": "../../registry/dist/components/live/chat-panel-fit.js",
"fullAbsolutePath": "registry/dist/components/live/chat-panel-fit.js"
},
{
"type": "component",
"name": "liveDanmakuSendbar",
@ -111,6 +119,14 @@
"fullRelativePath": "../../registry/dist/components/live/gift-box.js",
"fullAbsolutePath": "registry/dist/components/live/gift-box.js"
},
{
"type": "component",
"name": "hide-fullscreen-gift-bar",
"displayName": "全屏直播礼物简化",
"description": "by [@TimmyOVO](https://github.com/TimmyOVO)\n\n移除全屏观看直播时的底部礼物栏",
"fullRelativePath": "../../registry/dist/components/live/hide-gift-fullscreen.js",
"fullAbsolutePath": "registry/dist/components/live/hide-gift-fullscreen.js"
},
{
"type": "component",
"name": "liveHomeMute",
@ -167,11 +183,27 @@
"fullRelativePath": "../../registry/dist/components/style/auto-hide-sidebar.js",
"fullAbsolutePath": "registry/dist/components/style/auto-hide-sidebar.js"
},
{
"type": "component",
"name": "clear-home",
"displayName": "首页净化",
"description": "by [@RieN7](https://github.com/rien7)\n\n删除首页特定类型的卡片",
"fullRelativePath": "../../registry/dist/components/style/clear-home.js",
"fullAbsolutePath": "registry/dist/components/style/clear-home.js"
},
{
"type": "component",
"name": "customFontFamily",
"displayName": "自定义字体",
"description": "by [@Tinhone](https://github.com/Tinhone)\n\n使用组件提供的字体设置覆盖原版的主站字体并使主站字体可被自定义。字体设置写法请参考 [MDN](https://developer.mozilla.org/zh-CN/docs/Web/CSS/font-family) 、默认设置与设置说明",
"fullRelativePath": "../../registry/dist/components/style/custom-font-family.js",
"fullAbsolutePath": "registry/dist/components/style/custom-font-family.js"
},
{
"type": "component",
"name": "customNavbar",
"displayName": "自定义顶栏",
"description": "使用脚本提供的顶栏替换原版的主站顶栏 (非主站不替换), 启用后还可以在反广告插件 (例如 ABP) 中配置禁止原版顶栏的消息 iframe 以提升性能:\r\n- *://message.bilibili.com/pages/nav/index_new_pc_sync\r\n- *://message.bilibili.com/pages/nav/index_new_sync\r\n",
"description": "使用脚本提供的顶栏替换原版的主站顶栏 (非主站不替换).\r\n\r\n启用后还可以在反广告插件 (例如 ABP) 中配置禁止原版顶栏的消息 iframe 以提升性能:\r\n- *://message.bilibili.com/pages/nav/index_new_pc_sync\r\n- *://message.bilibili.com/pages/nav/index_new_sync\r\n\r\n选项说明:\r\n- `全局固定`: 将顶栏固定显示, 即使页面向下滚动也仍然可见. (除了一部分有 b 站定制顶栏的页面会忽略此选项)\r\n- `主题色填充`: 使用通用设置中的主题颜色填充顶栏的背景.\r\n- `透明填充`: 在首页和主站的部分页面中, 当存在顶部横幅图片时, 顶栏背景将变为透明 + 阴影的样式.\r\n- `背景模糊`: 启用背景模糊效果, 会使背景颜色变得半透明, 注意这个效果非常非常消耗图形性能, 慎用.\r\n- `投影`: 在顶栏下方增加一小段投影效果.\r\n- `使用季节 Logo`: 使用 b 站首页的特殊 Logo 图片替换顶栏的 Logo 图, 通常会对应当前季节, 特殊活动期间可能会有变化导致看不清. (比如搞了个全白的 Logo, 而顶栏正好也是白色的)\r\n- `触摸模式`: 启用后顶栏的一级入口点击时不进行跳转, 方便触屏使用.\r\n- `新标签页打开`: 控制顶栏内链接是否在新标签页打开.\r\n- `自动刷新数据`: 启用后, 部分有弹窗的顶栏入口在鼠标经过时会自动刷新数据.\r\n- `顶栏高度`: 自定义顶栏的高度 (b 站原版的为 64px)\r\n- `消息提醒样式`: 自定义顶栏入口的消息提醒样式.\r\n- `搜索栏宽度`: 自定义搜索栏占顶栏的比例.\r\n- `链接对齐样式`: 自定义纯链接弹窗内链接文字的对齐样式.\r\n",
"fullRelativePath": "../../registry/dist/components/style/custom-navbar.js",
"fullAbsolutePath": "registry/dist/components/style/custom-navbar.js"
},
@ -239,6 +271,14 @@
"fullRelativePath": "../../registry/dist/components/style/hide/user-card.js",
"fullAbsolutePath": "registry/dist/components/style/hide/user-card.js"
},
{
"type": "component",
"name": "hideVideoNotes",
"displayName": "隐藏记笔记",
"description": "隐藏视频页面中的 \"记笔记\" 按钮.\r\n",
"fullRelativePath": "../../registry/dist/components/style/hide/video/notes.js",
"fullAbsolutePath": "registry/dist/components/style/hide/video/notes.js"
},
{
"type": "component",
"name": "hideRecommendedLive",
@ -251,10 +291,18 @@
"type": "component",
"name": "hideRelatedVideos",
"displayName": "隐藏视频推荐",
"description": "隐藏番剧和视频页面右侧的推荐视频列表. 注意: 如果你想关闭 b 站的自动连播 (自动播放下一个推荐视频) 功能, 需要先取消隐藏视频推荐才能看到开关.",
"description": "隐藏番剧和视频页面右侧的推荐视频列表. 注意: 如果你想关闭 b 站的自动连播 (自动播放下一个推荐视频) 功能, 需要先取消隐藏视频推荐才能看到开关.\r\n",
"fullRelativePath": "../../registry/dist/components/style/hide/video/related-videos.js",
"fullAbsolutePath": "registry/dist/components/style/hide/video/related-videos.js"
},
{
"type": "component",
"name": "hideVideoReport",
"displayName": "隐藏稿件投诉",
"description": "隐藏视频页面中的 \"稿件投诉\" 按钮.\r\n",
"fullRelativePath": "../../registry/dist/components/style/hide/video/report.js",
"fullAbsolutePath": "registry/dist/components/style/hide/video/report.js"
},
{
"type": "component",
"name": "hideVideoShare",
@ -291,7 +339,7 @@
"type": "component",
"name": "playerOnTopNew",
"displayName": "播放器置顶(新)",
"description": "by [@RieN7](https://github.com/rien7)\n\n原来的播放器置顶插件现在已经不可用了这是一个新的版本可以在视频页面中将播放器放在页面最上方.",
"description": "by [@RieN7](https://github.com/rien7),[@ZiuChen](https://github.com/ZiuChen),[@LockRim](https://github.com/LockRim)\n\n原来的播放器置顶插件现在已经不可用了这是一个新的版本可以在视频页面中将播放器放在页面最上方.",
"fullRelativePath": "../../registry/dist/components/style/player-on-top-new.js",
"fullAbsolutePath": "registry/dist/components/style/player-on-top-new.js"
},
@ -331,7 +379,7 @@
"type": "component",
"name": "simplifyComments",
"displayName": "简化评论区",
"description": "- 删除热评头像下方的关注按钮\r\n- 删除用户的等级标识\r\n- 删除发送源信息(\\`来自安卓客户端\\` 这种)\r\n- 删除用户名右边的勋章\r\n- 删除评论区顶部的横幅\r\n- 发送时间移动到右上角\r\n- 位图图标全部换用矢量图标, 高分屏不会模糊\r\n- 投票仅显示链接, 隐藏下面的大框.\r\n\r\n> 注: 关注和等级可以通过鼠标停留在头像上, 在弹出的资料卡小窗中查看.\r\n",
"description": "去除或优化评论区内的元素, 可配置以下选项 (描述的是勾选时的效果):\r\n\r\n> 配置项仅对新版评论区有效\r\n\r\n- `用户等级`: 隐藏用户等级标识. (隐藏后, 可以通过鼠标停留在头像上, 在弹出的资料卡小窗中查看)\r\n- `装扮 & 时间`: 隐藏装扮图片, 并把发送时间移动到装扮的位置.\r\n- `头像框`: 隐藏头像框.\r\n- `回复换行`:\r\n - 楼中楼回复也另起一行显示, 和一级回复保持一致.\r\n - `热评`, `UP 主点赞` 等标记和点赞栏放在同一行.\r\n- `编辑框`: 将提示文本居上, 符合用户实际输入文字的位置, 并将发布按钮的字号略微调小.\r\n- `粉丝勋章`: 隐藏用户的粉丝勋章.\r\n- `活动横幅`: 隐藏评论区顶部的活动横幅\r\n",
"fullRelativePath": "../../registry/dist/components/style/simplify/comments.js",
"fullAbsolutePath": "registry/dist/components/style/simplify/comments.js"
},
@ -355,7 +403,7 @@
"type": "component",
"name": "disableSpecialDanmaku",
"displayName": "禁用特殊弹幕样式",
"description": "移除高赞弹幕或 UP 主弹幕的特殊样式, 弹幕内容不会移除.",
"description": "移除高赞弹幕或 UP 主弹幕的特殊样式, 弹幕内容不会移除.\r\n",
"fullRelativePath": "../../registry/dist/components/style/special-danmaku.js",
"fullAbsolutePath": "registry/dist/components/style/special-danmaku.js"
},
@ -407,11 +455,19 @@
"fullRelativePath": "../../registry/dist/components/touch/player-gestures.js",
"fullAbsolutePath": "registry/dist/components/touch/player-gestures.js"
},
{
"type": "component",
"name": "albumPubTimeShow",
"displayName": "相簿发布时间显示",
"description": "by [@Light_Quanta](https://github.com/LightQuanta)\n\n在相簿界面显示相簿的发布时间",
"fullRelativePath": "../../registry/dist/components/utils/album-time-show.js",
"fullAbsolutePath": "registry/dist/components/utils/album-time-show.js"
},
{
"type": "component",
"name": "autoLike",
"displayName": "自动点赞",
"description": "进入视频 / 查看动态时, 自动点赞.\r\n",
"description": "by [@CrazyboyQCD](https://github.com/CrazyboyQCD)\n\n进入视频 / 查看动态时, 自动点赞\\\r\n无法触发未加载动态的点赞当启用手动对动态点赞后可手动触发启用后不会触发动态自动点赞\\\r\n安装快捷键扩展后可以点击【l】键或【L】键触发\\\r\n还可以添加动态点赞的黑名单\\\r\n",
"fullRelativePath": "../../registry/dist/components/utils/auto-like.js",
"fullAbsolutePath": "registry/dist/components/utils/auto-like.js"
},
@ -503,6 +559,14 @@
"fullRelativePath": "../../registry/dist/components/utils/import-series.js",
"fullAbsolutePath": "registry/dist/components/utils/import-series.js"
},
{
"type": "component",
"name": "ipShow",
"displayName": "评论区IP属地显示",
"description": "by [@Light_Quanta](https://github.com/LightQuanta)\n\n在评论区显示评论的IP属地信息",
"fullRelativePath": "../../registry/dist/components/utils/ip-show.js",
"fullAbsolutePath": "registry/dist/components/utils/ip-show.js"
},
{
"type": "component",
"name": "keymap",
@ -519,11 +583,19 @@
"fullRelativePath": "../../registry/dist/components/utils/remove-promotions.js",
"fullAbsolutePath": "registry/dist/components/utils/remove-promotions.js"
},
{
"type": "component",
"name": "subscribeTimeShow",
"displayName": "关注时间显示",
"description": "by [@Light_Quanta](https://github.com/LightQuanta)\n\n在粉丝/关注列表显示关注的具体时间",
"fullRelativePath": "../../registry/dist/components/utils/subscribe-time-show.js",
"fullAbsolutePath": "registry/dist/components/utils/subscribe-time-show.js"
},
{
"type": "component",
"name": "urlParamsClean",
"displayName": "网址参数清理",
"description": "自动删除网址中的多余跟踪参数. 请注意这会导致浏览器历史记录出现重复的标题 (分别是转换前后的网址), 并可能导致后退要多退几次.\r\n",
"description": "自动删除网址中的多余跟踪参数。请注意这会导致浏览器历史记录出现重复的标题(分别是转换前后的网址),并可能导致后退要多退几次。\r\n",
"fullRelativePath": "../../registry/dist/components/utils/url-params-clean.js",
"fullAbsolutePath": "registry/dist/components/utils/url-params-clean.js"
},
@ -551,6 +623,14 @@
"fullRelativePath": "../../registry/dist/components/utils/watchlater-redirect.js",
"fullAbsolutePath": "registry/dist/components/utils/watchlater-redirect.js"
},
{
"type": "component",
"name": "autoRemoveWatchlater",
"displayName": "自动移出稍后再看",
"description": "在稍后再看页面播放结束时, 自动将当前视频移出稍后再看.\r\n注意:\r\n- 一定要播放结束, 快结束时手动切走不算\r\n- b 站的稍后再看列表不会实时刷新\r\n",
"fullRelativePath": "../../registry/dist/components/video/auto-remove-watchlater.js",
"fullAbsolutePath": "registry/dist/components/video/auto-remove-watchlater.js"
},
{
"type": "component",
"name": "avUrl",
@ -779,7 +859,7 @@
"type": "component",
"name": "showUploadTime",
"displayName": "显示视频投稿时间",
"description": "by [@wisokey](https://github.com/wisokey)\n\n为视频播放页面的推荐列表中的视频添加显示视频投稿时间.\r\n\r\n`时间格式` 替换up名的文本格式 (默认为'up · yyyy-MM-dd'):\r\n - y: 年\r\n - M: 月\r\n - d: 日\r\n - h: 时\r\n - m: 分\r\n - s: 秒\r\n - q: 季度\r\n - up: up名\r\n",
"description": "by [@wisokey](https://github.com/wisokey)\n\n为视频播放页面的推荐列表中的视频添加显示视频投稿时间.\r\n\r\n`时间格式` 替换up名的文本格式 (默认为'up · yyyy-MM-dd'):\r\n - y: 年\r\n - M: 月\r\n - d: 日\r\n - h: 时\r\n - m: 分\r\n - s: 秒\r\n - q: 季度\r\n - up: up名\r\n - \\r: 回车符\r\n - \\n换行符\r\n - \\t制表符",
"fullRelativePath": "../../registry/dist/components/video/player/show-upload-time.js",
"fullAbsolutePath": "registry/dist/components/video/player/show-upload-time.js"
},

View File

@ -116,6 +116,20 @@
在直播区中, 可从功能面板中直接切换勋章和头衔. 默认显示 256 个 (同时也是上限), 可在选项中修改.
### [直播间网页全屏自适应](../../registry/dist/components/live/chat-panel-fit.js)
`liveChatPanelFit`
**jsDelivr:** [`Stable`](https://cdn.jsdelivr.net/gh/the1812/Bilibili-Evolved@master/registry/dist/components/live/chat-panel-fit.js) / [`Preview`](https://cdn.jsdelivr.net/gh/the1812/Bilibili-Evolved@preview/registry/dist/components/live/chat-panel-fit.js)
**GitHub:** [`Stable`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/components/live/chat-panel-fit.js) / [`Preview`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/components/live/chat-panel-fit.js)
在直播网页全屏时, 自动调整侧边栏的宽度, 使得视频区域的比例和视频源相匹配, 达到无黑边的效果.
如果在侧边栏的边缘拖动, 可以自定义侧边栏的固定宽度, 双击边缘可以还原到自动宽度.
- `侧边栏最大宽度 (px)`: 限制侧边栏可被拉伸到的最大宽度. (最小宽度固定为 190px, 再小的话布局就要出问题了)
> 注意, 由于有最大宽度和最小宽度的限制, 部分窗口尺寸下仍然无法做到无黑边.
### [直播弹幕发送栏](../../registry/dist/components/live/danmaku-sendbar.js)
`liveDanmakuSendbar`
@ -134,6 +148,17 @@
在直播的网页全屏(不能是全屏)模式下往控制栏添加包裹按钮.
### [全屏直播礼物简化](../../registry/dist/components/live/hide-gift-fullscreen.js)
`hide-fullscreen-gift-bar`
**jsDelivr:** [`Stable`](https://cdn.jsdelivr.net/gh/the1812/Bilibili-Evolved@master/registry/dist/components/live/hide-gift-fullscreen.js) / [`Preview`](https://cdn.jsdelivr.net/gh/the1812/Bilibili-Evolved@preview/registry/dist/components/live/hide-gift-fullscreen.js)
**GitHub:** [`Stable`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/components/live/hide-gift-fullscreen.js) / [`Preview`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/components/live/hide-gift-fullscreen.js)
by [@TimmyOVO](https://github.com/TimmyOVO)
移除全屏观看直播时的底部礼物栏
### [直播首页静音](../../registry/dist/components/live/home-mute.js)
`liveHomeMute`
@ -197,6 +222,28 @@
自动隐藏脚本的侧栏 (功能和设置图标). 设置面板停靠在右侧时不建议使用, 因为网页的滚动条会占用右边缘的触发区域.
### [首页净化](../../registry/dist/components/style/clear-home.js)
`clear-home`
**jsDelivr:** [`Stable`](https://cdn.jsdelivr.net/gh/the1812/Bilibili-Evolved@master/registry/dist/components/style/clear-home.js) / [`Preview`](https://cdn.jsdelivr.net/gh/the1812/Bilibili-Evolved@preview/registry/dist/components/style/clear-home.js)
**GitHub:** [`Stable`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/components/style/clear-home.js) / [`Preview`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/components/style/clear-home.js)
by [@RieN7](https://github.com/rien7)
删除首页特定类型的卡片
### [自定义字体](../../registry/dist/components/style/custom-font-family.js)
`customFontFamily`
**jsDelivr:** [`Stable`](https://cdn.jsdelivr.net/gh/the1812/Bilibili-Evolved@master/registry/dist/components/style/custom-font-family.js) / [`Preview`](https://cdn.jsdelivr.net/gh/the1812/Bilibili-Evolved@preview/registry/dist/components/style/custom-font-family.js)
**GitHub:** [`Stable`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/components/style/custom-font-family.js) / [`Preview`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/components/style/custom-font-family.js)
by [@Tinhone](https://github.com/Tinhone)
使用组件提供的字体设置覆盖原版的主站字体,并使主站字体可被自定义。字体设置写法请参考 [MDN](https://developer.mozilla.org/zh-CN/docs/Web/CSS/font-family) 、默认设置与设置说明
### [自定义顶栏](../../registry/dist/components/style/custom-navbar.js)
`customNavbar`
@ -204,10 +251,27 @@
**GitHub:** [`Stable`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/components/style/custom-navbar.js) / [`Preview`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/components/style/custom-navbar.js)
使用脚本提供的顶栏替换原版的主站顶栏 (非主站不替换), 启用后还可以在反广告插件 (例如 ABP) 中配置禁止原版顶栏的消息 iframe 以提升性能:
使用脚本提供的顶栏替换原版的主站顶栏 (非主站不替换).
启用后还可以在反广告插件 (例如 ABP) 中配置禁止原版顶栏的消息 iframe 以提升性能:
- *://message.bilibili.com/pages/nav/index_new_pc_sync
- *://message.bilibili.com/pages/nav/index_new_sync
选项说明:
- `全局固定`: 将顶栏固定显示, 即使页面向下滚动也仍然可见. (除了一部分有 b 站定制顶栏的页面会忽略此选项)
- `主题色填充`: 使用通用设置中的主题颜色填充顶栏的背景.
- `透明填充`: 在首页和主站的部分页面中, 当存在顶部横幅图片时, 顶栏背景将变为透明 + 阴影的样式.
- `背景模糊`: 启用背景模糊效果, 会使背景颜色变得半透明, 注意这个效果非常非常消耗图形性能, 慎用.
- `投影`: 在顶栏下方增加一小段投影效果.
- `使用季节 Logo`: 使用 b 站首页的特殊 Logo 图片替换顶栏的 Logo 图, 通常会对应当前季节, 特殊活动期间可能会有变化导致看不清. (比如搞了个全白的 Logo, 而顶栏正好也是白色的)
- `触摸模式`: 启用后顶栏的一级入口点击时不进行跳转, 方便触屏使用.
- `新标签页打开`: 控制顶栏内链接是否在新标签页打开.
- `自动刷新数据`: 启用后, 部分有弹窗的顶栏入口在鼠标经过时会自动刷新数据.
- `顶栏高度`: 自定义顶栏的高度 (b 站原版的为 64px)
- `消息提醒样式`: 自定义顶栏入口的消息提醒样式.
- `搜索栏宽度`: 自定义搜索栏占顶栏的比例.
- `链接对齐样式`: 自定义纯链接弹窗内链接文字的对齐样式.
### [夜间模式跟随系统](../../registry/dist/components/style/dark-mode/follow-system.js)
`darkModeFollowSystem`
@ -284,6 +348,15 @@ by [@WakelessSloth56](https://github.com/WakelessSloth56)
隐藏鼠标指向用户名或用户头像时弹出的浮动用户信息卡片
### [隐藏记笔记](../../registry/dist/components/style/hide/video/notes.js)
`hideVideoNotes`
**jsDelivr:** [`Stable`](https://cdn.jsdelivr.net/gh/the1812/Bilibili-Evolved@master/registry/dist/components/style/hide/video/notes.js) / [`Preview`](https://cdn.jsdelivr.net/gh/the1812/Bilibili-Evolved@preview/registry/dist/components/style/hide/video/notes.js)
**GitHub:** [`Stable`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/components/style/hide/video/notes.js) / [`Preview`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/components/style/hide/video/notes.js)
隐藏视频页面中的 "记笔记" 按钮.
### [隐藏直播推荐](../../registry/dist/components/style/hide/video/recommended-live.js)
`hideRecommendedLive`
@ -302,6 +375,15 @@ by [@WakelessSloth56](https://github.com/WakelessSloth56)
隐藏番剧和视频页面右侧的推荐视频列表. 注意: 如果你想关闭 b 站的自动连播 (自动播放下一个推荐视频) 功能, 需要先取消隐藏视频推荐才能看到开关.
### [隐藏稿件投诉](../../registry/dist/components/style/hide/video/report.js)
`hideVideoReport`
**jsDelivr:** [`Stable`](https://cdn.jsdelivr.net/gh/the1812/Bilibili-Evolved@master/registry/dist/components/style/hide/video/report.js) / [`Preview`](https://cdn.jsdelivr.net/gh/the1812/Bilibili-Evolved@preview/registry/dist/components/style/hide/video/report.js)
**GitHub:** [`Stable`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/components/style/hide/video/report.js) / [`Preview`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/components/style/hide/video/report.js)
隐藏视频页面中的 "稿件投诉" 按钮.
### [隐藏视频分享](../../registry/dist/components/style/hide/video/share.js)
`hideVideoShare`
@ -355,7 +437,7 @@ by [@WakelessSloth56](https://github.com/WakelessSloth56)
**GitHub:** [`Stable`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/components/style/player-on-top-new.js) / [`Preview`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/components/style/player-on-top-new.js)
by [@RieN7](https://github.com/rien7)
by [@RieN7](https://github.com/rien7),[@ZiuChen](https://github.com/ZiuChen),[@LockRim](https://github.com/LockRim)
原来的播放器置顶插件,现在已经不可用了,这是一个新的版本,可以在视频页面中将播放器放在页面最上方.
@ -402,16 +484,19 @@ by [@RieN7](https://github.com/rien7)
**GitHub:** [`Stable`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/components/style/simplify/comments.js) / [`Preview`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/components/style/simplify/comments.js)
- 删除热评头像下方的关注按钮
- 删除用户的等级标识
- 删除发送源信息(\`来自安卓客户端\` 这种)
- 删除用户名右边的勋章
- 删除评论区顶部的横幅
- 发送时间移动到右上角
- 位图图标全部换用矢量图标, 高分屏不会模糊
- 投票仅显示链接, 隐藏下面的大框.
去除或优化评论区内的元素, 可配置以下选项 (描述的是勾选时的效果):
> 注: 关注和等级可以通过鼠标停留在头像上, 在弹出的资料卡小窗中查看.
> 配置项仅对新版评论区有效
- `用户等级`: 隐藏用户等级标识. (隐藏后, 可以通过鼠标停留在头像上, 在弹出的资料卡小窗中查看)
- `装扮 & 时间`: 隐藏装扮图片, 并把发送时间移动到装扮的位置.
- `头像框`: 隐藏头像框.
- `回复换行`:
- 楼中楼回复也另起一行显示, 和一级回复保持一致.
- `热评`, `UP 主点赞` 等标记和点赞栏放在同一行.
- `编辑框`: 将提示文本居上, 符合用户实际输入文字的位置, 并将发布按钮的字号略微调小.
- `粉丝勋章`: 隐藏用户的粉丝勋章.
- `活动横幅`: 隐藏评论区顶部的活动横幅
### [简化首页](../../registry/dist/components/style/simplify/home.js)
`simplifyHome`
@ -498,6 +583,17 @@ by [@RieN7](https://github.com/rien7)
- 进度调整可在左上角和右上角取消
- 进度调整时在不同位置滑动, 可以使用3档不同的灵敏度.
### [相簿发布时间显示](../../registry/dist/components/utils/album-time-show.js)
`albumPubTimeShow`
**jsDelivr:** [`Stable`](https://cdn.jsdelivr.net/gh/the1812/Bilibili-Evolved@master/registry/dist/components/utils/album-time-show.js) / [`Preview`](https://cdn.jsdelivr.net/gh/the1812/Bilibili-Evolved@preview/registry/dist/components/utils/album-time-show.js)
**GitHub:** [`Stable`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/components/utils/album-time-show.js) / [`Preview`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/components/utils/album-time-show.js)
by [@Light_Quanta](https://github.com/LightQuanta)
在相簿界面显示相簿的发布时间
### [自动点赞](../../registry/dist/components/utils/auto-like.js)
`autoLike`
@ -505,7 +601,12 @@ by [@RieN7](https://github.com/rien7)
**GitHub:** [`Stable`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/components/utils/auto-like.js) / [`Preview`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/components/utils/auto-like.js)
进入视频 / 查看动态时, 自动点赞.
by [@CrazyboyQCD](https://github.com/CrazyboyQCD)
进入视频 / 查看动态时, 自动点赞\
无法触发未加载动态的点赞,当启用手动对动态点赞后可手动触发,启用后不会触发动态自动点赞\
安装快捷键扩展后可以点击【l】键或【L】键触发\
还可以添加动态点赞的黑名单\
### [屏蔽黑名单up主](../../registry/dist/components/utils/black-list.js)
`blackList`
@ -636,6 +737,17 @@ by [@aiden](https://github.com/swhoro)
在合集页面制作一个批量导入按钮,可以新增所有合集内视频至同名收藏夹
### [评论区IP属地显示](../../registry/dist/components/utils/ip-show.js)
`ipShow`
**jsDelivr:** [`Stable`](https://cdn.jsdelivr.net/gh/the1812/Bilibili-Evolved@master/registry/dist/components/utils/ip-show.js) / [`Preview`](https://cdn.jsdelivr.net/gh/the1812/Bilibili-Evolved@preview/registry/dist/components/utils/ip-show.js)
**GitHub:** [`Stable`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/components/utils/ip-show.js) / [`Preview`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/components/utils/ip-show.js)
by [@Light_Quanta](https://github.com/LightQuanta)
在评论区显示评论的IP属地信息
### [快捷键扩展](../../registry/dist/components/utils/keymap.js)
`keymap`
@ -658,6 +770,17 @@ by [@aiden](https://github.com/swhoro)
- `保留活动横幅`: 保留视频页面的活动横幅.
- `保留小喇叭`: 保留视频页面的活动横幅下方评论区上方的黄色小喇叭通知以及动态的黄色小喇叭.
### [关注时间显示](../../registry/dist/components/utils/subscribe-time-show.js)
`subscribeTimeShow`
**jsDelivr:** [`Stable`](https://cdn.jsdelivr.net/gh/the1812/Bilibili-Evolved@master/registry/dist/components/utils/subscribe-time-show.js) / [`Preview`](https://cdn.jsdelivr.net/gh/the1812/Bilibili-Evolved@preview/registry/dist/components/utils/subscribe-time-show.js)
**GitHub:** [`Stable`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/components/utils/subscribe-time-show.js) / [`Preview`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/components/utils/subscribe-time-show.js)
by [@Light_Quanta](https://github.com/LightQuanta)
在粉丝/关注列表显示关注的具体时间
### [网址参数清理](../../registry/dist/components/utils/url-params-clean.js)
`urlParamsClean`
@ -665,7 +788,7 @@ by [@aiden](https://github.com/swhoro)
**GitHub:** [`Stable`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/components/utils/url-params-clean.js) / [`Preview`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/components/utils/url-params-clean.js)
自动删除网址中的多余跟踪参数. 请注意这会导致浏览器历史记录出现重复的标题 (分别是转换前后的网址), 并可能导致后退要多退几次.
自动删除网址中的多余跟踪参数。请注意这会导致浏览器历史记录出现重复的标题(分别是转换前后的网址),并可能导致后退要多退几次。
### [v1 设置迁移](../../registry/dist/components/utils/v1-migrate.js)
`v1Migrate`
@ -694,6 +817,18 @@ by [@aiden](https://github.com/swhoro)
将稍后再看的链接重定向为普通播放网址.
### [自动移出稍后再看](../../registry/dist/components/video/auto-remove-watchlater.js)
`autoRemoveWatchlater`
**jsDelivr:** [`Stable`](https://cdn.jsdelivr.net/gh/the1812/Bilibili-Evolved@master/registry/dist/components/video/auto-remove-watchlater.js) / [`Preview`](https://cdn.jsdelivr.net/gh/the1812/Bilibili-Evolved@preview/registry/dist/components/video/auto-remove-watchlater.js)
**GitHub:** [`Stable`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/components/video/auto-remove-watchlater.js) / [`Preview`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/components/video/auto-remove-watchlater.js)
在稍后再看页面播放结束时, 自动将当前视频移出稍后再看.
注意:
- 一定要播放结束, 快结束时手动切走不算
- b 站的稍后再看列表不会实时刷新
### [网址AV号转换](../../registry/dist/components/video/av-url.js)
`avUrl`
@ -1050,6 +1185,9 @@ by [@wisokey](https://github.com/wisokey)
- s: 秒
- q: 季度
- up: up名
- \r: 回车符
- \n换行符
- \t制表符
### [跳过充电鸣谢](../../registry/dist/components/video/player/skip-charge-list.js)
`skipChargeList`

View File

@ -45,7 +45,7 @@
"type": "component",
"name": "disableSpecialDanmaku",
"displayName": "禁用特殊弹幕样式",
"description": "移除高赞弹幕或 UP 主弹幕的特殊样式, 弹幕内容不会移除.",
"description": "移除高赞弹幕或 UP 主弹幕的特殊样式, 弹幕内容不会移除.\r\n",
"fullRelativePath": "../../registry/dist/components/style/special-danmaku.js",
"fullAbsolutePath": "registry/dist/components/style/special-danmaku.js"
},
@ -53,7 +53,7 @@
"type": "component",
"name": "simplifyComments",
"displayName": "简化评论区",
"description": "- 删除热评头像下方的关注按钮\r\n- 删除用户的等级标识\r\n- 删除发送源信息(\\`来自安卓客户端\\` 这种)\r\n- 删除用户名右边的勋章\r\n- 删除评论区顶部的横幅\r\n- 发送时间移动到右上角\r\n- 位图图标全部换用矢量图标, 高分屏不会模糊\r\n- 投票仅显示链接, 隐藏下面的大框.\r\n\r\n> 注: 关注和等级可以通过鼠标停留在头像上, 在弹出的资料卡小窗中查看.\r\n",
"description": "去除或优化评论区内的元素, 可配置以下选项 (描述的是勾选时的效果):\r\n\r\n> 配置项仅对新版评论区有效\r\n\r\n- `用户等级`: 隐藏用户等级标识. (隐藏后, 可以通过鼠标停留在头像上, 在弹出的资料卡小窗中查看)\r\n- `装扮 & 时间`: 隐藏装扮图片, 并把发送时间移动到装扮的位置.\r\n- `头像框`: 隐藏头像框.\r\n- `回复换行`:\r\n - 楼中楼回复也另起一行显示, 和一级回复保持一致.\r\n - `热评`, `UP 主点赞` 等标记和点赞栏放在同一行.\r\n- `编辑框`: 将提示文本居上, 符合用户实际输入文字的位置, 并将发布按钮的字号略微调小.\r\n- `粉丝勋章`: 隐藏用户的粉丝勋章.\r\n- `活动横幅`: 隐藏评论区顶部的活动横幅\r\n",
"fullRelativePath": "../../registry/dist/components/style/simplify/comments.js",
"fullAbsolutePath": "registry/dist/components/style/simplify/comments.js"
},
@ -85,7 +85,7 @@
"type": "component",
"name": "hideRelatedVideos",
"displayName": "隐藏视频推荐",
"description": "隐藏番剧和视频页面右侧的推荐视频列表. 注意: 如果你想关闭 b 站的自动连播 (自动播放下一个推荐视频) 功能, 需要先取消隐藏视频推荐才能看到开关.",
"description": "隐藏番剧和视频页面右侧的推荐视频列表. 注意: 如果你想关闭 b 站的自动连播 (自动播放下一个推荐视频) 功能, 需要先取消隐藏视频推荐才能看到开关.\r\n",
"fullRelativePath": "../../registry/dist/components/style/hide/video/related-videos.js",
"fullAbsolutePath": "registry/dist/components/style/hide/video/related-videos.js"
},
@ -192,7 +192,7 @@
"type": "component",
"name": "customNavbar",
"displayName": "自定义顶栏",
"description": "使用脚本提供的顶栏替换原版的主站顶栏 (非主站不替换), 启用后还可以在反广告插件 (例如 ABP) 中配置禁止原版顶栏的消息 iframe 以提升性能:\r\n- *://message.bilibili.com/pages/nav/index_new_pc_sync\r\n- *://message.bilibili.com/pages/nav/index_new_sync\r\n",
"description": "使用脚本提供的顶栏替换原版的主站顶栏 (非主站不替换).\r\n\r\n启用后还可以在反广告插件 (例如 ABP) 中配置禁止原版顶栏的消息 iframe 以提升性能:\r\n- *://message.bilibili.com/pages/nav/index_new_pc_sync\r\n- *://message.bilibili.com/pages/nav/index_new_sync\r\n\r\n选项说明:\r\n- `全局固定`: 将顶栏固定显示, 即使页面向下滚动也仍然可见. (除了一部分有 b 站定制顶栏的页面会忽略此选项)\r\n- `主题色填充`: 使用通用设置中的主题颜色填充顶栏的背景.\r\n- `透明填充`: 在首页和主站的部分页面中, 当存在顶部横幅图片时, 顶栏背景将变为透明 + 阴影的样式.\r\n- `背景模糊`: 启用背景模糊效果, 会使背景颜色变得半透明, 注意这个效果非常非常消耗图形性能, 慎用.\r\n- `投影`: 在顶栏下方增加一小段投影效果.\r\n- `使用季节 Logo`: 使用 b 站首页的特殊 Logo 图片替换顶栏的 Logo 图, 通常会对应当前季节, 特殊活动期间可能会有变化导致看不清. (比如搞了个全白的 Logo, 而顶栏正好也是白色的)\r\n- `触摸模式`: 启用后顶栏的一级入口点击时不进行跳转, 方便触屏使用.\r\n- `新标签页打开`: 控制顶栏内链接是否在新标签页打开.\r\n- `自动刷新数据`: 启用后, 部分有弹窗的顶栏入口在鼠标经过时会自动刷新数据.\r\n- `顶栏高度`: 自定义顶栏的高度 (b 站原版的为 64px)\r\n- `消息提醒样式`: 自定义顶栏入口的消息提醒样式.\r\n- `搜索栏宽度`: 自定义搜索栏占顶栏的比例.\r\n- `链接对齐样式`: 自定义纯链接弹窗内链接文字的对齐样式.\r\n",
"fullRelativePath": "../../registry/dist/components/style/custom-navbar.js",
"fullAbsolutePath": "registry/dist/components/style/custom-navbar.js"
},
@ -216,7 +216,7 @@
"type": "component",
"name": "urlParamsClean",
"displayName": "网址参数清理",
"description": "自动删除网址中的多余跟踪参数. 请注意这会导致浏览器历史记录出现重复的标题 (分别是转换前后的网址), 并可能导致后退要多退几次.\r\n",
"description": "自动删除网址中的多余跟踪参数。请注意这会导致浏览器历史记录出现重复的标题(分别是转换前后的网址),并可能导致后退要多退几次。\r\n",
"fullRelativePath": "../../registry/dist/components/utils/url-params-clean.js",
"fullAbsolutePath": "registry/dist/components/utils/url-params-clean.js"
},

View File

@ -11,6 +11,5 @@
## 预览版
- 更新版本号, 打上 tag
- 生成文档
- 编译预览版
- 带 tag 推送
- 测下 Edge + Tampermonkey

View File

@ -14,14 +14,14 @@
"type": "tsc -p tsconfig.type-check.json --noEmit"
},
"devDependencies": {
"@babel/core": "7.20.12",
"@babel/parser": "^7.20.15",
"@babel/core": "7.22.10",
"@babel/parser": "^7.22.10",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/preset-env": "^7.20.2",
"@babel/preset-typescript": "^7.18.6",
"@babel/types": "^7.20.7",
"@babel/preset-env": "^7.22.10",
"@babel/preset-typescript": "^7.22.5",
"@babel/types": "^7.22.10",
"@types/async-exit-hook": "^2.0.0",
"@types/babel__core": "^7.20.0",
"@types/babel__core": "^7.20.1",
"@types/color": "^3.0.1",
"@types/glob": "^7.2.0",
"@types/lodash": "^4.14.172",
@ -36,7 +36,7 @@
"@typescript-eslint/parser": "^5.50.0",
"async-exit-hook": "^2.0.1",
"autoprefixer": "^10.0.1",
"babel-loader": "^8.1.0",
"babel-loader": "^9.1.3",
"browserslist": "^4.21.4",
"css-loader": "^5.0.0",
"eslint": "^7.32.0",
@ -44,7 +44,7 @@
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-import": "^2.20.1",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-vue": "7.1.0",
"eslint-plugin-vue": "^9.15.1",
"fast-sass-loader": "^2.0.0",
"glob": "^10.2.6",
"postcss": "^8.1.0",
@ -60,13 +60,13 @@
"to-string-loader": "^1.2.0",
"ts-node": "^10.7.0",
"typescript": "^4.9.5",
"vue-loader": "^15.8.3",
"vue-template-compiler": "^2.6.11",
"vue-loader": "^15.10.1",
"webpack": "^5.31.2",
"webpack-bundle-analyzer": "^4.5.0",
"webpack-cli": "^4.6.0",
"ws": "^8.5.0",
"yaml": "^2.3.1"
"yaml": "^2.3.1",
"zx": "^7.2.3"
},
"dependencies": {
"@popperjs/core": "^2.6.0",
@ -78,12 +78,12 @@
"protobufjs": "^6.11.2",
"streamsaver": "^2.0.6",
"tippy.js": "^6.3.1",
"vue": "^2.6.11",
"vue-fragment": "^1.5.2"
"vue": "^2.7.14"
},
"pnpm": {
"overrides": {
"caniuse-lite": "^1.0.30001481"
"caniuse-lite": "^1.0.30001481",
"consolidate": "1.0.1"
}
},
"keywords": [

11400
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
!function(e,a){"object"==typeof exports&&"object"==typeof module?module.exports=a():"function"==typeof define&&define.amd?define([],a):"object"==typeof exports?exports["style/clear-home"]=a():e["style/clear-home"]=a()}(globalThis,(()=>(()=>{"use strict";var e={d:(a,l)=>{for(var t in l)e.o(l,t)&&!e.o(a,t)&&Object.defineProperty(a,t,{enumerable:!0,get:l[t]})},o:(e,a)=>Object.prototype.hasOwnProperty.call(e,a)},a={};e.d(a,{component:()=>c});const l=coreApis.componentApis.define,t=coreApis.settings,s=[];let d=!1,o=!0;const i=(0,l.defineOptionsMetadata)({广告:{displayName:"广告",defaultValue:!0},番剧:{displayName:"番剧",defaultValue:!0},电影:{displayName:"电影",defaultValue:!0},国创:{displayName:"国创",defaultValue:!0},电视剧:{displayName:"电视剧",defaultValue:!0},综艺:{displayName:"综艺",defaultValue:!0},纪录片:{displayName:"纪录片",defaultValue:!0},动画:{displayName:"动画",defaultValue:!0},游戏:{displayName:"游戏",defaultValue:!0},鬼畜:{displayName:"鬼畜",defaultValue:!0},音乐:{displayName:"音乐",defaultValue:!0},舞蹈:{displayName:"舞蹈",defaultValue:!0},影视:{displayName:"影视",defaultValue:!0},娱乐:{displayName:"娱乐",defaultValue:!0},知识:{displayName:"知识",defaultValue:!0},科技:{displayName:"科技",defaultValue:!0},资讯:{displayName:"资讯",defaultValue:!0},美食:{displayName:"美食",defaultValue:!0},生活:{displayName:"生活",defaultValue:!0},汽车:{displayName:"汽车",defaultValue:!0},时尚:{displayName:"时尚",defaultValue:!0},运动:{displayName:"运动",defaultValue:!0},动物圈:{displayName:"动物圈",defaultValue:!0},VLOG:{displayName:"VLOG",defaultValue:!0},搞笑:{displayName:"搞笑",defaultValue:!0},单机游戏:{displayName:"单机游戏",defaultValue:!0},虚拟UP主:{displayName:"虚拟UP主",defaultValue:!0},公益:{displayName:"公益",defaultValue:!0},公开课:{displayName:"公开课",defaultValue:!0},专栏:{displayName:"专栏",defaultValue:!0},直播:{displayName:"直播",defaultValue:!0},赛事:{displayName:"赛事",defaultValue:!0},活动:{displayName:"活动",defaultValue:!0},课堂:{displayName:"课堂",defaultValue:!0},社区中心:{displayName:"社区中心",defaultValue:!0},新歌热榜:{displayName:"新歌热榜",defaultValue:!0},漫画:{displayName:"漫画",defaultValue:!0}});function u(e){console.log(e);for(const a of e)if(a.classList)if(a.classList.contains("floor-single-card")){const e=a.querySelector(".badge").textContent;e&&s.includes(e)&&(a.remove(),console.log("remove",e))}else a.classList.contains("bili-live-card")?d&&(a.remove(),console.log("remove live")):(a.classList.contains("bili-video-card")||a.classList.contains("feed-card"))&&o&&(a.querySelector(".bili-video-card__info--ad")||a.querySelector(".bili-video-card__info--creative-ad"))&&(a.remove(),console.log("remove ads"))}function n(e){const a=[];for(const l of e){const e=l.target;if(!(e.classList&&e.classList.contains("carousel-transform")||"SPAN"===e.tagName)&&l.addedNodes.length>0)for(const e of l.addedNodes){const l=e;"#text"===l.nodeName||l.classList&&l.classList.contains("bili-watch-later")||a.push(l)}}a.length>0&&u(a)}const c=(0,l.defineComponentMetadata)({name:"clear-home",author:{name:"RieN7",link:"https://github.com/rien7"},tags:[componentsTags.style],displayName:"首页净化",description:"删除首页特定类型的卡片",entry:async e=>{let{metadata:a,settings:l}=e;const i=document.querySelector("main > .feed2 > .recommended-container_floor-aside > .container");if(!i)return;new MutationObserver(n).observe(i,{childList:!0}),Object.keys(l.options).forEach((e=>{(0,t.addComponentListener)(`${a.name}.${e}`,(a=>{if(a){if("广告"===e)return void(o=!0);s.push(e),"直播"===e&&(d=!0)}}),!0)}));u(i.children)},options:i,commitHash:"1167f721dc805a6bfd3d4bcba753607eff4bacd5",coreVersion:"2.7.0"});return a=a.component})()));

View File

@ -69,7 +69,10 @@ const entry = async () => {
return
}
if (postContent.classList.contains('repost') || card.type === feedsCardTypes.repost) {
const contents = dq(postContent, '.content, .bili-dyn-content__orig__desc') as HTMLElement
const contents = dq(
postContent,
'.content, .bili-dyn-content__orig__desc, .dyn-card-opus__summary',
) as HTMLElement
if (!contents) {
return
}

View File

@ -293,7 +293,7 @@ body.disable-feeds-filter {
}
}
h2 {
font-weight: bold;
@include semi-bold();
font-size: 13px;
margin: 0;
margin-bottom: 8px;

View File

@ -1,5 +1,5 @@
<template>
<div class="multiple-widgets">
<div ref="element" class="multiple-widgets">
<VPopup
ref="medalPopup"
v-model="medalOpen"
@ -50,85 +50,76 @@
</div>
</template>
<script lang="ts">
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { addComponentListener, getComponentSettings } from '@/core/settings'
import { descendingSort } from '@/core/utils/sort'
import { DefaultWidget, VPopup } from '@/ui'
import { Medal, Title, Badge, getMedalList, getTitleList } from './badge'
import { BadgeHelperOptions } from './options'
const { options } = getComponentSettings('badgeHelper')
export default Vue.extend({
components: {
DefaultWidget,
VPopup,
},
data() {
return {
medalList: [],
titleList: [],
medalOpen: false,
titleOpen: false,
grayEffect: true,
const { options } = getComponentSettings<BadgeHelperOptions>('badgeHelper')
const element = ref<HTMLElement>()
const medalList = ref<Medal[]>([])
const titleList = ref<Title[]>([])
const medalOpen = ref(false)
const titleOpen = ref(false)
const grayEffect = ref(true)
const updateColumnsCount = () => {
const maxColumnCount = 15
const columnLength = 18
const medalColumns = Math.min(Math.ceil(medalList.value.length / columnLength), maxColumnCount)
element.value.style.setProperty('--medal-columns', medalColumns.toString())
const titleColumns = Math.min(Math.ceil(titleList.value.length / columnLength), maxColumnCount)
element.value.style.setProperty('--title-columns', titleColumns.toString())
}
const loadMedalList = async () => {
medalList.value = (await getMedalList())
.sort(descendingSort(it => it.level))
.slice(0, options.maxBadgeCount)
}
const loadTitleList = async () => {
titleList.value = (await getTitleList()).slice(0, options.maxBadgeCount)
}
const toggleBadge = async (badge: Badge, list: Badge[]) => {
console.log(badge)
if (badge.isActive) {
badge.isActive = false
await badge.deactivate()
} else {
const activeBadge = list.find(b => b.isActive)
if (activeBadge) {
activeBadge.isActive = false
}
},
async mounted() {
addComponentListener(
'badgeHelper.grayEffect',
(enable: boolean) => {
this.grayEffect = enable
},
true,
)
const init = async () => {
const medal = this.loadMedalList()
await Title.getImageMap()
const title = this.loadTitleList()
return Promise.all([medal, title])
badge.isActive = true
await badge.activate()
if (badge instanceof Medal) {
options.defaultMedalID = badge.id
}
await init()
this.updateColumnsCount()
},
methods: {
updateColumnsCount() {
const maxColumnCount = 15
const columnLength = 18
const element = this.$el as HTMLElement
const medalColumns = Math.min(Math.ceil(this.medalList.length / columnLength), maxColumnCount)
element.style.setProperty('--medal-columns', medalColumns.toString())
const titleColumns = Math.min(Math.ceil(this.titleList.length / columnLength), maxColumnCount)
element.style.setProperty('--title-columns', titleColumns.toString())
}
if (badge instanceof Medal) {
await loadMedalList()
} else if (badge instanceof Title) {
await loadTitleList()
}
}
onMounted(async () => {
addComponentListener(
'badgeHelper.grayEffect',
(enable: boolean) => {
grayEffect.value = enable
},
async loadMedalList() {
this.medalList = (await getMedalList())
.sort(descendingSort(it => it.level))
.slice(0, options.maxBadgeCount)
},
async loadTitleList() {
this.titleList = (await getTitleList()).slice(0, options.maxBadgeCount)
},
async toggleBadge(badge: Badge, list: Badge[]) {
console.log(badge)
if (badge.isActive) {
badge.isActive = false
await badge.deactivate()
} else {
const activeBadge = list.find(b => b.isActive)
if (activeBadge) {
activeBadge.isActive = false
}
badge.isActive = true
await badge.activate()
if (badge instanceof Medal) {
options.defaultMedalID = badge.id
}
}
if (badge instanceof Medal) {
await this.loadMedalList()
} else if (badge instanceof Title) {
await this.loadTitleList()
}
},
},
true,
)
const init = async () => {
const medal = loadMedalList()
await Title.getImageMap()
const title = loadTitleList()
return Promise.all([medal, title])
}
await init()
updateColumnsCount()
})
</script>

View File

@ -4,7 +4,7 @@ import {
getTextWithCredentials,
postTextWithCredentials,
} from '@/core/ajax'
import { formData, getCsrf } from '@/core/utils'
import { getCsrf } from '@/core/utils'
import { logError } from '@/core/utils/log'
const validateJson = (json: any, errorMessage: string) => {
@ -18,22 +18,6 @@ const validateJson = (json: any, errorMessage: string) => {
export abstract class Badge {
constructor(public isActive: boolean = false, public id: number = 0) {}
/** @deprecated */
static parseJson<T>(
text: string,
actions: {
successAction: (json: any) => T
errorMessage: string
errorAction: (json: any) => T
},
) {
const json = JSON.parse(text)
if (json.code !== 0) {
logError(`${actions.errorMessage} 错误码:${json.code} ${json.message || ''}`)
return actions.errorAction(json)
}
return actions.successAction(json)
}
abstract activate(): Promise<boolean>
abstract deactivate(): Promise<boolean>
}
@ -59,8 +43,8 @@ export class Medal extends Badge {
async activate() {
const text = await postTextWithCredentials(
'https://api.live.bilibili.com/xlive/web-room/v1/fansMedal/wear',
formData({
medal_id: this.id,
new URLSearchParams({
medal_id: this.id.toString(),
csrf_token: getCsrf(),
csrf: getCsrf(),
}),
@ -72,7 +56,7 @@ export class Medal extends Badge {
async deactivate() {
const text = await postTextWithCredentials(
'https://api.live.bilibili.com/xlive/web-room/v1/fansMedal/take_off',
formData({
new URLSearchParams({
csrf_token: getCsrf(),
csrf: getCsrf(),
}),
@ -104,9 +88,9 @@ export class Title extends Badge {
source: string
imageUrl: string
constructor(json: any) {
const { id, cid, wear, css, name, source } = json
super(wear, css)
this.tid = id
const { identification, wear, tid, cid, name, source } = json
super(wear, identification)
this.tid = tid
this.cid = cid
this.name = name
this.source = source
@ -116,66 +100,56 @@ export class Title extends Badge {
}
static async getImageMap() {
if (Title.imageMap === undefined) {
return Badge.parseJson(
const json = JSON.parse(
await getTextWithCredentials('https://api.live.bilibili.com/rc/v1/Title/webTitles'),
{
successAction(json) {
Title.imageMap = {}
json.data.forEach((it: any) => {
Title.imageMap[it.identification] = it.web_pic_url
})
return Title.imageMap
},
errorAction: () => ({}),
errorMessage: '获取头衔图片失败.',
},
)
if (validateJson(json, '获取头衔图片失败.')) {
Title.imageMap = {}
json.data.forEach((it: any) => {
Title.imageMap[it.identification] = it.web_pic_url
})
return Title.imageMap
}
return {}
}
return Title.imageMap
}
async activate() {
return Badge.parseJson(
const json = JSON.parse(
await postTextWithCredentials(
'https://api.live.bilibili.com/i/ajaxWearTitle',
`id=${this.tid}&cid=${this.cid}&csrf=${getCsrf()}&csrf_token=${getCsrf()}`,
),
{
successAction: () => {
this.isActive = true
return true
},
errorAction: () => false,
errorMessage: '佩戴头衔失败.',
},
)
if (validateJson(json, '佩戴头衔失败.')) {
this.isActive = true
return true
}
return false
}
async deactivate() {
return Badge.parseJson(
const json = JSON.parse(
await postTextWithCredentials(
'https://api.live.bilibili.com/i/ajaxCancelWearTitle',
`csrf=${getCsrf()}&csrf_token=${getCsrf()}`,
),
{
successAction: () => {
this.isActive = false
return true
},
errorAction: () => false,
errorMessage: '卸下头衔失败.',
},
)
if (validateJson(json, '卸下头衔失败.')) {
this.isActive = false
return true
}
return false
}
}
export const getTitleList = async () =>
Badge.parseJson(
export const getTitleList = async (): Promise<Title[]> => {
const json = JSON.parse(
await getTextWithCredentials(
'https://api.live.bilibili.com/i/api/ajaxTitleInfo?page=1&pageSize=256&had=1',
'https://api.live.bilibili.com/xlive/web-ucenter/v1/user_title/TitleList?normal=0&special=0&had=1&page=1&page_size=256',
),
{
successAction: json =>
lodash.get(json, 'data.list', []).map((it: any) => new Title(it)) as Title[],
errorAction: () => [] as Title[],
errorMessage: '无法获取头衔列表.',
},
)
if (validateJson(json, '无法获取头衔列表.')) {
return lodash.get(json, 'data.list', []).map((it: any) => new Title(it))
}
return []
}

View File

@ -0,0 +1 @@
在直播区中, 可从功能面板中直接切换勋章和头衔. 默认显示 256 个 (同时也是上限), 可在选项中修改.

View File

@ -1,14 +1,11 @@
import { defineComponentMetadata } from '@/components/define'
import { getNumberValidator, getUID, none } from '@/core/utils'
import { getUID, none } from '@/core/utils'
import { autoMatchMedal } from './auto-match'
import { badgeHelperOptions } from './options'
export const component = defineComponentMetadata({
name: 'badgeHelper',
displayName: '直播勋章快速更换',
description: {
'zh-CN':
'在直播区中, 可从功能面板中直接切换勋章和头衔. 默认显示 256 个 (同时也是上限), 可在选项中修改.',
},
entry: () => autoMatchMedal(),
reload: none,
unload: none,
@ -17,25 +14,6 @@ export const component = defineComponentMetadata({
component: () => import('./BadgeHelper.vue').then(m => m.default),
condition: () => Boolean(getUID()),
},
options: {
autoMatchMedal: {
defaultValue: true,
displayName: '自动佩戴当前直播间勋章',
},
maxBadgeCount: {
defaultValue: 256,
displayName: '最大显示数量',
validator: getNumberValidator(1, 256),
},
defaultMedalID: {
displayName: '默认勋章ID',
hidden: true,
defaultValue: 0,
},
grayEffect: {
displayName: '显示勋章的未点亮状态',
defaultValue: true,
},
},
options: badgeHelperOptions,
urlInclude: ['//live.bilibili.com'],
})

View File

@ -0,0 +1,24 @@
import { OptionsOfMetadata, defineOptionsMetadata } from '@/components/define'
import { getNumberValidator } from '@/core/utils'
export const badgeHelperOptions = defineOptionsMetadata({
autoMatchMedal: {
defaultValue: true,
displayName: '自动佩戴当前直播间勋章',
},
maxBadgeCount: {
defaultValue: 256,
displayName: '最大显示数量',
validator: getNumberValidator(1, 256),
},
defaultMedalID: {
displayName: '默认勋章ID',
hidden: true,
defaultValue: 0,
},
grayEffect: {
displayName: '显示勋章的未点亮状态',
defaultValue: true,
},
})
export type BadgeHelperOptions = OptionsOfMetadata<typeof badgeHelperOptions>

View File

@ -0,0 +1,157 @@
<template>
<div
ref="draggerElement"
class="chat-panel-fit-dragger"
:class="{ dragging: isDragging }"
:style="{ transform: `translateX(${-movement}px)` }"
@pointerdown="startDragging"
@pointermove="handlePointerMove"
@dblclick="resetCustomWidth"
>
<div class="chat-panel-fit-dragger-bar"></div>
<div class="chat-panel-fit-dragger-preview-area" :style="{ width: `${previewWidth}px` }">
<div class="chat-panel-fit-dragger-preview-area-background">
{{ previewWidth.toFixed(1) }}px
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, reactive, ref, onBeforeMount } from 'vue'
import { getComponentSettings } from '@/core/settings'
import { ChatPanelFitOptions, ChatPanelFitOptionsMinWidth } from './options'
const options = reactive(getComponentSettings<ChatPanelFitOptions>('liveChatPanelFit').options)
onBeforeMount(() => {
if (options.customWidth !== 0) {
document.documentElement.style.setProperty(
'--live-chat-panel-width',
`${options.customWidth}px`,
)
}
})
const getAutoWidth = () =>
parseFloat(document.documentElement.style.getPropertyValue('--live-chat-panel-width'))
const draggerElement = ref<HTMLElement>()
const startPoint = ref(0)
const movement = ref(0)
const previewWidth = computed(() => {
if (options.customWidth) {
return options.customWidth + movement.value
}
return getAutoWidth()
})
const isDragging = ref(false)
const resetCustomWidth = () => {
options.customWidth = 0
window.dispatchEvent(new CustomEvent('customWidthReset'))
}
const handlePointerMove = (e: PointerEvent) => {
if (!isDragging.value) {
return
}
const currentMovement = startPoint.value - e.screenX
movement.value =
lodash.clamp(
options.customWidth + currentMovement,
ChatPanelFitOptionsMinWidth,
options.maxWidth,
) - options.customWidth
}
const startDragging = (e: PointerEvent) => {
isDragging.value = true
startPoint.value = e.screenX
if (options.customWidth === 0) {
options.customWidth = getAutoWidth()
}
draggerElement.value.setPointerCapture(e.pointerId)
document.documentElement.style.cursor = 'ew-resize'
document.documentElement.classList.add('custom-width-dragging')
document.documentElement.addEventListener(
'pointerup',
() => {
document.documentElement.style.cursor = ''
document.documentElement.classList.remove('custom-width-dragging')
isDragging.value = false
startPoint.value = 0
if (!movement.value) {
return
}
const newWidth = previewWidth.value
options.customWidth = newWidth
movement.value = 0
document.documentElement.style.setProperty('--live-chat-panel-width', `${newWidth}px`)
},
{ once: true },
)
}
</script>
<style lang="scss">
@import 'common';
// z-index,
html.custom-width-dragging .player-full-win:not(.hide-aside-area) .player-ctnr {
z-index: 1001 !important;
.head-info-section {
display: none !important;
}
}
.aside-area-toggle-btn {
height: 90px;
@include absolute-v-center();
}
.chat-panel-fit-dragger {
$dragger-width: 8px;
pointer-events: none;
touch-action: none;
height: 100%;
width: 0;
position: absolute;
right: 0;
top: 0;
@include v-stretch();
.player-full-win:not(.hide-aside-area) & {
pointer-events: initial;
}
&-bar {
opacity: 0;
cursor: ew-resize;
transition: opacity 0.2s ease-out;
flex-grow: 1;
width: $dragger-width;
height: 100%;
background-color: var(--theme-color);
transform: translateX(#{-$dragger-width});
@include v-center();
html.custom-width-dragging &,
&:hover {
opacity: 1;
}
}
&-preview-area {
opacity: 0;
@include v-stretch();
pointer-events: none;
position: absolute;
top: 0;
height: 100%;
font-size: 16px;
background-color: #fff;
color: #000;
body.dark & {
background-color: #222;
color: #eee;
}
&-background {
flex-grow: 1;
background-color: var(--theme-color-10);
@include h-center();
justify-content: center;
}
}
&.dragging &-preview-area {
opacity: 1;
}
}
</style>

View File

@ -0,0 +1,10 @@
.player-full-win:not(.hide-aside-area) {
.live-room-app {
.aside-area {
width: var(--live-chat-panel-width, 302px) !important;
}
.player-section {
width: calc(100% - var(--live-chat-panel-width, 302px)) !important;
}
}
}

View File

@ -0,0 +1,6 @@
在直播网页全屏时, 自动调整侧边栏的宽度, 使得视频区域的比例和视频源相匹配, 达到无黑边的效果.
如果在侧边栏的边缘拖动, 可以自定义侧边栏的固定宽度, 双击边缘可以还原到自动宽度.
- `侧边栏最大宽度 (px)`: 限制侧边栏可被拉伸到的最大宽度. (最小宽度固定为 190px, 再小的话布局就要出问题了)
> 注意, 由于有最大宽度和最小宽度的限制, 部分窗口尺寸下仍然无法做到无黑边.

View File

@ -0,0 +1,94 @@
import { defineComponentMetadata } from '@/components/define'
import {
addComponentListener,
getComponentSettings,
removeComponentListener,
} from '@/core/settings'
import { select, sq } from '@/core/spin-query'
import { mountVueComponent } from '@/core/utils'
import { useScopedConsole } from '@/core/utils/log'
import { liveUrls } from '@/core/utils/urls'
import { ChatPanelFitOptions, ChatPanelFitOptionsMinWidth, chatPanelFitOptions } from './options'
const name = 'liveChatPanelFit'
const console = useScopedConsole(name)
const calcPanelWidth = () => {
const { maxWidth, customWidth } = getComponentSettings<ChatPanelFitOptions>(name).options
if (customWidth !== 0) {
return
}
const video = dq('.live-player-ctnr video') as HTMLVideoElement
if (!video) {
return
}
const widthRatio = video.videoWidth
const heightRatio = video.videoHeight
if (widthRatio === 0 || heightRatio === 0) {
return
}
const { innerWidth, innerHeight } = window
const liveChatPanelWidth = innerWidth - (widthRatio * innerHeight) / heightRatio
console.log({ liveChatPanelWidth })
document.documentElement.style.setProperty(
'--live-chat-panel-width',
`${lodash.clamp(liveChatPanelWidth, ChatPanelFitOptionsMinWidth, maxWidth)}px`,
)
}
const debounceCalcPanelWidth = lodash.debounce(calcPanelWidth, 200)
let draggerInstance: Vue
const load = async () => {
addComponentListener(`${name}.targetRatio`, calcPanelWidth)
addComponentListener(`${name}.maxWidth`, calcPanelWidth)
window.addEventListener('customWidthReset', calcPanelWidth)
window.addEventListener('resize', debounceCalcPanelWidth)
const video = await sq(
() => dq('.live-player-ctnr video') as HTMLVideoElement,
v => v !== null && v.readyState !== HTMLMediaElement.HAVE_NOTHING,
)
if (!video) {
console.log('未找到 video 元素')
return
}
calcPanelWidth()
const asideToggleButton = (await select('.aside-area-toggle-btn')) as HTMLElement
if (!asideToggleButton) {
console.log('未找到侧边栏按钮')
return
}
const { default: ChatPanelFitDragger } = await import('./ChatPanelFitDragger.vue')
draggerInstance = mountVueComponent(ChatPanelFitDragger)
asideToggleButton.insertAdjacentElement('afterend', draggerInstance.$el)
}
const unload = () => {
removeComponentListener(`${name}.targetRatio`, calcPanelWidth)
removeComponentListener(`${name}.maxWidth`, calcPanelWidth)
window.removeEventListener('customWidthReset', calcPanelWidth)
window.removeEventListener('resize', debounceCalcPanelWidth)
document.documentElement.style.removeProperty('--live-chat-panel-width')
if (draggerInstance) {
draggerInstance.$el.remove()
draggerInstance.$destroy()
draggerInstance = undefined
}
}
export const component = defineComponentMetadata({
name,
displayName: '直播间网页全屏自适应',
tags: [componentsTags.live, componentsTags.style],
urlInclude: [...liveUrls],
entry: load,
reload: load,
unload,
instantStyles: [
{
name,
style: () => import('./chat-panel-fit.scss'),
important: true,
},
],
options: chatPanelFitOptions,
})

View File

@ -0,0 +1,16 @@
import { OptionsOfMetadata, defineOptionsMetadata } from '@/components/define'
import { getNumberValidator } from '@/core/utils'
export const ChatPanelFitOptionsMinWidth = 190
export const chatPanelFitOptions = defineOptionsMetadata({
customWidth: {
hidden: true,
defaultValue: 0,
},
maxWidth: {
defaultValue: 1000,
displayName: '侧边栏最大宽度 (px)',
validator: getNumberValidator(ChatPanelFitOptionsMinWidth),
},
})
export type ChatPanelFitOptions = OptionsOfMetadata<typeof chatPanelFitOptions>

View File

@ -0,0 +1,7 @@
#full-screen-interactive-wrap {
display: none !important
}
#fullscreen-danmaku-vm .fullscreen-danmaku {
bottom: 5px !important
}

View File

@ -0,0 +1,21 @@
import { defineComponentMetadata } from '@/components/define'
import { liveUrls } from '@/core/utils/urls'
export const component = defineComponentMetadata({
name: 'hide-fullscreen-gift-bar',
displayName: '全屏直播礼物简化',
description: '移除全屏观看直播时的底部礼物栏',
author: {
name: 'TimmyOVO',
link: 'https://github.com/TimmyOVO',
},
instantStyles: [
{
name: 'hide-fullscreen-gift',
style: () => import('./hide-full-screen-gift.css'),
},
],
entry: none,
tags: [componentsTags.live, componentsTags.style],
urlInclude: liveUrls,
})

View File

@ -5,20 +5,15 @@
</template>
<script lang="ts">
import { DefaultWidget } from '@/ui'
import { getOriginalLiveroomUrl } from './get-original-liveroom-url'
export default Vue.extend({
components: {
DefaultWidget,
},
data() {
const match = document.URL.match(/^https:\/\/live\.bilibili\.com\/([\d]+)/)
if (!match) {
return {
href: document.URL,
}
}
return {
href: `https://live.bilibili.com/blanc/${match[1]}`,
href: getOriginalLiveroomUrl(document.URL),
}
},
})

View File

@ -0,0 +1,8 @@
/**
* url url
* @param url
*/
export const getOriginalLiveroomUrl = (url: string): string => {
const match = url.match(/^https:\/\/live\.bilibili\.com\/([\d]+)/)
return match ? `https://live.bilibili.com/blanc/${match[1]}` : url
}

View File

@ -1,5 +1,6 @@
import { defineComponentMetadata } from '@/components/define'
import { matchUrlPattern } from '@/core/utils'
import { isIframe, isNotHtml, matchUrlPattern } from '@/core/utils'
import { getOriginalLiveroomUrl } from './get-original-liveroom-url'
export const component = defineComponentMetadata({
name: 'originalLiveroom',
@ -7,7 +8,20 @@ export const component = defineComponentMetadata({
description:
'在直播间中提供返回原版直播间的按钮, 原版直播间将无视活动皮肤, 强制使用标准的直播页面.',
tags: [componentsTags.live],
entry: none,
entry: ({ settings }) => {
if (isNotHtml() || isIframe()) {
return
}
if (settings.options.defaultBack) {
window.location.assign(getOriginalLiveroomUrl(document.URL))
}
},
options: {
defaultBack: {
displayName: '默认返回原版直播间',
defaultValue: false,
},
},
urlInclude: [
// 不能直接用 liveUrls, 那个是带 blanc 检测的
/^https:\/\/live\.bilibili\.com\/[\d]+/,

View File

@ -0,0 +1,270 @@
import {
OptionsOfMetadata,
defineComponentMetadata,
defineOptionsMetadata,
} from '@/components/define'
import { ComponentEntry } from '@/components/types'
import { addComponentListener } from '@/core/settings'
type Options = OptionsOfMetadata<typeof options>
const removeBadgeList: string[] = []
let removeLive = false
let removeAds = true
const options = defineOptionsMetadata({
广: {
displayName: '广告',
defaultValue: true,
},
: {
displayName: '番剧',
defaultValue: true,
},
: {
displayName: '电影',
defaultValue: true,
},
: {
displayName: '国创',
defaultValue: true,
},
: {
displayName: '电视剧',
defaultValue: true,
},
: {
displayName: '综艺',
defaultValue: true,
},
: {
displayName: '纪录片',
defaultValue: true,
},
: {
displayName: '动画',
defaultValue: true,
},
: {
displayName: '游戏',
defaultValue: true,
},
: {
displayName: '鬼畜',
defaultValue: true,
},
: {
displayName: '音乐',
defaultValue: true,
},
: {
displayName: '舞蹈',
defaultValue: true,
},
: {
displayName: '影视',
defaultValue: true,
},
: {
displayName: '娱乐',
defaultValue: true,
},
: {
displayName: '知识',
defaultValue: true,
},
: {
displayName: '科技',
defaultValue: true,
},
: {
displayName: '资讯',
defaultValue: true,
},
: {
displayName: '美食',
defaultValue: true,
},
: {
displayName: '生活',
defaultValue: true,
},
: {
displayName: '汽车',
defaultValue: true,
},
: {
displayName: '时尚',
defaultValue: true,
},
: {
displayName: '运动',
defaultValue: true,
},
: {
displayName: '动物圈',
defaultValue: true,
},
VLOG: {
displayName: 'VLOG',
defaultValue: true,
},
: {
displayName: '搞笑',
defaultValue: true,
},
: {
displayName: '单机游戏',
defaultValue: true,
},
UP主: {
displayName: '虚拟UP主',
defaultValue: true,
},
: {
displayName: '公益',
defaultValue: true,
},
: {
displayName: '公开课',
defaultValue: true,
},
: {
displayName: '专栏',
defaultValue: true,
},
: {
displayName: '直播',
defaultValue: true,
},
: {
displayName: '赛事',
defaultValue: true,
},
: {
displayName: '活动',
defaultValue: true,
},
: {
displayName: '课堂',
defaultValue: true,
},
: {
displayName: '社区中心',
defaultValue: true,
},
: {
displayName: '新歌热榜',
defaultValue: true,
},
: {
displayName: '漫画',
defaultValue: true,
},
})
function cardClear(mutationsList: HTMLElement[]) {
for (const mutation of mutationsList) {
if (!mutation.classList) {
continue
}
if (mutation.classList.contains('floor-single-card')) {
const badge = mutation.querySelector('.badge').textContent
if (badge && removeBadgeList.includes(badge)) {
mutation.remove()
}
} else if (mutation.classList.contains('bili-live-card')) {
if (removeLive) {
mutation.remove()
}
} else if (
mutation.classList.contains('bili-video-card') ||
mutation.classList.contains('feed-card')
) {
if (
removeAds &&
(mutation.querySelector('.bili-video-card__info--ad') ||
mutation.querySelector('.bili-video-card__info--creative-ad'))
) {
mutation.remove()
}
}
}
}
function cardFilter(mutationsList: MutationRecord[]) {
const filteredList: HTMLElement[] = []
for (const mutation of mutationsList) {
const mutationTarget = mutation.target as HTMLElement
if (
(mutationTarget.classList && mutationTarget.classList.contains('carousel-transform')) ||
mutationTarget.tagName === 'SPAN'
) {
continue
}
if (mutation.addedNodes.length > 0) {
for (const node of mutation.addedNodes) {
const nodeTarget = node as HTMLElement
if (
nodeTarget.nodeName === '#text' ||
(nodeTarget.classList && nodeTarget.classList.contains('bili-watch-later'))
) {
continue
}
filteredList.push(nodeTarget)
}
} else {
continue
}
}
if (filteredList.length > 0) {
cardClear(filteredList)
}
}
const clearHome: ComponentEntry<Options> = async ({ metadata, settings }) => {
const container = document.querySelector(
'main > .feed2 > .recommended-container_floor-aside > .container',
)
if (!container) {
return
}
const config = { childList: true }
const observer = new MutationObserver(cardFilter)
observer.observe(container, config)
Object.keys(settings.options).forEach(disableType => {
addComponentListener(
`${metadata.name}.${disableType}`,
(value: boolean) => {
if (value) {
if (disableType === '广告') {
removeAds = true
return
}
removeBadgeList.push(disableType)
if (disableType === '直播') {
removeLive = true
}
}
},
true,
)
})
const containerList = container.children as unknown as HTMLElement[]
cardClear(containerList)
}
export const component = defineComponentMetadata({
name: 'clear-home',
author: {
name: 'RieN7',
link: 'https://github.com/rien7',
},
tags: [componentsTags.style],
displayName: '首页净化',
description: '删除首页特定类型的卡片',
entry: clearHome,
options,
})

View File

@ -0,0 +1,16 @@
$name: 'custom-font-family';
html[#{$name}--options--disable-quotation-mark-text-indent='true']
:is(
p[title^=''],
p[title^=''],
p[title^=''],
h1[title^=''],
h1[title^=''],
h1[title^=''],
h3[title^=''],
h3[title^=''],
h3[title^='']
) {
text-indent: initial !important;
}

View File

@ -0,0 +1,45 @@
<template>
<div class="custom-font-family-extra-options-entry">
<VButton ref="button" @mouseover="loadExtraOptions()" @click="toggleExtraOptionsDisplay()">
字体设置<VIcon icon="right-arrow" :size="16"></VIcon>
</VButton>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { VIcon, VButton } from '@/ui'
import {
setTriggerElement,
getExtraOptionsLoadState,
loadExtraOptions,
toggleExtraOptionsDisplay,
} from './vm'
export default defineComponent({
components: {
VIcon,
VButton,
},
methods: {
async loadExtraOptions() {
const isLoaded = await getExtraOptionsLoadState()
if (!isLoaded) {
await loadExtraOptions()
const triggerButton = this.$refs.button.$el as HTMLElement
setTriggerElement(triggerButton)
}
},
toggleExtraOptionsDisplay,
},
})
</script>
<style lang="scss">
.custom-font-family-extra-options-entry {
display: flex;
align-items: center;
justify-content: center;
}
</style>

View File

@ -0,0 +1,109 @@
@import 'common';
$pref: 'eop';
$prefH: 'eop-h';
$prefHL: 'eop-h-l';
$prefHR: 'eop-h-r';
$prefC: 'eop-c';
$prefCS: 'eop-c-s';
$panelPadding: 22px;
@mixin extra-options-panel {
&.extra-options-panel {
@include popup();
display: flex;
flex-direction: column;
width: 500px;
max-height: 80vh;
padding: $panelPadding 0;
left: 50%;
top: 50%;
transform: translateX(-50%) translateY(-50%) scale(0.95);
transition: all 0.2s ease-out;
font-size: 14px;
z-index: 100002;
&.open {
transform: translateX(-50%) translateY(-50%) scale(1);
}
&.peek {
opacity: 0.1;
}
> .#{$pref}-header {
display: flex;
justify-content: space-between;
padding: 0 $panelPadding 18px $panelPadding;
flex-shrink: 0;
> .#{$prefH}-left {
display: flex;
column-gap: 4px;
> .#{$prefHL}-title {
@include semi-bold();
font-size: 19px;
line-height: 20px;
}
}
> .#{$prefH}-right {
display: flex;
column-gap: 20px;
> [class*='#{$prefHR}'] {
cursor: pointer;
transition: all 0.2s ease-out;
&:hover {
color: var(--theme-color);
}
}
}
}
> .#{$pref}-separator {
height: 1px;
margin: 0 $panelPadding;
background: rgba(0, 0, 0, 0.1);
flex-shrink: 0;
body.dark & {
background: rgba(255, 255, 255, 0.1);
}
}
> .#{$pref}-content {
padding: 0 $panelPadding;
overflow: auto;
flex-grow: 1;
flex-shrink: 1;
> .#{$prefC}-section {
margin: 18px 0;
&:nth-child(1):nth-last-child(1) {
margin-bottom: 12px;
}
> .#{$prefCS}-title {
margin-bottom: 2px;
font-size: 14px;
}
> .#{$prefCS}-description {
margin-bottom: 8px;
opacity: 0.6;
font-size: 12px;
}
> .#{$prefCS}-input {
@include h-center();
flex-wrap: wrap;
}
}
}
}
}

View File

@ -0,0 +1,136 @@
<template>
<VPopup
ref="popup"
v-model="popupOpen"
class="custom-font-family-extra-options-panel extra-options-panel"
fixed
:lazy="false"
:trigger-element="triggerElement"
:class="{ peek: isPeeking }"
>
<div class="eop-header">
<div class="eop-h-left">
<VIcon class="eop-h-l-symbol" icon="mdi-format-font" :size="24"></VIcon>
<div class="eop-h-l-title">字体设置</div>
</div>
<div class="eop-h-right">
<VIcon
class="eop-h-r-reset"
title="重置面板中的所有选项为默认值"
icon="mdi-cog-sync-outline"
:size="24"
@click="confirmResetOptions()"
></VIcon>
<VIcon
class="eop-h-r-peek"
title="透视"
icon="mdi-eye-outline"
:size="24"
@mouseover="mouseOnPeekIcon = true"
@mouseout="mouseOnPeekIcon = false"
></VIcon>
<VIcon
class="eop-h-r-close"
title="关闭"
icon="mdi-close"
:size="24"
@click="popupOpen = false"
></VIcon>
</div>
</div>
<div class="eop-separator"></div>
<div class="eop-content">
<div class="eop-c-section">
<div class="eop-c-s-title">自定义字体</div>
<div class="eop-c-s-description">输入字体不同字体之间必须以英文逗号分隔</div>
<div class="eop-c-s-input input-font-family">
<TextArea v-model="inputFontFamily" />
</div>
</div>
</div>
</VPopup>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { VPopup, VIcon, TextArea } from '@/ui'
import { getComponentSettings } from '@/core/settings'
import { delay } from '@/core/utils'
import { useScopedConsole } from '@/core/utils/log'
import { fontFamilyDefaultValue } from '../font-family-default-value'
export default defineComponent({
components: {
VPopup,
VIcon,
TextArea,
},
props: {
triggerElement: {
type: HTMLElement,
default: null,
},
},
data() {
return {
popupOpen: false,
isPeeking: false,
mouseOnPeekIcon: false,
// inputFontFamily fontFamily
inputFontFamily: getComponentSettings('customFontFamily').options.fontFamily,
}
},
watch: {
// inputFontFamily fontFamily inputFontFamily 500ms
inputFontFamily: lodash.debounce(value => {
getComponentSettings('customFontFamily').options.fontFamily = value
}, 500),
async mouseOnPeekIcon(value: boolean) {
if (!value) {
this.isPeeking = false
return
}
if (value) {
await delay(200)
}
if (this.mouseOnPeekIcon) {
this.isPeeking = true
}
},
},
methods: {
confirmResetOptions() {
if (confirm('确定将面板中的所有选项重置为默认值吗?')) {
this.resetOptions()
}
},
resetOptions() {
getComponentSettings('customFontFamily').options.fontFamily = fontFamilyDefaultValue
this.inputFontFamily = fontFamilyDefaultValue
const console = useScopedConsole('自定义字体')
console.log('已将字体设置面板中的所有选项重置为默认值')
},
},
})
</script>
<style lang="scss">
@import './extra-options-panel';
.custom-font-family-extra-options-panel {
@include extra-options-panel;
> .eop-content > .eop-c-section > .eop-c-s-input.input-font-family .be-text-area {
min-height: 160px;
}
}
</style>

View File

@ -0,0 +1,24 @@
import { mountVueComponent } from '@/core/utils'
let extraOptionsVm: Vue & {
popupOpen: boolean
triggerElement: HTMLElement
}
export const setTriggerElement = (element: HTMLElement) => {
extraOptionsVm.triggerElement = element
}
export const getExtraOptionsLoadState = async () => {
return Boolean(extraOptionsVm)
}
export const loadExtraOptions = async () => {
const extraOptions = await import('./panel.vue').then(m => m.default)
extraOptionsVm = mountVueComponent(extraOptions)
document.body.insertAdjacentElement('beforeend', extraOptionsVm.$el)
}
export const toggleExtraOptionsDisplay = async () => {
extraOptionsVm.popupOpen = !extraOptionsVm.popupOpen
}

View File

@ -0,0 +1,2 @@
export const fontFamilyDefaultValue =
'Microsoft Yahei, Helvetica Neue, Helvetica, Arial, Hiragino Sans GB, Heiti SC, WenQuanYi Micro Hei'

View File

@ -0,0 +1,133 @@
import { defineComponentMetadata } from '@/components/define'
import { addComponentListener } from '@/core/settings'
import { fontFamilyDefaultValue } from './font-family-default-value'
const docElement = document.documentElement
const kebabName = 'custom-font-family'
const camelName = 'customFontFamily'
const name = camelName
const displayName = '自定义字体'
const description = {
'zh-CN':
'使用组件提供的字体设置覆盖原版的主站字体,并使主站字体可被自定义。字体设置写法请参考 [MDN](https://developer.mozilla.org/zh-CN/docs/Web/CSS/font-family) 、默认设置与设置说明',
}
const tags = [componentsTags.style, componentsTags.general]
const entry = () => {
addComponentListener(
`${name}.fontFamily`,
(value: string) => {
if (!value) {
docElement.setAttribute(`${kebabName}--detect--is-input-empty`, 'true')
return
}
docElement.style.setProperty(`--${kebabName}--options--font-family`, `${value}`)
docElement.setAttribute(`${kebabName}--detect--is-input-empty`, 'false')
},
true,
)
addComponentListener(
`${name}.disableQuotationMarkTextIndent`,
(value: boolean) => {
docElement.setAttribute(
`${kebabName}--options--disable-quotation-mark-text-indent`,
`${value}`,
)
},
true,
)
const onOptionNames = [
{ camel: 'onOrnament', kebab: 'on-ornament' },
{ camel: 'onFansMedal', kebab: 'on-fans-medal' },
{ camel: 'onDanmaku', kebab: 'on-danmaku' },
{ camel: 'onIconFont', kebab: 'on-icon-font' },
{ camel: 'onColumn', kebab: 'on-column' },
{ camel: 'onScore', kebab: 'on-score' },
]
onOptionNames.forEach(onOptionName => {
addComponentListener(
`${name}.${onOptionName.camel}`,
(value: boolean) => {
docElement.setAttribute(`${kebabName}--options--${onOptionName.kebab}`, `${value}`)
},
true,
)
})
}
const options = {
fontFamily: {
displayName: '字体',
defaultValue: fontFamilyDefaultValue,
hidden: true,
},
disableQuotationMarkTextIndent: {
displayName: '禁用引号缩进',
defaultValue: true,
},
onOrnament: {
displayName: '覆盖装扮字体',
defaultValue: false,
},
onFansMedal: {
displayName: '覆盖粉丝勋章字体',
defaultValue: false,
},
onDanmaku: {
displayName: '覆盖弹幕字体',
defaultValue: false,
},
onIconFont: {
displayName: '覆盖图标字体',
defaultValue: false,
},
onColumn: {
displayName: '覆盖专栏自定义字体',
defaultValue: false,
},
onScore: {
displayName: '覆盖评分字体',
defaultValue: false,
},
}
const extraOptions = () => import('./extra-options/entry.vue').then(m => m.default)
const instantStyles = [
{
name: `${name}--style--setFontFamily`,
style: () => import('./set-font-family.scss'),
important: true,
},
{
name: `${name}--style--disableQuotationMarkTextIndent`,
style: () => import('./disable-quotation-mark-text-indent.scss'),
important: true,
},
]
const author = {
name: 'Tinhone',
link: 'https://github.com/Tinhone',
}
export const component = defineComponentMetadata({
name,
displayName,
description,
tags,
entry,
options,
extraOptions,
instantStyles,
author,
})

View File

@ -0,0 +1,115 @@
$name: 'custom-font-family';
$fontFamilyCss: var(--#{$name}--options--font-family) !important;
// 收集 CSS 路径为单一字符串
@function collectPaths($pathList) {
$stringPath: '';
@each $path in $pathList {
$i: index($pathList, $path);
@if $i!=1 {
$stringPath: '#{$stringPath}, ';
}
$stringPath: '#{$stringPath}#{$path}';
}
@return $stringPath;
}
@function getOrnamentPath() {
$newVideo: '.reply-item .root-reply-container .content-warp .reply-decorate .user-sailing .user-sailing-text .sailing-text';
$dynamic: '.bili-dyn-ornament__type--3 span';
$dynamicCommentArea: '.bb-comment .sailing .sailing-info, .comment-bilibili-fold .sailing .sailing-info';
@return collectPaths(($newVideo, $dynamic, $dynamicCommentArea));
}
@function getFansMedalPath() {
$newVideo: '.badge-level';
@return collectPaths(($newVideo));
}
@function getDanmakuPath() {
$main: '.bili-dm';
@return collectPaths(($main));
}
@function getIconFontPath() {
$mainA: '.iconfont';
$mainB: '.icon-font';
$note: '.read-icon';
$noteEdit: '.bili-note-iconfont';
$footer: '.bili-footer-font';
$bangumiInfo: '[class^="icon-"]';
$history: '.bilifont';
$message: '.bp-icon-font';
$creativeCenterA: '.bcc-iconfont';
$creativeCenterB: '.fontvt';
@return collectPaths(
(
$mainA,
$mainB,
$note,
$noteEdit,
$footer,
$bangumiInfo,
$history,
$message,
$creativeCenterA,
$creativeCenterB
)
);
}
@function getColumnPath() {
$mainA: '.article-detail .article-container .article-container__content .article-content .read-article-holder';
$mainB: '.article-detail .article-container .article-container__content .article-content .read-article-holder *';
@return collectPaths(($mainA, $mainB));
}
@function getScorePath() {
$documentary: '.season-cover .score';
$big: '.season-cover .season-cover-score';
@return collectPaths(($documentary, $big));
}
// 装扮在评论区动态等界面显示
$ornament: getOrnamentPath();
// 粉丝勋章在评论区界面显示
$fansMedal: getFansMedalPath();
// 弹幕在播放器里显示
$danmaku: getDanmakuPath();
// 图标字体
$iconFont: getIconFontPath();
// 专栏阅读页可能出现的部分自定义字体
$column: getColumnPath();
// 分数在部分剧集的右下角显示
$score: getScorePath();
// 收集所有路径
$discardPaths: collectPaths(($ornament, $fansMedal, $danmaku, $iconFont, $column, $score));
$onOptionNames: (
'on-ornament',
'on-fans-medal',
'on-danmaku',
'on-icon-font',
'on-column',
'on-score'
);
$onOptionPathLists: ($ornament, $fansMedal, $danmaku, $iconFont, $column, $score);
$onOptionGroups: zip($onOptionNames, $onOptionPathLists);
html[#{$name}--detect--is-input-empty='false'] {
:not(#{$discardPaths}) {
font-family: $fontFamilyCss;
}
@each $onOptionName, $onOptionPathList in $onOptionGroups {
&[#{$name}--options--#{$onOptionName}='true'] :is(#{$onOptionPathList}) {
font-family: $fontFamilyCss;
}
}
}

View File

@ -314,9 +314,10 @@ export default Vue.extend({
color: var(--foreground-color);
border-radius: 0 0 8px 8px;
&:not(:empty):not(.hidden) {
html:not([data-navbar-notify-style='hidden']) &:not(:empty):not(.hidden) {
opacity: 1;
}
html[data-navbar-notify-style='dot'] &,
&.dot {
color: transparent;
border-radius: 50%;

View File

@ -6,8 +6,9 @@
border-bottom: 2px solid transparent;
font-size: 15px;
line-height: normal;
&::before {
content: "";
content: '';
position: absolute;
top: calc(100% - 4px);
left: 8px;
@ -18,7 +19,23 @@
transition: 0.16s 0.1s ease-out;
transform: scaleX(0);
}
&:hover::before {
transform: scaleX(1);
}
}
html[data-navbar-link-popup-content-align-style='left'] &,
&.left {
justify-content: flex-start;
}
html[data-navbar-link-popup-content-align-style='center'] &,
&.center {
justify-content: center;
}
html[data-navbar-link-popup-content-align-style='right'] &,
&.right {
justify-content: flex-end;
}
}

View File

@ -6,7 +6,7 @@ import { logo } from './logo/logo'
import { home } from './home/home'
import { gamesIframe, livesIframe, mangaIframe } from './iframe/iframe'
import { blanks } from './flexible-blank/flexible-blank'
import { bangumi, music, drawing, shop, match } from './simple-links/simple-links'
import { bangumi, music, drawing, shop, match, creations } from './simple-links/simple-links'
import { upload } from './upload/upload'
import { search } from './search/search'
import { feeds } from './feeds/feeds'
@ -39,6 +39,7 @@ export const getBuiltInItems = (): CustomNavbarItemInit[] => [
watchlater,
favorites,
history,
creations,
upload,
blank4,
]

View File

@ -1,6 +1,8 @@
import { ComponentEntry } from '@/components/types'
import { addComponentListener } from '@/core/settings'
import { isIframe, isNotHtml, matchUrlPattern, mountVueComponent } from '@/core/utils'
import { setupNotifyStyle } from './notify-style'
import { setupLinkPopupContentAlignStyle } from './link-popup-content-align-style'
export const entry: ComponentEntry = async ({ metadata: { name } }) => {
// const url = document.URL.replace(location.search, '')
@ -20,6 +22,13 @@ export const entry: ComponentEntry = async ({ metadata: { name } }) => {
},
true,
)
addComponentListener(
`${name}.searchBarWidth`,
value => {
document.documentElement.style.setProperty('--navbar-search-width', `${value}vw`)
},
true,
)
const globalFixedExclude = [
'https://space.bilibili.com',
'https://www.bilibili.com/read',
@ -43,4 +52,6 @@ export const entry: ComponentEntry = async ({ metadata: { name } }) => {
;['fill', 'shadow', 'blur'].forEach(style => {
addComponentListener(`${name}.${style}`, value => customNavbar.toggleStyle(value, style), true)
})
setupNotifyStyle()
setupLinkPopupContentAlignStyle()
}

View File

@ -31,7 +31,7 @@
</template>
<script lang="ts">
import { VideoCard } from '@/components/feeds/video-card'
import { formatDuration, formatCount, parseDuration } from '@/core/utils/formatters'
import { formatDuration, formatCount, parseDuration, parseCount } from '@/core/utils/formatters'
import { isNewID } from '@/components/feeds/notify'
import { feedsCardTypes, groupVideoFeeds } from '@/components/feeds/api'
import VideoCardComponent from '@/components/feeds/VideoCard.vue'
@ -98,7 +98,7 @@ export default Vue.extend({
upName: author.name,
upID: author.mid,
watchlater: true,
playCount: formatCount(archive.stat.play),
playCount: formatCount(parseCount(archive.stat.play)),
get new() {
return isNewID(this.id)
},

View File

@ -1,3 +1,20 @@
使用脚本提供的顶栏替换原版的主站顶栏 (非主站不替换), 启用后还可以在反广告插件 (例如 ABP) 中配置禁止原版顶栏的消息 iframe 以提升性能:
使用脚本提供的顶栏替换原版的主站顶栏 (非主站不替换).
启用后还可以在反广告插件 (例如 ABP) 中配置禁止原版顶栏的消息 iframe 以提升性能:
- *://message.bilibili.com/pages/nav/index_new_pc_sync
- *://message.bilibili.com/pages/nav/index_new_sync
选项说明:
- `全局固定`: 将顶栏固定显示, 即使页面向下滚动也仍然可见. (除了一部分有 b 站定制顶栏的页面会忽略此选项)
- `主题色填充`: 使用通用设置中的主题颜色填充顶栏的背景.
- `透明填充`: 在首页和主站的部分页面中, 当存在顶部横幅图片时, 顶栏背景将变为透明 + 阴影的样式.
- `背景模糊`: 启用背景模糊效果, 会使背景颜色变得半透明, 注意这个效果非常非常消耗图形性能, 慎用.
- `投影`: 在顶栏下方增加一小段投影效果.
- `使用季节 Logo`: 使用 b 站首页的特殊 Logo 图片替换顶栏的 Logo 图, 通常会对应当前季节, 特殊活动期间可能会有变化导致看不清. (比如搞了个全白的 Logo, 而顶栏正好也是白色的)
- `触摸模式`: 启用后顶栏的一级入口点击时不进行跳转, 方便触屏使用.
- `新标签页打开`: 控制顶栏内链接是否在新标签页打开.
- `自动刷新数据`: 启用后, 部分有弹窗的顶栏入口在鼠标经过时会自动刷新数据.
- `顶栏高度`: 自定义顶栏的高度 (b 站原版的为 64px)
- `消息提醒样式`: 自定义顶栏入口的消息提醒样式.
- `搜索栏宽度`: 自定义搜索栏占顶栏的比例.
- `链接对齐样式`: 自定义纯链接弹窗内链接文字的对齐样式.

View File

@ -7,12 +7,23 @@ import { LaunchBarActionProvider } from '@/components/launch-bar/launch-bar-acti
import { urlInclude, urlExclude } from './urls'
import { entry } from './entry'
import { getNumberValidator } from '@/core/utils'
import { NavbarNotifyStyle } from './notify-style'
import { NavbarLinkPopupContentAlignStyle } from './link-popup-content-align-style'
const styleID = 'custom-navbar-style'
const options = defineOptionsMetadata({
hidden: {
hidden: true,
defaultValue: ['blank1', 'blank4', 'drawing', 'music', 'gamesIframe', 'bangumi', 'match'],
defaultValue: [
'blank1',
'blank4',
'drawing',
'music',
'gamesIframe',
'bangumi',
'match',
'creations',
],
displayName: '隐藏的元素',
},
order: {
@ -76,6 +87,24 @@ const options = defineOptionsMetadata({
displayName: '显示已失效视频',
hidden: true,
},
notifyStyle: {
defaultValue: NavbarNotifyStyle.Number,
dropdownEnum: NavbarNotifyStyle,
displayName: '消息提醒样式',
},
linkPopupContentAlignStyle: {
defaultValue: NavbarLinkPopupContentAlignStyle.Left,
dropdownEnum: NavbarLinkPopupContentAlignStyle,
displayName: '链接对齐样式',
},
searchBarWidth: {
defaultValue: 15,
slider: {
min: 8,
max: 64,
},
displayName: '搜索栏宽度 (%)',
},
})
export const component = defineComponentMetadata({
name: 'customNavbar',

View File

@ -0,0 +1,25 @@
import { addComponentListener } from '@/core/settings'
export enum NavbarLinkPopupContentAlignStyle {
Left = '左侧对齐',
Center = '居中对齐',
Right = '右侧对齐',
}
export const setupLinkPopupContentAlignStyle = () => {
const map = {
[NavbarLinkPopupContentAlignStyle.Left]: 'left',
[NavbarLinkPopupContentAlignStyle.Center]: 'center',
[NavbarLinkPopupContentAlignStyle.Right]: 'right',
}
addComponentListener(
'customNavbar.linkPopupContentAlignStyle',
(value: NavbarLinkPopupContentAlignStyle) => {
document.documentElement.setAttribute(
'data-navbar-link-popup-content-align-style',
map[value],
)
},
true,
)
}

View File

@ -0,0 +1,22 @@
import { addComponentListener } from '@/core/settings'
export enum NavbarNotifyStyle {
Number = '数字',
Hidden = '隐藏',
Dot = '点状',
}
export const setupNotifyStyle = () => {
const map = {
[NavbarNotifyStyle.Number]: 'number',
[NavbarNotifyStyle.Hidden]: 'hidden',
[NavbarNotifyStyle.Dot]: 'dot',
}
addComponentListener(
'customNavbar.notifyStyle',
(value: NavbarNotifyStyle) => {
document.documentElement.setAttribute('data-navbar-notify-style', map[value])
},
true,
)
}

View File

@ -30,6 +30,14 @@ const entries = [
href: 'https://www.bilibili.com/v/popular/rank/all',
name: '排行榜',
},
{
href: 'https://www.bilibili.com/v/popular/music',
name: '全站音乐榜',
},
{
href: 'https://www.bilibili.com/v/popular/drama',
name: '短剧榜',
},
] as RankingEntry[]
export default Vue.extend({
name: 'RankingPopup',

View File

@ -29,6 +29,7 @@ export default Vue.extend({
}
.input-area .launch-bar-form .input {
padding: 8px 6px;
width: var(--navbar-search-width);
}
}

View File

@ -41,3 +41,8 @@ export const match = getSimpleLinkItem({
displayName: '赛事',
href: 'https://www.bilibili.com/v/game/match/',
})
export const creations = getSimpleLinkItem({
name: 'creations',
displayName: '创作中心',
href: 'https://member.bilibili.com/platform/home',
})

View File

@ -111,10 +111,16 @@ export default Vue.extend({
async nextPage() {
try {
const filter = this.filter as SubscriptionStatusFilter
const followStatus = filter.viewAll ? 0 : (filter.status as number)
const params = new URLSearchParams({
type: this.type !== SubscriptionTypes.Bangumi ? '2' : '1',
pn: this.page,
ps: '16',
vmid: getUID(),
follow_status: followStatus.toString(),
})
const json = await getJsonWithCredentials(
`https://api.bilibili.com/x/space/bangumi/follow/list?type=${
this.type !== SubscriptionTypes.Bangumi ? '2' : '1'
}&pn=${this.page}&ps=16&vmid=${getUID()}`,
`https://api.bilibili.com/x/space/bangumi/follow/list?${params}`,
)
if (json.code !== 0) {
logError(`加载订阅信息失败: ${json.message}`)
@ -137,16 +143,10 @@ export default Vue.extend({
),
card => card.id,
)
.filter(card => {
if (filter.viewAll) {
return true
}
return card.status === filter.status
})
.sort(subscriptionSorter)
this.page++
this.cards = this.cards.concat(newCards)
this.hasMorePage = newCards.length > 0
this.hasMorePage = this.cards.length < json.data.total
} finally {
this.loading = false
}

View File

@ -436,7 +436,7 @@ export default Vue.extend({
}
span {
font-size: 14px;
font-weight: bold;
@include semi-bold();
opacity: 0.7;
}
}

View File

@ -0,0 +1,3 @@
.video-toolbar-container .video-note .video-note-inner {
display: none !important;
}

View File

@ -0,0 +1 @@
隐藏视频页面中的 "记笔记" 按钮.

View File

@ -0,0 +1,15 @@
import { defineComponentMetadata } from '@/components/define'
const name = 'hideVideoNotes'
export const component = defineComponentMetadata({
name,
displayName: '隐藏记笔记',
tags: [componentsTags.video, componentsTags.style],
entry: none,
instantStyles: [
{
name,
style: () => import('./hide-video-notes.scss'),
},
],
})

View File

@ -0,0 +1 @@
隐藏番剧和视频页面右侧的推荐视频列表. 注意: 如果你想关闭 b 站的自动连播 (自动播放下一个推荐视频) 功能, 需要先取消隐藏视频推荐才能看到开关.

View File

@ -12,9 +12,5 @@ export const component = defineComponentMetadata({
},
],
tags: [componentsTags.style, componentsTags.video],
description: {
'zh-CN':
'隐藏番剧和视频页面右侧的推荐视频列表. 注意: 如果你想关闭 b 站的自动连播 (自动播放下一个推荐视频) 功能, 需要先取消隐藏视频推荐才能看到开关.',
},
urlInclude: videoAndBangumiUrls,
})

View File

@ -2,13 +2,16 @@
#reco_list,
.bilibili-player-ending-panel-box-videos,
.r-con .rcmd-list,
.playlist-container .recommend-list-container,
.bpx-player-ending-related,
.plp-r [class*="recommend_wrap"] {
display: none !important;
}
.bilibili-player-ending-panel-box-functions .bilibili-player-upinfo-spans {
position: static !important;
}
.bilibili-player-ending-panel-box {
.bilibili-player-ending-panel-box,
.bpx-player-ending-content {
display: flex !important;
justify-content: center !important;
flex-direction: column !important;

View File

@ -0,0 +1,3 @@
.video-toolbar-container .video-complaint {
display: none !important;
}

View File

@ -0,0 +1 @@
隐藏视频页面中的 "稿件投诉" 按钮.

View File

@ -0,0 +1,15 @@
import { defineComponentMetadata } from '@/components/define'
const name = 'hideVideoReport'
export const component = defineComponentMetadata({
name,
displayName: '隐藏稿件投诉',
tags: [componentsTags.video, componentsTags.style],
entry: none,
instantStyles: [
{
name,
style: () => import('./hide-video-report.scss'),
},
],
})

View File

@ -2,37 +2,67 @@
<div class="fresh-home-extra-options">
<div class="fresh-home-extra-options-title">布局配置</div>
<VLoading v-if="!loaded" />
<div v-if="loaded" class="fresh-home-extra-options-tips">
<div class="fresh-home-extra-options-tip">
按住并拖动可以调整顺序, 打开换行后下一个元素将会另起一行.
</div>
<div class="fresh-home-extra-options-legends">
<div class="fresh-home-extra-options-legend">
<VIcon icon="mdi-arrow-expand-horizontal" :size="16" />
<div class="fresh-home-extra-options-legend-text">可变宽度</div>
</div>
<div class="fresh-home-extra-options-legend">
<VIcon icon="mdi-arrow-collapse-horizontal" :size="16" />
<div class="fresh-home-extra-options-legend-text">固定宽度</div>
</div>
<div class="fresh-home-extra-options-legend">
<VIcon icon="mdi-wrap" :size="16" />
<div class="fresh-home-extra-options-legend-text">切换换行</div>
</div>
<div class="fresh-home-extra-options-legend">
<VIcon icon="mdi-eye-outline" :size="16" />
<div class="fresh-home-extra-options-legend-text">切换显示</div>
</div>
</div>
</div>
<div v-show="loaded" ref="sortList" class="fresh-home-extra-options-sort-list">
<div
v-for="item of sortItems"
v-for="item of sortedItems"
:key="item.layoutItem.name"
class="fresh-home-extra-options-sort-item-container"
:data-name="item.layoutItem.name"
class="fresh-home-extra-options-sort-item"
:class="{ 'fresh-home-extra-options-sort-item-dimmed': item.layoutSettings.hidden }"
>
<div
:data-name="item.layoutItem.name"
:style="{ 'flex-grow': item.layoutItem.grow ? '1' : '0' }"
class="fresh-home-extra-options-sort-item"
>
<div class="fresh-home-extra-options-sort-item-name">
<VIcon v-if="item.layoutItem.grow" icon="mdi-arrow-expand-horizontal" :size="16" />
<VIcon v-else icon="mdi-arrow-collapse-horizontal" :size="16" />
{{ item.layoutItem.displayName }}
</div>
<div class="fresh-home-extra-options-sort-item-config">
<VButton
icon
round
@click="item.layoutSettings.linebreak = !item.layoutSettings.linebreak"
>
<VIcon v-if="item.layoutSettings.linebreak" icon="mdi-wrap" :size="16" />
<VIcon v-else icon="mdi-wrap-disabled" :size="16" />
</VButton>
</div>
<VIcon
v-if="item.layoutItem.grow"
title="可变宽度"
icon="mdi-arrow-expand-horizontal"
class="fresh-home-extra-options-sort-item-type"
:size="16"
/>
<VIcon
v-else
icon="mdi-arrow-collapse-horizontal"
class="fresh-home-extra-options-sort-item-type"
title="固定宽度"
:size="16"
/>
<div class="fresh-home-extra-options-sort-item-name">{{ item.layoutItem.displayName }}</div>
<div class="fresh-home-extra-options-sort-item-actions">
<VButton
type="transparent"
title="切换换行"
icon
@click="toggleLinebreak(item.layoutItem)"
>
<VIcon v-if="item.layoutSettings.linebreak" icon="mdi-wrap" :size="16" />
<VIcon v-else icon="mdi-wrap-disabled" :size="16" />
</VButton>
<VButton type="transparent" title="切换显示" icon @click="toggleVisible(item.layoutItem)">
<VIcon v-if="item.layoutSettings.hidden" icon="mdi-eye-off-outline" :size="16" />
<VIcon v-else icon="mdi-eye-outline" :size="16" />
</VButton>
</div>
<div
v-if="item.layoutSettings.linebreak"
class="fresh-home-extra-options-sort-item-linebreak"
></div>
</div>
</div>
</div>
@ -57,15 +87,40 @@ export default Vue.extend({
return {
loaded: false,
layouts,
sortedItems: [],
}
},
computed: {
async mounted() {
const list: HTMLElement = this.$refs.sortList
const Sortable = await SortableJSLibrary
console.log({ list })
Sortable.create(list, {
delay: 100,
forceFallback: true,
onEnd: (e: SortableEvent) => {
const sortedItems = [...this.sortedItems] as SortItem[]
const [targetItem] = sortedItems.splice(e.oldIndex, 1)
console.log(targetItem.layoutItem.name, `${e.oldIndex} -> ${e.newIndex}`)
sortedItems.splice(e.newIndex, 0, targetItem)
const { layoutOptions } = freshHomeOptions
sortedItems.forEach((it, index) => {
;(layoutOptions[it.layoutItem.name] as FreshLayoutItemSettings).order = index + 1
it.layoutSettings.order = index + 1
})
this.sortedItems = sortedItems
},
})
this.sortItems()
this.loaded = true
},
methods: {
sortItems() {
return (this.layouts as FreshLayoutItem[])
this.sortedItems = (this.layouts as FreshLayoutItem[])
.map((layoutItem, index): SortItem => {
const layoutSettings: FreshLayoutItemSettings = {
linebreak: false,
order: index + 1,
hidden: false,
...freshHomeOptions.layoutOptions[layoutItem.name],
}
return {
@ -76,19 +131,16 @@ export default Vue.extend({
.filter(it => it !== null)
.sort(ascendingSort(it => it.layoutSettings.order))
},
},
async mounted() {
const list: HTMLElement = this.$refs.sortList
const Sortable = await SortableJSLibrary
console.log({ list })
Sortable.create(list, {
delay: 100,
forceFallback: true,
onEnd: (e: SortableEvent) => {
console.log(e)
},
})
this.loaded = true
toggleLinebreak(item: FreshLayoutItem) {
const options = freshHomeOptions.layoutOptions[item.name]
options.linebreak = !options.linebreak
this.sortItems()
},
toggleVisible(item: FreshLayoutItem) {
const options = freshHomeOptions.layoutOptions[item.name]
options.hidden = !options.hidden
this.sortItems()
},
},
})
</script>
@ -98,30 +150,57 @@ export default Vue.extend({
.fresh-home-extra-options {
@include v-stretch(10px);
padding: 4px 0;
&-sort-list {
@include h-stretch();
column-gap: 8px;
row-gap: 4px;
&-tips {
@include v-stretch(4px);
}
&-tip {
opacity: 0.6;
}
&-legends {
@include h-center();
column-gap: 6px;
row-gap: 2px;
flex-wrap: wrap;
}
&-sort-item-container {
display: contents;
&-legend {
@include h-center(4px);
}
.mdi-arrow-expand-horizontal {
&,
& ~ .fresh-home-extra-options-legend-text {
color: #a875ff;
}
}
.mdi-arrow-collapse-horizontal {
&,
& ~ .fresh-home-extra-options-legend-text {
color: #009fd9;
}
}
&-sort-list {
@include h-stretch();
gap: 6px;
flex-wrap: wrap;
}
&-sort-item {
@include v-center(6px);
@include h-center(4px);
@include border-card();
padding: 8px;
@include shadow();
padding: 2px 4px 2px 8px;
transition: border-color 0.2s ease-out;
body.dark &:hover,
&:hover {
border-color: var(--theme-color);
cursor: move;
}
&-name {
@include h-center(4px);
&-dimmed {
opacity: 0.5;
}
&-linebreak {
flex: 1 0 100%;
&-actions {
@include h-center();
flex-grow: 1;
justify-content: flex-end;
}
}
}

View File

@ -53,7 +53,7 @@ export default Vue.extend({
}
&-title {
color: var(--home-color);
font-weight: bold;
@include semi-bold();
font-size: 20px;
line-height: 28px;
}

View File

@ -1,15 +1,25 @@
<template>
<fragment>
<div
class="fresh-home-content-layout-item"
:class="{
grow: item.grow,
}"
>
<component :is="item.component" />
</div>
<div v-if="options.linebreak" class="fresh-home-content-layout-item linebreak"></div>
</fragment>
<div
class="fresh-home-content-layout-item-container"
:class="{ 'fresh-home-content-layout-item-hidden': options.hidden }"
>
<template v-if="!options.hidden">
<div
class="fresh-home-content-layout-item"
:class="{
grow: item.grow,
}"
:style="{ order: options.order }"
>
<component :is="item.component" />
</div>
<div
v-if="options.linebreak"
class="fresh-home-content-layout-item linebreak"
:style="{ order: options.order }"
/>
</template>
</div>
</template>
<script lang="ts">
import { freshHomeOptions } from './options'
@ -33,6 +43,12 @@ export default Vue.extend({
&-content-layout-item {
flex: 0 0 auto;
padding: 12px;
&-container {
display: contents;
&.fresh-home-content-layout-item-hidden {
display: none;
}
}
&.linebreak {
padding: 0;
flex: 1 0 100%;

View File

@ -25,7 +25,7 @@ export const component = defineComponentMetadata({
})
},
options: freshHomeOptionsMetadata,
// extraOptions: () => import('./ExtraOptions.vue'),
extraOptions: () => import('./ExtraOptions.vue'),
unload: () => document.body.classList.add('home-redesign-off'),
reload: () => document.body.classList.remove('home-redesign-off'),
instantStyles: [

View File

@ -0,0 +1,22 @@
import { addComponentListener } from '@/core/settings'
export enum areaPrimaryTitleColorEnum {
Dark = '深色',
Light = '浅色',
}
const map = {
[areaPrimaryTitleColorEnum.Dark]: 'dark',
[areaPrimaryTitleColorEnum.Light]: 'light',
}
addComponentListener(
'freshHome.areaPrimaryTitleColor',
(value: areaPrimaryTitleColorEnum) => {
document.documentElement.setAttribute(
'fresh-home--options--area-primary-title-color',
map[value],
)
},
true,
)

View File

@ -97,7 +97,16 @@ export default Vue.extend({
bottom: 8px;
font-size: 18px;
@include semi-bold();
color: black;
transition: color 0.2s ease-out, text-shadow 0.2s ease-out;
html[fresh-home--options--area-primary-title-color='dark'] & {
color: black;
}
html[fresh-home--options--area-primary-title-color='light'] & {
color: #fafafa;
text-shadow: rgba(0, 0, 0, 0.2) 0 0 8px;
}
}
}
&-other {

View File

@ -123,14 +123,13 @@ export default Vue.extend({
}
&-header {
@include h-center();
height: 26px;
justify-content: space-between;
}
}
&-rank-list {
@include v-stretch(var(--fresh-home-categories-header-gap));
&-header {
// Timeline header , 20px, padding 8px, line-height
// line-height: calc(20px + 8px);
@include h-center();
justify-content: space-between;
.be-icon {

View File

@ -11,6 +11,10 @@
class="fresh-home-categories-bangumi-timeline-item animation"
:class="{ today: index === todayIndex }"
>
<div
v-if="item.episodes.length === 0"
class="fresh-home-categories-bangumi-timeline-empty-background"
></div>
<div class="fresh-home-categories-bangumi-timeline-date">
<div
class="fresh-home-categories-bangumi-timeline-date-icon"
@ -29,8 +33,18 @@
TODAY
</div>
</div>
<VButton
v-if="item.episodes.length > 0"
icon
type="transparent"
title="上一页"
@click="offsetPage(item, -1)"
>
<VIcon icon="left-arrow" :size="16" />
</VButton>
<div
ref="seasonsList"
:data-date="item.date"
class="fresh-home-categories-bangumi-timeline-seasons-container scroll-top scroll-bottom"
:class="{ 'not-empty': item.episodes.length > 0 }"
>
@ -55,7 +69,7 @@
<div
class="fresh-home-categories-bangumi-timeline-season-cover"
:class="{
published: index === todayIndex && publishedToday(season),
published: index === todayIndex && isPublished(season),
today: index === todayIndex,
follow: season.follow,
}"
@ -78,15 +92,18 @@
<div
class="fresh-home-categories-bangumi-timeline-season-time"
:class="{
published: index === todayIndex && publishedToday(season),
published: index === todayIndex && isPublished(season),
follow: season.follow,
today: index === todayIndex,
}"
>
<div class="fresh-home-categories-bangumi-timeline-season-time-icon">
<VIcon
:icon="season.follow ? 'mdi-heart-outline' : 'mdi-progress-clock'"
v-if="!season.follow"
:icon="isPublished(season) ? 'mdi-clock-check-outline' : 'mdi-progress-clock'"
:size="14"
/>
<VIcon v-else icon="mdi-heart-outline" :size="14" />
</div>
<div class="fresh-home-categories-bangumi-timeline-season-time-text">
{{ season.pub_time }}
@ -95,11 +112,20 @@
</a>
</div>
</div>
<VButton
v-if="item.episodes.length > 0"
icon
type="transparent"
title="下一页"
@click="offsetPage(item, 1)"
>
<VIcon icon="right-arrow" :size="16" />
</VButton>
</div>
</div>
</template>
<script lang="ts">
import { DpiImage, VIcon, VEmpty, VLoading } from '@/ui'
import { DpiImage, VIcon, VEmpty, VButton, VLoading } from '@/ui'
import { addComponentListener } from '@/core/settings'
import { enableHorizontalScroll } from '@/core/horizontal-scroll'
import { cssVariableMixin, requestMixin } from '../../../../mixin'
@ -138,11 +164,11 @@ const rankListHeight = rankListCssVars.panelHeight - 2 * rankListCssVars.padding
const timelineCssVars = (() => {
const seasonItemWidth = 250
const seasonTodayWidth = 250
const timelineItemHeight = 66
const timelineTodayHeight = 96
const timelineViewportItemsHeight = 6 * timelineItemHeight + timelineTodayHeight
const timelineItemGap = (rankListHeight - timelineViewportItemsHeight) / 6
const timelineViewportHeight = 6 * timelineItemGap + timelineViewportItemsHeight
const timelineItemHeight = 82
const timelineTodayHeight = 114
const timelineViewportItemsHeight = 5 * timelineItemHeight + timelineTodayHeight
const timelineItemGap = (rankListHeight - timelineViewportItemsHeight) / 5
const timelineViewportHeight = 5 * timelineItemGap + timelineViewportItemsHeight
return {
seasonItemWidth,
seasonTodayWidth,
@ -159,6 +185,7 @@ export default Vue.extend({
VIcon,
VEmpty,
VLoading,
VButton,
},
mixins: [
requestMixin({ requestMethod: getJsonWithCredentials }),
@ -252,7 +279,7 @@ export default Vue.extend({
if (seasonsData.length === 0) {
return
}
const lastPublishedItem = [...seasonsData].reverse().find(it => this.publishedToday(it))
const lastPublishedItem = [...seasonsData].reverse().find(it => this.isPublished(it))
if (!lastPublishedItem) {
this.scrolled = true
return
@ -273,7 +300,7 @@ export default Vue.extend({
}
return season.pub_index
},
publishedToday(season: TimelineSeason) {
isPublished(season: TimelineSeason) {
if (season.delay) {
return false
}
@ -282,6 +309,16 @@ export default Vue.extend({
dayOfWeekText(item: TimelineDay) {
return `${['日', '一', '二', '三', '四', '五', '六', '日'][item.day_of_week]}`
},
offsetPage(item: TimelineDay, offset: number) {
const list = this.$refs.seasonsList as HTMLElement[]
const container = list.find(it => it.dataset.date === item.date)
const containerWidth = container.clientWidth
const pageWidth =
Math.trunc(containerWidth / timelineCssVars.seasonItemWidth) *
timelineCssVars.seasonItemWidth
const scrollArea = container.children[0]
scrollArea?.scrollBy(offset * pageWidth, 0)
},
},
})
</script>
@ -326,8 +363,12 @@ export default Vue.extend({
}
}
&-item {
@include h-center(24px);
@include h-center(4px);
@include border-card();
scroll-snap-align: start;
overflow: hidden;
padding: 0 4px 0 16px;
position: relative;
flex-shrink: 0;
height: var(--timeline-item-height);
&.today {
@ -337,6 +378,7 @@ export default Vue.extend({
&-date {
display: grid;
flex-shrink: 0;
margin-right: 8px;
grid-template: 'icon number' 18px 'icon text' 22px / 50px auto;
gap: 8px;
&-icon {
@ -400,7 +442,12 @@ export default Vue.extend({
}
&-seasons-container {
@include h-stretch();
@include scroll-mask-x(18px, var(--home-base-color));
body:not(.dark) & {
@include scroll-mask-x(18px, #fdfdfd);
}
body.dark & {
@include scroll-mask-x(18px, #222);
}
width: 0;
flex: 1 0 0;
margin: 0 2px;
@ -415,7 +462,7 @@ export default Vue.extend({
scroll-snap-type: x mandatory;
}
&-season {
--cover-size: 50px;
--cover-size: 56px;
scroll-snap-align: start;
flex-shrink: 0;
padding: 7px;
@ -429,11 +476,11 @@ export default Vue.extend({
align-content: center;
align-items: center;
width: var(--season-item-width);
height: var(--timeline-item-height);
// height: var(--timeline-item-height);
transition: 0.2s ease-out;
&:not(:last-child) {
padding-right: calc(var(--timeline-item-gap) / 2 + 6px);
border-right: 1px solid #8884;
// border-right: 1px solid #e8e8e8;
}
&-cover {
@ -464,7 +511,7 @@ export default Vue.extend({
@include semi-bold();
@include single-line();
&.today {
@include max-line(2, 1.25);
@include max-line(2, 1.3);
}
}
&-episode {
@ -475,10 +522,11 @@ export default Vue.extend({
}
&-time {
grid-area: time;
@include card(6px);
@include card();
@include round-bar(20);
@include h-center(4px);
box-shadow: none;
padding: 2px 4px;
padding: 2px 6px 2px 4px;
&.published {
border-color: var(--theme-color);
&.follow {
@ -493,6 +541,9 @@ export default Vue.extend({
&.follow:not(.published) &-icon {
color: var(--theme-color);
}
&.today {
transform: translateX(-2px);
}
}
&:hover &-title {
@ -504,8 +555,8 @@ export default Vue.extend({
&.today {
width: var(--season-today-width);
height: var(--timeline-today-height);
--cover-size: 80px;
// height: var(--timeline-today-height);
--cover-size: 84px;
grid-template:
'cover title title' 2fr
'cover episode episode' 1fr
@ -519,6 +570,24 @@ export default Vue.extend({
}
}
}
&-empty-background {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(
45deg,
transparent 25%,
#8882 0,
#8882 50%,
transparent 0,
transparent 75%,
#8882 0,
#8882 100%
);
background-size: 60px 60px;
}
}
}
</style>

View File

@ -171,7 +171,7 @@ export default Vue.extend({
border-radius: 50%;
background-color: #333;
color: #eee;
font-weight: bold;
@include semi-bold();
&.top-three {
background-color: var(--theme-color);
color: var(--foreground-color);

View File

@ -365,9 +365,9 @@ export default Vue.extend({
content: attr(data-number);
@include absolute-center();
@include h-center();
@include semi-bold();
justify-content: center;
top: 0;
font-weight: bold;
width: 28px;
height: 28px;
border-radius: 50%;

View File

@ -7,4 +7,5 @@ export interface FreshLayoutItem extends WithName {
export interface FreshLayoutItemSettings {
linebreak: boolean
order: number
hidden: boolean
}

View File

@ -1,6 +1,7 @@
import { defineOptionsMetadata } from '@/components/define'
import { getNumberValidator } from '@/core/utils'
import type { FreshLayoutItemSettings } from './layouts/fresh-layout-item'
import { areaPrimaryTitleColorEnum } from './init-dropdown-options/area-primary-title-color'
export enum RankListMode {
Default = 'default',
@ -14,22 +15,28 @@ export const freshHomeOptionsMetadata = defineOptionsMetadata({
blackboard: {
linebreak: false,
order: 1,
// as boolean 一下, 不然 TS 会推断为只能 false
hidden: false as boolean,
},
trending: {
linebreak: true,
order: 2,
hidden: false,
},
feeds: {
linebreak: false,
order: 3,
hidden: false,
},
areas: {
linebreak: true,
order: 4,
hidden: false,
},
categories: {
linebreak: false,
order: 5,
hidden: false,
},
} satisfies Record<string, FreshLayoutItemSettings>,
hidden: true,
@ -42,6 +49,11 @@ export const freshHomeOptionsMetadata = defineOptionsMetadata({
displayName: '启用横向滚动',
defaultValue: false,
},
areaPrimaryTitleColor: {
displayName: '栏目主标题颜色',
dropdownEnum: areaPrimaryTitleColorEnum,
defaultValue: areaPrimaryTitleColorEnum.Dark,
},
maxWidth: {
displayName: '最大宽度 (px)',
defaultValue: 1440,

View File

@ -1,6 +1,14 @@
import { defineComponentMetadata } from '@/components/define'
import { playerReady } from '@/core/utils'
import { createPlayerModeChangeEvent } from '@/components/video/player-adaptor'
import { getComponentSettings, addComponentListener } from '@/core/settings'
import { playerReady, getNumberValidator } from '@/core/utils'
import { videoUrls } from '@/core/utils/urls'
/**
* element after
* @param element
* @param after
*/
function moveElementAfter(element: Element, after: Element) {
const parent = element.parentElement
if (!parent) {
@ -10,27 +18,110 @@ function moveElementAfter(element: Element, after: Element) {
parent.insertBefore(element, after.nextSibling)
}
const playerOnTop = async () => {
/**
*
* `#danmukuBox``marginTop`
* B站内联的JS代码
*/
function calcMarginTop() {
const e = window.innerHeight
const t = Math.max((document.body && document.body.clientWidth) || window.innerWidth, 1100)
const n = innerWidth > 1680 ? 411 : 350
const o = parseInt(String((16 * (e - (window.innerWidth > 1690 ? 318 : 308))) / 9))
const r = t - 112 - n
const d = r < o ? r : o
const a = Math.round((d + n) * (9 / 16)) + (innerWidth > 1680 ? 56 : 46)
return `${a}px`
}
/**
*
* UP信息与弹幕列表样式
*/
function sideEffect() {
const author = document.querySelector('.up-panel-container') as HTMLDivElement
const danmuku = document.querySelector('#danmukuBox') as HTMLDivElement
// 缓存当前播放器模式 用于判断是否为宽屏模式
let currentMode = 'normal'
// 监听播放器宽屏模式 调整UP信息与弹幕列表样式
window.addEventListener(
'playerModeChange',
(ev: ReturnType<typeof createPlayerModeChangeEvent>) => {
const { mode } = ev.detail
currentMode = mode
if (mode === 'wide') {
danmuku.style.marginTop = '0px'
author.style.marginTop = calcMarginTop()
} else {
// 恢复原始样式
author.style.marginTop = '0px'
}
},
)
// 监听屏幕尺寸变化
window.addEventListener('resize', () => {
// 播放器宽屏模式
if (currentMode === 'wide') {
author.style.marginTop = calcMarginTop()
}
})
}
/**
*
*
*/
async function playerOnTop({ settings: { options: opt }, metadata }) {
await playerReady()
const title = document.querySelector('#viewbox_report')
const toolbar = document.querySelector('#arc_toolbar_report')
moveElementAfter(title, toolbar)
const player = document.querySelector('#playerWrap') as HTMLDivElement
// 将标题放到播放器和点赞中间位置
moveElementAfter(title, player)
player.style.marginTop = `${opt.marginTop}px`
const author = document.querySelector('#v_upinfo').parentElement
const danmuku = document.querySelector('#danmukuBox')
moveElementAfter(author, danmuku)
// 监听设置变化 实时更新到页面
addComponentListener(`${metadata.name}.marginTop`, (value: number) => {
player.style.marginTop = `${value}px`
})
sideEffect()
}
export const component = defineComponentMetadata({
name: 'playerOnTopNew',
author: {
name: 'RieN7',
link: 'https://github.com/rien7',
},
author: [
{
name: 'RieN7',
link: 'https://github.com/rien7',
},
{
name: 'ZiuChen',
link: 'https://github.com/ZiuChen',
},
{
name: 'LockRim',
link: 'https://github.com/LockRim',
},
],
tags: [componentsTags.style, componentsTags.video],
options: {
marginTop: {
displayName: '顶部留白 (px)',
defaultValue: 20,
validator: getNumberValidator(0, Infinity),
},
},
urlInclude: videoUrls,
displayName: '播放器置顶(新)',
description:
'原来的播放器置顶插件,现在已经不可用了,这是一个新的版本,可以在视频页面中将播放器放在页面最上方.',
entry: playerOnTop,
reload: playerOnTop,
reload: () =>
playerOnTop({ settings: getComponentSettings('playerOnTopNew'), metadata: component }),
})

View File

@ -4,21 +4,8 @@ $prefix: 'simplifyComments-switch';
.bili-comment {
.reply-item {
// body.#{$prefix}-level & {
.user-level,
.sub-user-level {
display: none !important;
}
// }
// body.#{$prefix}-subReplyNewline & {
.sub-user-info {
display: flex !important;
}
// }
// body.#{$prefix}-layout & {
.reply-decorate {
display: none !important;
}
// [布局微调]
// 回复项
.sub-user-info,
.sub-user-name {
line-height: 1.5 !important;
@ -27,65 +14,105 @@ $prefix: 'simplifyComments-switch';
margin-bottom: 4px !important;
max-height: 19.5px !important;
}
.reply-content-container {
display: block !important;
flex-basis: 100% !important;
}
.root-reply:not(.reply-content-container) {
padding: 0 !important;
display: flex !important;
align-items: center !important;
flex-wrap: wrap !important;
}
// 笔记
.note-content {
flex-basis: 100% !important;
margin-bottom: 4px !important;
}
// 笔记图片
.image-exhibition {
flex-basis: 100% !important;
margin-bottom: 8px !important;
}
.root-reply,
.reply-info,
.sub-reply-info {
position: static !important;
}
.reply-info,
.sub-reply-info {
display: inline-flex !important;
}
.reply-time,
.sub-reply-time {
position: absolute !important;
top: 0 !important;
right: 0 !important;
margin-right: 0 !important;
line-height: 1.5 !important;
}
.sub-reply-time {
top: 8px !important;
}
// 右侧操作菜单
.reply-operation-warp,
.sub-reply-operation-warp {
right: -5px !important;
}
.reply-tag-list {
display: inline-flex !important;
margin: 2px 0 0 18px !important;
.reply-tag-item {
padding: 4px 6px !important;
body.#{$prefix}-userLevel & {
.user-level,
.sub-user-level {
display: none !important;
}
}
// }
}
// body.#{$prefix}-replyEditor & {
.reply-box-send {
.send-text {
font-size: 14px !important;
body.#{$prefix}-userPendent & {
.bili-avatar-pendent-dom {
display: none !important;
}
}
body.#{$prefix}-decorateAndTime & {
.reply-decorate {
display: none !important;
}
.reply-time,
.sub-reply-time {
position: absolute !important;
top: 0 !important;
right: 0 !important;
margin-right: 0 !important;
line-height: 1.5 !important;
}
.sub-reply-time {
top: 8px !important;
}
// .sub-user-info {
// display: flex !important;
// }
}
body.#{$prefix}-fansMedal & {
.fan-badge {
display: none !important;
}
}
body.#{$prefix}-subReplyNewLine & {
.root-reply:not(.reply-content-container) {
padding: 0 !important;
display: flex !important;
align-items: center !important;
flex-wrap: wrap !important;
}
.reply-content-container {
display: block !important;
flex-basis: 100% !important;
}
.root-reply,
.reply-info,
.sub-reply-info {
position: static !important;
}
.reply-info,
.sub-reply-info {
display: inline-flex !important;
}
.reply-tag-list {
display: inline-flex !important;
margin: 2px 0 0 18px !important;
.reply-tag-item {
padding: 4px 6px !important;
}
}
}
}
.reply-box-textarea {
line-height: normal !important;
body.#{$prefix}-eventBanner & {
.reply-notice {
display: none !important;
}
}
body.#{$prefix}-replyEditor & {
.reply-box-send {
.send-text {
font-size: 14px !important;
}
}
.reply-box-textarea {
line-height: normal !important;
}
}
// }
}

View File

@ -1,10 +1,13 @@
- 删除热评头像下方的关注按钮
- 删除用户的等级标识
- 删除发送源信息(\`来自安卓客户端\` 这种)
- 删除用户名右边的勋章
- 删除评论区顶部的横幅
- 发送时间移动到右上角
- 位图图标全部换用矢量图标, 高分屏不会模糊
- 投票仅显示链接, 隐藏下面的大框.
去除或优化评论区内的元素, 可配置以下选项 (描述的是勾选时的效果):
> 注: 关注和等级可以通过鼠标停留在头像上, 在弹出的资料卡小窗中查看.
> 配置项仅对新版评论区有效
- `用户等级`: 隐藏用户等级标识. (隐藏后, 可以通过鼠标停留在头像上, 在弹出的资料卡小窗中查看)
- `装扮 & 时间`: 隐藏装扮图片, 并把发送时间移动到装扮的位置.
- `头像框`: 隐藏头像框.
- `回复换行`:
- 楼中楼回复也另起一行显示, 和一级回复保持一致.
- `热评`, `UP 主点赞` 等标记和点赞栏放在同一行.
- `编辑框`: 将提示文本居上, 符合用户实际输入文字的位置, 并将发布按钮的字号略微调小.
- `粉丝勋章`: 隐藏用户的粉丝勋章.
- `活动横幅`: 隐藏评论区顶部的活动横幅

View File

@ -1,8 +1,46 @@
import { defineComponentMetadata } from '@/components/define'
import { wrapSwitchOptions } from '@/components/switch-options'
const name = 'simplifyComments'
export const component = defineComponentMetadata({
export const component = wrapSwitchOptions({
name: 'simplifyOptions',
switchProps: {
checkedIcon: 'mdi-checkbox-marked-circle',
notCheckedIcon: 'mdi-checkbox-blank-circle-outline',
},
dimAt: false,
switches: {
userLevel: {
defaultValue: true,
displayName: '用户等级',
},
decorateAndTime: {
defaultValue: true,
displayName: '装扮 & 时间',
},
userPendent: {
defaultValue: false,
displayName: '头像框',
},
subReplyNewLine: {
defaultValue: true,
displayName: '回复换行',
},
replyEditor: {
defaultValue: true,
displayName: '编辑框',
},
fansMedal: {
defaultValue: false,
displayName: '粉丝勋章',
},
eventBanner: {
defaultValue: true,
displayName: '活动横幅',
},
},
})({
name,
displayName: '简化评论区',
entry: async ({ metadata }) => {
const { addComponentListener } = await import('@/core/settings')
addComponentListener(
@ -23,6 +61,5 @@ export const component = defineComponentMetadata({
style: () => import('./comments-v2.scss'),
},
],
displayName: '简化评论区',
tags: [componentsTags.style],
})

View File

@ -1,4 +1,4 @@
import { newSwitchComponentWrapper, defineSwitchMetadata } from '@/components/switch-options'
import { wrapSwitchOptions, defineSwitchMetadata } from '@/components/switch-options'
import { addComponentListener, getComponentSettings } from '@/core/settings'
import { sq } from '@/core/spin-query'
@ -9,11 +9,6 @@ import { mainSiteUrls } from '@/core/utils/urls'
const switchMetadata = defineSwitchMetadata({
name: 'simplifyOptions',
dimAt: 'checked',
switchProps: {
checkedIcon: 'mdi-eye-off-outline',
notCheckedIcon: 'mdi-eye-outline',
},
switches: {
categories: {
defaultValue: false,
@ -47,7 +42,7 @@ const switchMetadata = defineSwitchMetadata({
})
const console = useScopedConsole('简化首页')
export const component = newSwitchComponentWrapper(switchMetadata)({
export const component = wrapSwitchOptions(switchMetadata)({
name: 'simplifyHome',
displayName: '简化首页',
description: '隐藏原版首页不需要的元素 / 分区.',

View File

@ -1,14 +1,9 @@
import { newSwitchComponentWrapper } from '@/components/switch-options'
import { wrapSwitchOptions } from '@/components/switch-options'
import { styledComponentEntry } from '@/components/styled-component'
import { liveUrls } from '@/core/utils/urls'
export const component = newSwitchComponentWrapper({
export const component = wrapSwitchOptions({
name: 'simplifyOptions',
dimAt: 'checked',
switchProps: {
checkedIcon: 'mdi-eye-off-outline',
notCheckedIcon: 'mdi-eye-outline',
},
switches: {
vip: {
defaultValue: true,
@ -18,6 +13,10 @@ export const component = newSwitchComponentWrapper({
defaultValue: true,
displayName: '入场通知',
},
wealthMedal: {
defaultValue: true,
displayName: '荣耀等级勋章',
},
fansMedal: {
defaultValue: true,
displayName: '粉丝勋章',

View File

@ -3,6 +3,7 @@ $prefix: 'simplifyLiveroom-switch';
.#{$prefix} {
&-vip .vip-icon,
&-wealthMedal .wealth-medal-ctnr,
&-fansMedal .fans-medal-item-ctnr,
&-title .title-label,
&-userLevel .user-level-icon,
@ -24,6 +25,7 @@ $prefix: 'simplifyLiveroom-switch';
&-enterPrompt .chat-history-panel .brush-prompt,
&-enterPrompt .chat-item.convention-msg,
&-pk .chaos-pk,
&-pk .awesome-pk-box,
&-topRank .chat-item.top3-notice,
&-topRank .chat-item .rank-icon {
display: none !important;
@ -250,6 +252,9 @@ $prefix: 'simplifyLiveroom-switch';
border-radius: 0 !important;
background-color: transparent !important;
}
.wealth-medal-ctnr.medal-guard {
display: none !important;
}
.fans-medal-item.medal-guard {
margin-left: 0 !important;
}

View File

@ -0,0 +1 @@
移除高赞弹幕或 UP 主弹幕的特殊样式, 弹幕内容不会移除.

View File

@ -6,6 +6,38 @@ import {
import { ComponentEntry } from '@/components/types'
import { playerUrls } from '@/core/utils/urls'
import { addComponentListener } from '@/core/settings'
import { watchLocalStorage } from '@/core/local-storage'
enum DanmakuBorderStyle {
Heavy,
Stroke,
Shadow,
}
const danmakuTextShadowMap = {
[DanmakuBorderStyle.Heavy]:
'1px 0 1px #000000,0 1px 1px #000000,0 -1px 1px #000000,-1px 0 1px #000000',
[DanmakuBorderStyle.Stroke]: '0px 0px 1px #000000,0 0 1px #000000,0 0 1px #000000',
[DanmakuBorderStyle.Shadow]: '1px 1px 2px #000000,0 0 1px #000000',
}
const watchDanmakuBorderSettings = () => {
const localStorageKey = 'bpx_player_profile'
const updateDanmakuTextShadow = (playerSettings: any) => {
const danmakuBorderStyle: DanmakuBorderStyle = lodash.get(
playerSettings,
'dmSetting.fontborder',
)
document.documentElement.style.setProperty(
'--danmaku-text-shadow',
danmakuTextShadowMap[danmakuBorderStyle],
)
}
updateDanmakuTextShadow(JSON.parse(localStorage.getItem(localStorageKey)))
watchLocalStorage((key, value) => {
if (key === localStorageKey) {
updateDanmakuTextShadow(JSON.parse(value))
}
})
}
const options = defineOptionsMetadata({
highlight: {
@ -16,6 +48,10 @@ const options = defineOptionsMetadata({
displayName: '禁用UP主弹幕',
defaultValue: true,
},
vip: {
displayName: '禁用大会员弹幕',
defaultValue: true,
},
})
type Options = OptionsOfMetadata<typeof options>
@ -30,6 +66,7 @@ const entry: ComponentEntry<Options> = ({ metadata, settings }) => {
true,
)
})
watchDanmakuBorderSettings()
}
const name = 'disableSpecialDanmaku'
@ -45,8 +82,5 @@ export const component = defineComponentMetadata({
},
],
urlInclude: playerUrls,
description: {
'zh-CN': '移除高赞弹幕或 UP 主弹幕的特殊样式, 弹幕内容不会移除.',
},
options,
})

View File

@ -28,3 +28,12 @@ body.disable-up-danmaku-style {
}
}
}
body.disable-vip-danmaku-style {
.bili-dm-vip {
display: contents !important;
text-shadow: inherit !important;
}
.bili-dm:not([style*='--textShadow']) {
--textShadow: var(--danmaku-text-shadow);
}
}

View File

@ -0,0 +1,21 @@
/* 保持相簿元素高度 */
.album-card {
margin-bottom: 21px;
}
/* 上移原互动数据 */
.album-card .album-card__info {
margin-top: -5px;
}
/* 相簿发布时间样式 */
.album-pub-time {
color: #99a2aa;
body.dark & {
color: #aaa;
}
position: absolute;
margin-top: -5px;
}

View File

@ -0,0 +1,63 @@
import { defineComponentMetadata } from '@/components/define'
import { attributesSubtree, childList } from '@/core/observer'
import { select } from '@/core/spin-query'
let albumList: Element
let oldObserver: MutationObserver
const observeAlbum = async (node: Element) => {
// 相簿更新时会复用原有元素,必须得监听子元素属性变化
const [observer] = attributesSubtree(node, () => {
// 为所有相簿添加发布时间显示
albumList.querySelectorAll('.album-card').forEach(e => {
const pubTimeElement = e.querySelector('.album-pub-time')
// eslint-disable-next-line no-underscore-dangle
const time = (e as any).__vue__.item.ctime
if (time !== undefined) {
if (pubTimeElement !== null) {
pubTimeElement.innerHTML = new Date(time * 1000).toLocaleString()
} else {
e.insertAdjacentHTML(
'beforeend',
`<div class="album-pub-time">${new Date(time * 1000).toLocaleString()}</div>`,
)
}
}
})
})
// 移除旧的MutationObserver
oldObserver?.disconnect()
oldObserver = observer
const { addImportantStyle } = await import('@/core/style')
const { default: style } = await import('./album-time.scss')
addImportantStyle(style, 'album-pub-time-style')
}
const entry = async () => {
const spaceContainer = await select('.s-space')
childList(spaceContainer, async () => {
if (!document.URL.match(/^https:\/\/space\.bilibili\.com\/\d+\/album/)) {
return
}
albumList = await select('.album-list__content')
observeAlbum(albumList)
})
}
export const component = defineComponentMetadata({
name: 'albumPubTimeShow',
author: {
name: 'Light_Quanta',
link: 'https://github.com/LightQuanta',
},
displayName: '相簿发布时间显示',
tags: [componentsTags.utils],
urlInclude: [/^https:\/\/space\.bilibili\.com/],
entry,
description: {
'zh-CN': '在相簿界面显示相簿的发布时间',
},
})

View File

@ -0,0 +1,207 @@
<template>
<VPopup
ref="popup"
v-model="isOpen"
class="like-black-list"
fixed
:lazy="false"
:trigger-element="triggerElement"
>
<div class="black-list-header">
<VIcon class="title-icon" icon="mdi-sort" :size="24"></VIcon>
<div class="title">添加黑名单</div>
<div class="close" @click="isOpen = false">
<VIcon icon="close" :size="18"></VIcon>
</div>
</div>
<div class="black-list-content">
<div class="black-list-section">
<div class="black-list-section-title">添加到黑名单</div>
<div class="black-list-section-input">
<TextBox :text="name" @change="changeName" />
<VButton @click="add">添加</VButton>
</div>
</div>
<div class="black-list-section">
<div class="black-list-section-title">黑名单列表</div>
<div class="black-list-section-description">点击×图标可以删除名单.</div>
<VLoading v-if="!isLoaded" />
<div
v-show="isLoaded"
ref="black-listSortList"
class="black-list-section-content black-list-sort-list"
>
<div v-for="item of list" :key="item" class="black-list-sort-item" :data-name="item">
<div class="item-name">
{{ item }}
</div>
<div class="toggle-visible">
<VIcon :size="18" icon="close" @click="toggleVisible(item)"></VIcon>
</div>
</div>
</div>
</div>
</div>
</VPopup>
</template>
<script lang="ts">
import { VPopup, TextBox, VIcon, VButton } from '@/ui'
export default Vue.extend({
components: {
VPopup,
TextBox,
VIcon,
VButton,
},
props: {
triggerElement: {
type: HTMLElement,
default: null,
},
list: {
type: Array,
default: null,
},
titleName: {
type: String,
default: '',
},
},
data() {
return {
isOpen: false,
isLoaded: false,
name: '',
}
},
async mounted() {
this.isLoaded = true
},
methods: {
toggle() {
this.$refs.popup.toggle()
},
changeName(val: string) {
this.name = val
},
add() {
if (this.name.length && !this.list.includes(this.name)) {
// eslint-disable-next-line vue/no-mutating-props
this.list.push(this.name)
}
this.name = ''
},
toggleVisible(item: string) {
// eslint-disable-next-line vue/no-mutating-props
this.list.splice(this.list.indexOf(item), 1)
},
},
})
</script>
<style lang="scss">
@import 'common';
.like-black-list {
@include popup();
width: 400px;
font-size: 14px;
padding: 12px 12px 12px 18px;
left: 50%;
top: 50%;
transform: translateX(-50%) translateY(-50%) scale(0.95);
transition: 0.2s ease-out;
z-index: 100002;
&.open {
transform: translateX(-50%) translateY(-50%) scale(1);
}
.black-list-header {
@include h-center();
justify-content: space-between;
.title {
margin-left: 6px;
font-size: 18px;
@include semi-bold();
}
.grow {
flex: 1;
}
.close {
padding: 6px;
cursor: pointer;
transition: 0.2s ease-out;
&:hover {
color: var(--theme-color);
}
}
}
.black-list-content {
.black-list-section {
margin-top: 12px;
> :not(:last-child) {
margin-bottom: 6px;
}
&-title {
font-size: 14px;
}
&-input {
display: flex;
div {
margin: 0 10px;
}
}
&-description {
font-size: 12px;
opacity: 0.6;
line-height: 1.5;
}
&-content {
@include h-center();
flex-wrap: wrap;
.be-slider {
margin: 0 4px;
flex: 1;
}
.padding-value {
margin-left: 12px;
width: 50px;
text-align: end;
}
.black-list-sort-item {
@include card();
@include h-center();
transition: none;
white-space: nowrap;
padding: 6px;
padding-left: 8px;
margin: 0 4px 4px 0;
cursor: move;
&:hover {
border-color: var(--theme-color);
}
&.black-list-hidden {
opacity: 0.5;
}
&.sortable-ghost {
opacity: 0;
}
&.sortable-chosen {
box-shadow: 0 4px 16px 0 rgb(0 0 0 / 16%);
transform: scale(1.05);
}
&.sortable-drag {
opacity: 1;
}
&.sortable-drag.black-list-hidden {
opacity: 0.5;
}
.toggle-visible {
margin-left: 6px;
cursor: pointer;
}
}
}
}
}
}
</style>

View File

@ -1 +1,4 @@
进入视频 / 查看动态时, 自动点赞.
进入视频 / 查看动态时, 自动点赞\
无法触发未加载动态的点赞,当启用手动对动态点赞后可手动触发,启用后不会触发动态自动点赞\
安装快捷键扩展后可以点击【l】键或【L】键触发\
还可以添加动态点赞的黑名单\

View File

@ -1,8 +1,11 @@
import { defineComponentMetadata } from '@/components/define'
import { forEachFeedsCard } from '@/components/feeds/api'
import { select } from '@/core/spin-query'
import { matchUrlPattern, playerReady } from '@/core/utils'
import { feedsUrls, videoAndBangumiUrls } from '@/core/utils/urls'
import { KeyBindingAction } from '../keymap/bindings'
import { forEachFeedsCard } from '@/components/feeds/api'
import { BlackListDataKey, loadlikeButton } from './vm'
import { getData, registerData } from '@/plugins/data'
const feedsLikeQueue = [] as HTMLElement[]
export const component = defineComponentMetadata({
@ -10,6 +13,10 @@ export const component = defineComponentMetadata({
displayName: '自动点赞',
tags: [componentsTags.utils, componentsTags.feeds, componentsTags.video],
urlInclude: [...videoAndBangumiUrls, ...feedsUrls],
author: {
name: 'CrazyboyQCD',
link: 'https://github.com/CrazyboyQCD',
},
options: {
video: {
defaultValue: true,
@ -19,35 +26,72 @@ export const component = defineComponentMetadata({
defaultValue: true,
displayName: '对动态点赞',
},
manualFeed: {
defaultValue: true,
displayName: '手动对动态点赞',
},
users: {
displayName: '黑名单',
defaultValue: [] as string[],
hidden: true,
},
},
extraOptions: () => import('./settings.vue').then(m => m.default),
entry: async ({ settings: { options } }) => {
if (options.video && videoAndBangumiUrls.some(url => matchUrlPattern(url))) {
await playerReady()
const likeButton = (await select(
'.video-toolbar .like, .tool-bar .like-info, .video-toolbar-v1 .like',
)) as HTMLSpanElement
const likeButton = (await select('.video-like')) as HTMLSpanElement
if (!likeButton || likeButton.classList.contains('on')) {
return
}
likeButton.click()
}
if (options.feed && feedsUrls.some(url => matchUrlPattern(url))) {
window.setInterval(() => {
if (feedsLikeQueue.length === 0) {
return
}
const button = feedsLikeQueue.shift()
button?.click()
}, 1000)
forEachFeedsCard({
added: card => {
const likeButton = dq(card.element, '.bili-dyn-action.like') as HTMLElement
if (!likeButton || likeButton.classList.contains('active')) {
const blackListData = {
users: options.users,
}
registerData(BlackListDataKey, blackListData)
if (options.manualFeed) {
loadlikeButton()
} else {
window.setInterval(() => {
if (feedsLikeQueue.length === 0) {
return
}
feedsLikeQueue.push(likeButton)
},
})
const button = feedsLikeQueue.shift()
button?.click()
}, 1000)
const blackList = getData(BlackListDataKey)
forEachFeedsCard({
added: card => {
if (blackList.includes(card.username)) {
return
}
const likeButtons = dq(card.element, '.bili-dyn-action.like') as HTMLElement
if (!likeButtons || likeButtons.classList.contains('active')) {
return
}
feedsLikeQueue.push(likeButtons)
},
})
}
}
},
plugin: {
displayName: '点赞 - 手动',
setup: ({ addData }) => {
addData('keymap.actions', (actions: Record<string, KeyBindingAction>) => {
actions.manuallike = {
displayName: '手动触发对动态点赞',
run: context => {
const { clickElement } = context
return clickElement('.manual-like', context)
},
}
})
addData('keymap.presets', (presetBase: Record<string, string>) => {
presetBase.manuallike = 'l L'
})
},
},
})

View File

@ -0,0 +1,89 @@
<template>
<VButton round class="manual-like" @click="like">
<div id="text" :class="{ like: isLike }">正在点赞: {{ curLikeCnt }} / {{ totalLikeCnt }}</div>
<VIcon v-show="!isLike" colored icon="like" :size="20" />
</VButton>
</template>
<script lang="ts">
import { VButton, VIcon } from '@/ui'
export default Vue.extend({
components: {
VButton,
VIcon,
},
props: {
list: {
type: Array,
default: null,
},
},
data() {
return {
isClick: false,
isLike: false,
totalLikeCnt: 0,
curLikeCnt: 0,
feedsLikeQueue: [] as HTMLElement[],
}
},
methods: {
like() {
if (this.isClick) {
return
}
this.isClick = true
// forEachFeedsCarddqa
const likeButtons = (
Array.from(document.getElementsByClassName('bili-dyn-title__text')) as HTMLElement[]
).reduce((buttons, e) => {
if (this.list.includes(e.textContent.trim())) {
return buttons
}
const likeButton = e.closest('.bili-dyn-item__main').querySelector('.bili-dyn-action.like')
if (likeButton && !likeButton.classList.contains('active')) {
buttons.push(likeButton as HTMLElement)
}
return buttons
}, [] as HTMLElement[])
this.feedsLikeQueue.push(...likeButtons)
this.totalLikeCnt = this.feedsLikeQueue.length
this.curLikeCnt = 0
this.isLike = true
const t = window.setInterval(() => {
if (this.feedsLikeQueue.length === 0) {
this.isLike = false
this.isClick = false
clearInterval(t)
return
}
const button = this.feedsLikeQueue.shift()
button?.click()
this.curLikeCnt++
}, 1200)
},
},
})
</script>
<style lang="scss">
.manual-like {
left: 5px;
z-index: 1001;
height: 30px;
position: fixed;
* {
color: rgb(251, 114, 153);
}
#text {
overflow: hidden;
white-space: nowrap;
max-width: 0px;
transition: max-width 0.4s linear !important;
&.like {
max-width: 150px;
}
}
}
</style>

View File

@ -0,0 +1,50 @@
<template>
<div>
<div class="custom-black-list-extra-options">
<VButton ref="button" @mouseover="setBlackListProps" @click="toggleBlackList">
黑名单
<VIcon icon="right-arrow" :size="16" />
</VButton>
</div>
</div>
</template>
<script lang="ts">
import { VIcon, VButton } from '@/ui'
import { loadBlackList, setBlackListProps, toggleBlackList } from './vm'
export default Vue.extend({
components: {
VIcon,
VButton,
},
data() {
return {
isFirstLoad: false,
}
},
methods: {
toggle() {
this.$refs.popup.toggle()
},
async setBlackListProps() {
if (this.isFirstLoad) {
return
}
this.isFirstLoad = await loadBlackList()
if (this.isFirstLoad) {
const triggerButton = this.$refs.button.$el as HTMLElement
setBlackListProps(triggerButton)
}
},
toggleBlackList,
},
})
</script>
<style lang="scss">
.custom-black-list-extra-options {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20px;
}
</style>

View File

@ -0,0 +1,48 @@
import { select } from '@/core/spin-query'
import { mountVueComponent } from '@/core/utils'
import { getData } from '@/plugins/data'
export const BlackListDataKey = 'like-black-List.data'
type SettingsVmType = Vue & {
toggle: () => void
triggerElement: HTMLElement
list: string[]
titleName: string
}
let blacklistVM: SettingsVmType = null
export const setBlackListProps = (element: HTMLElement) => {
if (!blacklistVM) {
return
}
blacklistVM.triggerElement = element
const blackList = getData(BlackListDataKey)
blacklistVM.list = blackList[0].users
blacklistVM.titleName = '黑名单'
}
export const loadlikeButton = async () => {
const LikeButton = await import('./like.vue').then(m => m.default)
const blackList = getData(BlackListDataKey)
const likebuttonVM: Vue & { list: string[] } = mountVueComponent(LikeButton)
likebuttonVM.list = blackList[0].users
const bg = (await select('#app')) as HTMLElement
bg.insertAdjacentElement('afterbegin', likebuttonVM.$el)
}
export const loadBlackList = async () => {
if (blacklistVM) {
return false
}
const blackList = await import('./blackList.vue').then(m => m.default)
blacklistVM = mountVueComponent(blackList)
document.body.insertAdjacentElement('beforeend', blacklistVM.$el)
return true
}
export const toggleBlackList = async () => {
if (!blacklistVM) {
await loadBlackList()
}
blacklistVM?.toggle()
}

View File

@ -132,7 +132,7 @@ export default Vue.extend({
.title {
margin-left: 6px;
font-size: 18px;
font-weight: bold;
@include semi-bold();
}
.grow {
flex: 1;

View File

@ -0,0 +1,171 @@
/* eslint-disable no-underscore-dangle */
import { defineComponentMetadata } from '@/components/define'
import { CommentItem, CommentReplyItem } from '@/components/utils/comment-apis'
// 新版评论区IP属地获取
const getIpLocation = (element: HTMLElement) => {
const props = (element as any).__vueParentComponent?.props
const reply = props?.reply ?? props?.subReply
return reply?.reply_control?.location ?? undefined
}
let version = 2
let bbComment: any
// 带IP属地显示的评论创建
const createListCon = function listCon(item, i, pos) {
const blCon = this._parentBlacklistDom(item, i, pos)
const con = [
`<div class="con ${pos === i ? 'no-border' : ''}">`,
`<div class="user">${this._createNickNameDom(item)}`,
this._createLevelLink(item),
this._identity(item.mid, item.assist, item.member.fans_detail),
`${this._createNameplate(item.member.nameplate) + this._createUserSailing(item)}</div>`,
this._createMsgContent(item),
this._createPerfectReply(item),
'<div class="info">',
this._createPlatformDom(item.content.plat),
'<span class="time-location">',
'<span class="reply-time">'.concat(this._formateTime(item.ctime), '</span>'),
item?.reply_control?.location
? `<span class="reply-location">${item?.reply_control?.location || ''}</span>`
: '',
'</span>',
item.lottery_id
? ''
: `<span class="like ${item.action === 1 ? 'liked' : ''}"><i></i><span>${
item.like ? item.like : ''
}</span></span>`,
item.lottery_id ? '' : `<span class="hate ${item.action === 2 ? 'hated' : ''}"><i></i></span>`,
item.lottery_id ? '' : this._createReplyBtn(item.rcount),
item.lottery_id && item.mid !== bbComment.userStatus.mid
? ''
: `<div class="operation more-operation"><div class="spot"></div><div class="opera-list"><ul>${
this._canSetTop(item)
? `<li class="set-top">${item.isUpTop ? '取消置顶' : '设为置顶'}</li>`
: ''
}${this._canBlackList(item.mid) ? '<li class="blacklist">加入黑名单</li>' : ''}${
this._canReport(item.mid) ? '<li class="report">举报</li>' : ''
}${
this._canDel(item.mid) && !item.isTop
? `<li class="del" data-mid="${item.mid}">删除</li>`
: ''
}</ul></div></div>`,
this._createLotteryContent(item.content),
this._createVoteContent(item.content),
this._createTags(item),
'</div>',
'<div class="reply-box">',
this._createSubReplyList(
item.replies,
item.rcount,
false,
item.rpid,
item.folder && item.folder.has_folded,
item.reply_control,
),
'</div>',
'<div class="paging-box">',
'</div>',
'</div>',
].join('')
return item.state === bbComment.blacklistCode ? blCon : con
}
// 带IP属地显示的回复创建
const createSubReplyItem = function subReply(item, i) {
const dom = [
`<div class="reply-item reply-wrap" data-id="${item.rpid}" data-index="${i}">`,
this._createSubReplyUserFace(item),
'<div class="reply-con">',
'<div class="user">',
this._createNickNameDom(item),
this._createLevelLink(item),
this._identity(item.mid),
this._createSubMsgContent(item),
'</div>',
'</div>',
'<div class="info">',
'<span class="time-location">',
'<span class="reply-time">'.concat(this._formateTime(item.ctime), '</span>'),
item?.reply_control?.location
? `<span class="reply-location">${item?.reply_control?.location || ''}</span>`
: '',
'</span>',
`<span class="like ${item.action === 1 ? 'liked' : ''}"><i></i><span>${
item.like ? item.like : ''
}</span></span>`,
`<span class="hate ${item.action === 2 ? 'hated' : ''}"><i></i></span>`,
'<span class="reply btn-hover">回复</span>',
`<div class="operation btn-hover btn-hide-re"><div class="spot"></div><div class="opera-list"><ul>${
this._canBlackList(item.mid) ? '<li class="blacklist">加入黑名单</li>' : ''
}${this._canReport(item.mid) ? '<li class="report">举报</li>' : ''}${
this._canDel(item.mid) ? `<li class="del" data-mid="${item.mid}">删除</li>` : ''
}</ul></div></div>`,
'</div>',
'</div>',
].join('')
return dom
}
const observer = new MutationObserver(mutations => {
mutations.forEach(() => {
// 监听旧版评论区全局变量
if (typeof unsafeWindow.bbComment !== 'undefined') {
// 评论创建函数替换
bbComment = unsafeWindow.bbComment
bbComment.prototype._createListCon = createListCon
bbComment.prototype._createSubReplyItem = createSubReplyItem
version = 1
observer.disconnect()
}
})
})
observer.observe(document.head, { childList: true })
const processItems = (items: CommentReplyItem[]) => {
items.forEach(item => {
const location = getIpLocation(item.element)
if (location !== undefined) {
const replyTime =
item.element.querySelector('.reply-info>.reply-time') ??
item.element.querySelector('.sub-reply-info>.sub-reply-time')
if (replyTime.childElementCount === 0) {
// 避免在评论更新的情况下重复添加
const replyLocation = document.createElement('span')
replyLocation.style.marginLeft = '5px'
replyLocation.innerText = location
replyTime.appendChild(replyLocation)
}
}
})
}
const entry = async () => {
const { forEachCommentItem } = await import('@/components/utils/comment-apis')
const addIpLocation = (comment: CommentItem) => {
if (version === 2) {
processItems([comment, ...comment.replies])
comment.addEventListener('repliesUpdate', replies => processItems(replies.detail))
}
}
forEachCommentItem({
added: addIpLocation,
})
}
export const component = defineComponentMetadata({
name: 'ipShow',
author: {
name: 'Light_Quanta',
link: 'https://github.com/LightQuanta',
},
displayName: '评论区IP属地显示',
tags: [componentsTags.utils],
entry,
description: {
'zh-CN': '在评论区显示评论的IP属地信息',
},
})

View File

@ -129,8 +129,8 @@ export default Vue.extend({
&-title {
flex: 1 0 auto;
@include h-center();
@include semi-bold();
font-size: 18px;
font-weight: bold;
.be-icon {
margin-right: 6px;
}

View File

@ -20,7 +20,6 @@
.rank-container .cm-module,
body:not(.preserve-event-banner) .activity-m,
body:not(.preserve-event-banner) .activity-m-v1,
body:not(.preserve-reply-notice) .reply-notice,
body.remove-game-match-module .bili-wrapper > .home-match,
body.remove-game-match-module #reportFirst3,
.home-content .ad-panel,
@ -64,7 +63,7 @@ body:not(.preserve-feed-goods) .dyn-goods {
color: #888;
background-color: #8882;
font-size: 24pt;
font-weight: bold;
@include semi-bold();
align-items: center;
justify-content: space-evenly;
}
@ -81,11 +80,11 @@ body.dark .blocked-ads.new {
pointer-events: none !important;
opacity: 0 !important;
}
body:not(.preserve-feed-goods) .bili-dyn-content__orig__additional:has(.dyn-goods) {
body:not(.preserve-feed-goods) .bili-dyn-content__orig__additional:has(.dyn-goods) {
margin: 0 !important;
}
.bili-video-card__wrap {
a[href*="cm.bilibili.com"] {
a[href*='cm.bilibili.com'] {
pointer-events: none !important;
cursor: default !important;
@ -96,7 +95,7 @@ body:not(.preserve-feed-goods) .bili-dyn-content__orig__additional:has(.dyn-good
display: none !important;
}
body.promotion-show-placeholder &::before {
content: "🚫已屏蔽广告";
content: '🚫已屏蔽广告';
border-radius: 6px;
@include h-center();
justify-content: center;

View File

@ -0,0 +1,66 @@
import { defineComponentMetadata } from '@/components/define'
import { childList } from '@/core/observer'
import { select } from '@/core/spin-query'
let relationList: Element
let oldObserver: MutationObserver
const observeFans = async (node: Element) => {
// 监听关注列表元素变化
const [observer] = childList(node, () => {
// 读取Vue属性里的关注列表
// eslint-disable-next-line no-underscore-dangle
const subscribeTime = (
relationList.parentElement.parentElement.parentElement.parentElement as any
).__vue__.relationList.map(l => l.mtime)
// 为所有子元素添加关注时间显示
relationList.querySelectorAll('.list-item>.content').forEach((e, index) => {
// 防止重复添加元素
if (e.querySelector('.subscribe-time-fix') === null) {
const time = subscribeTime[index]
if (time !== undefined) {
e.querySelector('p').insertAdjacentHTML(
'afterend',
`<div class="desc subscribe-time-fix">关注时间:${new Date(
time * 1000,
).toLocaleString()}</div>`,
)
}
}
})
})
// 移除旧的MutationObserver
oldObserver?.disconnect()
oldObserver = observer
const { addImportantStyle } = await import('@/core/style')
const { default: style } = await import('./subscribe-time.scss')
addImportantStyle(style, 'subscribe-time-style')
}
const entry = async () => {
const spaceContainer = await select('.s-space')
childList(spaceContainer, async () => {
if (!document.URL.match(/^https:\/\/space\.bilibili\.com\/\d+\/fans/)) {
return
}
relationList = await select('.relation-list')
observeFans(relationList)
})
}
export const component = defineComponentMetadata({
name: 'subscribeTimeShow',
author: {
name: 'Light_Quanta',
link: 'https://github.com/LightQuanta',
},
displayName: '关注时间显示',
tags: [componentsTags.utils],
urlInclude: [/^https:\/\/space\.bilibili\.com/],
entry,
description: {
'zh-CN': '在粉丝/关注列表显示关注的具体时间',
},
})

View File

@ -0,0 +1,14 @@
/* 上移原名称 */
#page-follows .list-item .content .title {
margin-top: -9px;
}
/* 上移原简介/官方认证 */
#page-follows .list-item .content p {
margin-top: -1px;
}
/* 修复关注时间元素的高度带来的布局影响 */
.subscribe-time-fix {
margin-bottom: -10px;
}

View File

@ -1 +1 @@
自动删除网址中的多余跟踪参数. 请注意这会导致浏览器历史记录出现重复的标题 (分别是转换前后的网址), 并可能导致后退要多退几次.
自动删除网址中的多余跟踪参数。请注意这会导致浏览器历史记录出现重复的标题(分别是转换前后的网址),并可能导致后退要多退几次。

View File

@ -49,6 +49,7 @@ const entry = async () => {
'vd_source',
'is_story_h5',
'buvid',
'plat_id',
'goFrom',
'jumpLinkType',
'hasBack',
@ -56,6 +57,10 @@ const entry = async () => {
'msource',
'live_from',
'plat_id',
'extra_jump_from',
'subarea_rank',
'popular_rank',
'launch_id',
]
const [blockParams] = registerAndGetData('urlParamsClean.params', builtInBlockParams)
const builtInSiteSpecifiedParams = [

Some files were not shown because too many files have changed in this diff Show More