mirror of
https://github.com/the1812/Bilibili-Evolved.git
synced 2025-11-04 21:22:45 +08:00
Merge branch 'preview-features' into preview
This commit is contained in:
commit
1b524b518a
56
CHANGELOG.md
56
CHANGELOG.md
@ -1,7 +1,61 @@
|
||||
# 更新日志
|
||||
|
||||
## v2.7.0-preview
|
||||
`2023-03-26`
|
||||
|
||||
包含 v2.7.0 的所有更新内容.
|
||||
|
||||
- 新增功能 `组件二等分`, 在出现问题时可帮助定位是哪个组件导致的问题. (#3829, PR #3965 by [JLoeve](https://github.com/LonelySteve))
|
||||
- 优化了 `loadFeatureCode` 的代码. (PR #4013 by [timongh](https://github.com/timongh))
|
||||
- 搜索栏在搜索 ID 时的优化, 新增功能可以通过安装对应的插件来使用: (#677)
|
||||
- 搜索 av / BV 号时, 支持显示视频标题
|
||||
- 支持搜索专栏 cv 号 / 文集 rl 号
|
||||
- 支持搜索音频 au 号 / 播放列表 am 号
|
||||
- 支持搜索番剧 ss / ep 号, 或番剧详情 md 号
|
||||
- `自定义顶栏` 的收藏和稍后再看支持显示数量. (#4069)
|
||||
- 优化了 `网址参数清理` 的逻辑, 通过 Hook History API 来减少出现重复的历史记录. (#4039)
|
||||
- 新增功能 `隐藏热搜`. (#3744)
|
||||
> 隐藏搜索栏和搜索页面中的 `bilibili 热搜`.
|
||||
- 新增功能 `隐藏视频分享`. (#3663)
|
||||
> 隐藏视频和番剧播放器下方的分享按钮.
|
||||
- `自定义顶栏` 的订阅支持过滤 在看 / 看过 / 想看. (#3217)
|
||||
- 新增插件 `快捷键扩展 - 开关灯`. (#3587)
|
||||
> 在快捷键的动作列表里添加一个 "开关灯".
|
||||
|
||||
## v2.7.0
|
||||
`2023-03-26`
|
||||
|
||||
增加了 GitHub Projects 看板: https://github.com/users/the1812/projects/1/views/3, 上面会列出当前和未来计划的功能, 可供参考.
|
||||
|
||||
✨新增
|
||||
- 快捷键扩展的几个插件自带了 YouTube / PotPlayer 等预设的默认按键. (#3971)
|
||||
- `简化直播间` 在开启隐藏付费礼物时, 将直接隐藏左下角的打榜入口 (以前是领银瓜子所以没算进付费礼物). (#4067)
|
||||
- `专栏文字选择` 更名为 `专栏复制优化`, 现在专栏的文字默认就可以直接选择, 本功能可以用于去除复制后带上的多余文本. (#4065)
|
||||
- 删除功能 `直播录像下载`, 现在已经没有直播录像的页面了. (#4061)
|
||||
- 在检测到[解除番剧区域限制](https://greasyfork.org/zh-CN/scripts/25718-%E8%A7%A3%E9%99%A4b%E7%AB%99%E5%8C%BA%E5%9F%9F%E9%99%90%E5%88%B6)的脚本在运行时, 本脚本将停止运行以避免兼容性问题. (#2704)
|
||||
- `选集区域优化` 在多个分组时支持仅展开当前分组. (#3899)
|
||||
- `播放时自动关灯` 支持星光动画. (PR #4077 by [z503722728](https://github.com/z503722728))
|
||||
|
||||
🐛修复
|
||||
- 修复 `直播信息扩充` 失效的问题. (#4034, PR #4043 by [deepdarkssj](https://github.com/deepdarkssj))
|
||||
- 修复 `简化直播间` 在开启隐藏付费礼物时, 右下布局错乱. (PR #4089 by [Pencil](https://github.com/pencilqaq))
|
||||
- 修复动态相关功能对新版专栏卡片不生效. (#3994)
|
||||
- 修复搜索界面的广告链接仍然可点击. (#3969)
|
||||
- 修复 `图片批量导出` 在动态失效了. (#4038)
|
||||
- 修复 `选集区域优化` 仅开启 `展开选集标题` 时视频标题无法显示完全. (#3909)
|
||||
- 修复 `查看封面` 在复制链接后没有显示成功图标. (#4002)
|
||||
- 修复番剧页面的部分夜间模式. (PR #4077 by [z503722728](https://github.com/z503722728))
|
||||
|
||||
☕开发者相关
|
||||
- 修复 `IframePopup.vue` 的类型定义. (PR #4014 by [timongh](https://github.com/timongh))
|
||||
- 修复 `UserItem.vue` 初始化 Toast 的逻辑问题. (PR #4056 by [timongh](https://github.com/timongh))
|
||||
- 优化了 `switch-options.ts` 的注释. (PR #4021 by [timongh](https://github.com/timongh))
|
||||
- 清理了项目中 Vue 文件里对未定义字段的引用. (#3992)
|
||||
- 导出了 Dialog API (`@/core/dialog`), 允许其他组件使用.
|
||||
- 调整了 CONTRIBUTING 中的本地开发流程描述. (PR #4077 by [z503722728](https://github.com/z503722728))
|
||||
|
||||
## v2.6.3-preview
|
||||
`2023-02-09`
|
||||
`2023-02-20`
|
||||
|
||||
包含 v2.6.3 的所有更新内容.
|
||||
|
||||
|
||||
@ -1,5 +1,10 @@
|
||||
# 代码贡献指南
|
||||
|
||||
Bilibili Evolved 是一个基于 Web 前端技术构建的油猴脚本, 贡献代码前应具备的相关能力有:
|
||||
- 了解最基础的油猴脚本的开发流程, 包括理解 [UserScript Header 和 GM API](https://www.tampermonkey.net/documentation.php?locale=en).
|
||||
- 熟悉 Web 前端技术, 编写逻辑时使用 [TypeScript](https://www.typescriptlang.org/), 编写样式时使用 [Scss](https://sass-lang.com/), 创建 UI 时使用 [Vue 2](https://v2.cn.vuejs.org/).
|
||||
- 新增的代码能够通过 [ESLint](https://eslint.org/) 和 [TypeScript](https://www.typescriptlang.org/) 检查.
|
||||
|
||||
## 搭建开发环境
|
||||
|
||||
- 需要安装 [Node.js](https://nodejs.org/en/download/) (>= 14.0), [Visual Studio Code](https://code.visualstudio.com/) 和 [pnpm](https://pnpm.io/installation).
|
||||
@ -13,6 +18,10 @@ cd registry
|
||||
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 和其他文件.
|
||||
|
||||
### 本体
|
||||
需要说明的是, 脚本本体和功能是分开的两个项目. 本体的代码在 `src/` 下, 开发时产生 `dist/bilibili-evolved.dev.user.js` 文件. 功能的代码位于 `registry/` 下, 开发时在 `registry/dist/` 下产生文件.
|
||||
> 如果不使用 Visual Studio Code, 则需要根据 `.vscode/tasks.json` 中各个任务定义的命令手动在终端执行. (npm scripts 仅用于 CI)
|
||||
@ -20,10 +29,11 @@ pnpm install
|
||||
配置本地调试环境:
|
||||
|
||||
**如果使用的是基于 Chromium 的浏览器**
|
||||
1. Chrome 插件管理 `chrome://extensions/` > Tampermonkey > 详细信息
|
||||
2. 打开 `允许访问文件网址`
|
||||
3. 新建脚本
|
||||
4. 粘贴内容:
|
||||
1. VS Code 中运行 `启动开发服务 dev-server` 任务, 会在项目的 `dist/` 文件夹下生成一个开发用的脚本 `dist/bilibili-evolved.dev.user.js`.
|
||||
2. Chrome 插件管理 `chrome://extensions/` > Tampermonkey > 详细信息
|
||||
3. 打开 `允许访问文件网址`
|
||||
4. 新建脚本
|
||||
5. 粘贴内容:
|
||||
```js
|
||||
// ==UserScript==
|
||||
// @name Bilibili Evolved (Local)
|
||||
@ -63,19 +73,20 @@ pnpm install
|
||||
// @connect localhost
|
||||
// @connect *
|
||||
// @require https://raw.githubusercontent.com/lodash/lodash/4.17.15/dist/lodash.min.js
|
||||
// @require file://{{ bilibili-evolved.dev.user.js的绝对路径 }}
|
||||
// @icon https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/images/logo-small.png
|
||||
// @icon64 https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/images/logo.png
|
||||
// ==/UserScript==
|
||||
```
|
||||
6. 在那些 `@require` 下面再添加一行 `@require file://{{ bilibili-evolved.dev.user.js的绝对路径 }}`
|
||||
> Windows 例子: `@require file://C:/xxx/Bilibili-Evolved/bilibili-evolved.dev.user.js`
|
||||
|
||||
> macOS 例子: `@require file:///Users/xxx/Documents/Bilibili-Evolved/bilibili-evolved.dev.user.js`
|
||||
6. 将里面的 `{{ bilibili-evolved.dev.user.js的绝对路径 }}` 替换为第一步生成的文件的真实路径.
|
||||
> Windows 例子: `@require file://C:/xxx/Bilibili-Evolved/dist/bilibili-evolved.dev.user.js`
|
||||
|
||||
> macOS 例子: `@require file:///Users/xxx/Documents/Bilibili-Evolved/dist/bilibili-evolved.dev.user.js`
|
||||
|
||||
> 上面那些其他的 @require 跟 `src/client/common.meta.json` 里的保持一致就行, 偶尔这些依赖项会变动导致这个本地调试脚本失效, 到时候照着改一下就行.
|
||||
7. 保存脚本, 运行 `启动开发服务 dev-server` 任务
|
||||
8. 进入 b 站, 安装 `DevClient` 组件, 功能中显示已连接时就是成功了
|
||||
|
||||
7. 进入 b 站, 安装 `DevClient` 组件, 功能中显示已连接时就是成功了
|
||||
|
||||
**如果使用 Firefox 或 Safari**
|
||||
1. 运行 `启动开发服务 dev-server` 任务时, 假设得到的本体链接为 `http://localhost:23333/dist/bilibili-evolved.dev.user.js`
|
||||
@ -138,6 +149,7 @@ pnpm install
|
||||
|
||||
## 可用资源
|
||||
本体提供了大量 API 供组件 / 插件使用.
|
||||
|
||||
### 全局
|
||||
全局变量, 无需 `import` 就可以直接使用. (Tampermonkey API 这里不再列出了, 可根据代码提示使用)
|
||||
|
||||
@ -230,7 +242,11 @@ pnpm install
|
||||
- `ui/AsyncButton.vue`: `click` 事件为异步函数时, 执行期间自动使 `Button` 禁用, 其他和 `Button` 相同.
|
||||
|
||||
## 代码风格检查
|
||||
项目中含有 ESLint, 不通过 ESLint 是无法进行 Pull Request 的. 配置基于 `airbnb-base`, `typescript-eslint/recommended`, `vue/recommended` 修改而来, 几个比较特殊的规则如下:
|
||||
项目中含有 ESLint, 不通过 ESLint 是无法进行 Pull Request 的.
|
||||
|
||||
你可以使用 [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) 插件实时检查当前代码, 也可以运行 `生产:代码检查 prod:lint` 或 `生产:代码修复 prod:lint-fix` 来使用 ESLint 的命令行进行检查.
|
||||
|
||||
配置基于 `airbnb-base`, `typescript-eslint/recommended`, `vue/recommended` 修改而来, 几个比较特殊的规则如下:
|
||||
|
||||
### 强制性
|
||||
- 除了 Vue 单文件组件, 禁止使用 `export default`, 所有导出必须命名.
|
||||
@ -243,12 +259,11 @@ pnpm install
|
||||
- 不需要使用 `this` 特性的函数, 均使用箭头函数.
|
||||
|
||||
## 提交 commit
|
||||
仅提交源代码上的修改即可, 不建议把 dist 文件夹里的产物也提交, 否则容易在 PR 时产生冲突.
|
||||
仅提交源代码上的修改即可, 不要把 dist 文件夹里的产物也提交, 产物会在发布新版本时在对应的生产分支上构建.
|
||||
|
||||
commit message 只需写明改动点, 中英文随意, 也不强求类似 [commit-lint](https://github.com/conventional-changelog/commitlint) 的格式.
|
||||
|
||||
## 发起 PR (合并请求)
|
||||
将你的分支往主仓库的 `preview-features` (新增功能) 或 `preview-fixes` (功能修复) 分支合并就行.
|
||||
|
||||
## 自行保留
|
||||
你可以选择不将功能代码合并到主仓库, 因此也没有 ESLint 的限制. PR 时仅添加指向你的仓库中的组件信息即可, 具体来说, 是在 `registry/lib/docs/third-party.ts` 中, 往对应数组中添加你的功能的相关信息, 当然别忘了把 `owner` 设为你的 GitHub 用户名.
|
||||
或者, 也可以选择不将功能代码合并到主仓库, 因此也没有 ESLint 的限制. PR 时仅添加指向你的仓库中的组件信息即可, 具体来说, 是在 `registry/lib/docs/third-party.ts` 中, 往对应数组中添加你的功能的相关信息, 当然别忘了把 `owner` 设为你的 GitHub 用户名.
|
||||
|
||||
@ -28,6 +28,16 @@ https://afdian.net/@the1812?tab=sponsor
|
||||
|
||||
| 时间 | 用户名 | 单号后4位 | 金额 |
|
||||
| ------------------- | ---------------- | --------- | ------- |
|
||||
| 2023.03.20 21:41:05 | 匿名 | 2237 | ¥6.66 |
|
||||
| 2023.03.19 22:06:03 | *碧 | 3670 | ¥20.00 |
|
||||
| 2023.03.15 11:36:31 | k*n | 6491 | ¥1.00 |
|
||||
| 2023.03.14 00:29:54 | *曰 | 6448 | ¥10.00 |
|
||||
| 2023.03.14 00:28:35 | *曰 | 8124 | ¥10.00 |
|
||||
| 2023.03.04 11:47:53 | 匿名 | 0764 | ¥5.00 |
|
||||
| 2023.02.28 20:56:51 | *肠 | 2857 | ¥10.00 |
|
||||
| 2023.02.26 19:52:19 | 无*n | 0780 | ¥1.00 |
|
||||
| 2023.02.20 23:40:27 | 匿名 | 5551 | ¥5.00 |
|
||||
| 2023.02.20 21:15:04 | 匿名 | 6088 | ¥5.00 |
|
||||
| 2023.02.18 22:49:21 | \*😕\* | 9195 | ¥5.00 |
|
||||
| 2023.02.14 20:40:45 | 匿名 | 9914 | ¥5.00 |
|
||||
| 2023.02.13 12:33:00 | A*l | 2182 | ¥66.00 |
|
||||
|
||||
@ -95,14 +95,6 @@
|
||||
"fullRelativePath": "../../registry/dist/components/live/danmaku-sendbar.js",
|
||||
"fullAbsolutePath": "registry/dist/components/live/danmaku-sendbar.js"
|
||||
},
|
||||
{
|
||||
"type": "component",
|
||||
"name": "downloadLiveRecords",
|
||||
"displayName": "直播录像下载",
|
||||
"description": "在直播录像页面 `live.bilibili.com/record/` 中添加下载支持.",
|
||||
"fullRelativePath": "../../registry/dist/components/live/download-records.js",
|
||||
"fullAbsolutePath": "registry/dist/components/live/download-records.js"
|
||||
},
|
||||
{
|
||||
"type": "component",
|
||||
"name": "liveGiftBox",
|
||||
@ -223,6 +215,14 @@
|
||||
"fullRelativePath": "../../registry/dist/components/style/hide/banner.js",
|
||||
"fullAbsolutePath": "registry/dist/components/style/hide/banner.js"
|
||||
},
|
||||
{
|
||||
"type": "component",
|
||||
"name": "hideTrendingSearch",
|
||||
"displayName": "隐藏热搜",
|
||||
"description": "隐藏搜索栏和搜索页面中的 `bilibili 热搜`.",
|
||||
"fullRelativePath": "../../registry/dist/components/style/hide/trending-search.js",
|
||||
"fullAbsolutePath": "registry/dist/components/style/hide/trending-search.js"
|
||||
},
|
||||
{
|
||||
"type": "component",
|
||||
"name": "hideRecommendedLive",
|
||||
@ -239,6 +239,14 @@
|
||||
"fullRelativePath": "../../registry/dist/components/style/hide/video/related-videos.js",
|
||||
"fullAbsolutePath": "registry/dist/components/style/hide/video/related-videos.js"
|
||||
},
|
||||
{
|
||||
"type": "component",
|
||||
"name": "hideVideoShare",
|
||||
"displayName": "隐藏视频分享",
|
||||
"description": "隐藏视频和番剧播放器下方的分享按钮.\r\n",
|
||||
"fullRelativePath": "../../registry/dist/components/style/hide/video/share.js",
|
||||
"fullAbsolutePath": "registry/dist/components/style/hide/video/share.js"
|
||||
},
|
||||
{
|
||||
"type": "component",
|
||||
"name": "hideVideoTopMask",
|
||||
@ -402,8 +410,8 @@
|
||||
{
|
||||
"type": "component",
|
||||
"name": "columnUnlock",
|
||||
"displayName": "专栏文字选择",
|
||||
"description": "使专栏的文字可以选择.",
|
||||
"displayName": "专栏复制优化",
|
||||
"description": "(原名: 专栏文字选择, 现在专栏已经不限制选中文字了)\r\n\r\n避免专栏的文字复制后在最后带上出处信息, 更贴近原生的复制行为.\r\n",
|
||||
"fullRelativePath": "../../registry/dist/components/utils/column-unlock.js",
|
||||
"fullAbsolutePath": "registry/dist/components/utils/column-unlock.js"
|
||||
},
|
||||
@ -483,7 +491,7 @@
|
||||
"type": "component",
|
||||
"name": "urlParamsClean",
|
||||
"displayName": "网址参数清理",
|
||||
"description": "自动删除网址中的多余跟踪参数. 请注意这会导致浏览器历史记录出现重复的标题 (分别是转换前后的网址), 并可能导致后退要多退几次.",
|
||||
"description": "自动删除网址中的多余跟踪参数. 请注意这会导致浏览器历史记录出现重复的标题 (分别是转换前后的网址), 并可能导致后退要多退几次.\r\n",
|
||||
"fullRelativePath": "../../registry/dist/components/utils/url-params-clean.js",
|
||||
"fullAbsolutePath": "registry/dist/components/utils/url-params-clean.js"
|
||||
},
|
||||
@ -776,6 +784,30 @@
|
||||
"description": "by FoundTheWOUT\n\n在视频播放器右上角显示系统时间.",
|
||||
"owner": "FoundTheWOUT"
|
||||
},
|
||||
{
|
||||
"type": "plugin",
|
||||
"name": "launchBar.actions.audioSearch",
|
||||
"displayName": "搜索栏 - 音频跳转",
|
||||
"description": "在输入音频的 au 号或播放列表的 am 号时, 提供对应的跳转选项.\r\n",
|
||||
"fullRelativePath": "../../registry/dist/plugins/launch-bar/audio-search.js",
|
||||
"fullAbsolutePath": "registry/dist/plugins/launch-bar/audio-search.js"
|
||||
},
|
||||
{
|
||||
"type": "plugin",
|
||||
"name": "launchBar.actions.bangumiSearch",
|
||||
"displayName": "搜索栏 - 番剧跳转",
|
||||
"description": "在输入番剧的 ss 号 / ep 号, 或番剧详情的 md 号时, 提供对应的跳转选项.\r\n",
|
||||
"fullRelativePath": "../../registry/dist/plugins/launch-bar/bangumi-search.js",
|
||||
"fullAbsolutePath": "registry/dist/plugins/launch-bar/bangumi-search.js"
|
||||
},
|
||||
{
|
||||
"type": "plugin",
|
||||
"name": "launchBar.actions.cvSearch",
|
||||
"displayName": "搜索栏 - 专栏跳转",
|
||||
"description": "在输入专栏的 cv 号或专栏文集的 rl 号时, 提供对应的跳转选项.\r\n",
|
||||
"fullRelativePath": "../../registry/dist/plugins/launch-bar/cv-search.js",
|
||||
"fullAbsolutePath": "registry/dist/plugins/launch-bar/cv-search.js"
|
||||
},
|
||||
{
|
||||
"type": "plugin",
|
||||
"name": "launchBar.trendingSearch",
|
||||
@ -832,6 +864,14 @@
|
||||
"fullRelativePath": "../../registry/dist/plugins/utils/keymap-toggle-danmaku-list.js",
|
||||
"fullAbsolutePath": "registry/dist/plugins/utils/keymap-toggle-danmaku-list.js"
|
||||
},
|
||||
{
|
||||
"type": "plugin",
|
||||
"name": "keymap.actions.togglePlayerLight",
|
||||
"displayName": "快捷键扩展 - 开关灯",
|
||||
"description": "在快捷键的动作列表里添加一个 \"开关灯\".",
|
||||
"fullRelativePath": "../../registry/dist/plugins/utils/keymap-toggle-player-light.js",
|
||||
"fullAbsolutePath": "registry/dist/plugins/utils/keymap-toggle-player-light.js"
|
||||
},
|
||||
{
|
||||
"type": "plugin",
|
||||
"name": "keymap.actions.toggleSubtitle",
|
||||
|
||||
@ -116,15 +116,6 @@
|
||||
|
||||
在直播的网页全屏和全屏模式状态下, 在底部显示弹幕栏.
|
||||
|
||||
### [直播录像下载](../../registry/dist/components/live/download-records.js)
|
||||
`downloadLiveRecords`
|
||||
|
||||
**jsDelivr:** [`Stable`](https://fastly.jsdelivr.net/gh/the1812/Bilibili-Evolved@master/registry/dist/components/live/download-records.js) / [`Preview`](https://fastly.jsdelivr.net/gh/the1812/Bilibili-Evolved@preview/registry/dist/components/live/download-records.js)
|
||||
|
||||
**GitHub:** [`Stable`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/components/live/download-records.js) / [`Preview`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/components/live/download-records.js)
|
||||
|
||||
在直播录像页面 `live.bilibili.com/record/` 中添加下载支持.
|
||||
|
||||
### [直播全屏包裹](../../registry/dist/components/live/gift-box.js)
|
||||
`liveGiftBox`
|
||||
|
||||
@ -264,6 +255,15 @@
|
||||
|
||||
隐藏首页顶部横幅.
|
||||
|
||||
### [隐藏热搜](../../registry/dist/components/style/hide/trending-search.js)
|
||||
`hideTrendingSearch`
|
||||
|
||||
**jsDelivr:** [`Stable`](https://fastly.jsdelivr.net/gh/the1812/Bilibili-Evolved@master/registry/dist/components/style/hide/trending-search.js) / [`Preview`](https://fastly.jsdelivr.net/gh/the1812/Bilibili-Evolved@preview/registry/dist/components/style/hide/trending-search.js)
|
||||
|
||||
**GitHub:** [`Stable`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/components/style/hide/trending-search.js) / [`Preview`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/components/style/hide/trending-search.js)
|
||||
|
||||
隐藏搜索栏和搜索页面中的 `bilibili 热搜`.
|
||||
|
||||
### [隐藏直播推荐](../../registry/dist/components/style/hide/video/recommended-live.js)
|
||||
`hideRecommendedLive`
|
||||
|
||||
@ -282,6 +282,15 @@
|
||||
|
||||
隐藏番剧和视频页面右侧的推荐视频列表. 注意: 如果你想关闭 b 站的自动连播 (自动播放下一个推荐视频) 功能, 需要先取消隐藏视频推荐才能看到开关.
|
||||
|
||||
### [隐藏视频分享](../../registry/dist/components/style/hide/video/share.js)
|
||||
`hideVideoShare`
|
||||
|
||||
**jsDelivr:** [`Stable`](https://fastly.jsdelivr.net/gh/the1812/Bilibili-Evolved@master/registry/dist/components/style/hide/video/share.js) / [`Preview`](https://fastly.jsdelivr.net/gh/the1812/Bilibili-Evolved@preview/registry/dist/components/style/hide/video/share.js)
|
||||
|
||||
**GitHub:** [`Stable`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/components/style/hide/video/share.js) / [`Preview`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/components/style/hide/video/share.js)
|
||||
|
||||
隐藏视频和番剧播放器下方的分享按钮.
|
||||
|
||||
### [隐藏视频标题层](../../registry/dist/components/style/hide/video/top-mask.js)
|
||||
`hideVideoTopMask`
|
||||
|
||||
@ -487,14 +496,16 @@ by [@snowraincloud](https://github.com/snowraincloud)
|
||||
|
||||
在功能面板中提供一些可以每日进行的操作.
|
||||
|
||||
### [专栏文字选择](../../registry/dist/components/utils/column-unlock.js)
|
||||
### [专栏复制优化](../../registry/dist/components/utils/column-unlock.js)
|
||||
`columnUnlock`
|
||||
|
||||
**jsDelivr:** [`Stable`](https://fastly.jsdelivr.net/gh/the1812/Bilibili-Evolved@master/registry/dist/components/utils/column-unlock.js) / [`Preview`](https://fastly.jsdelivr.net/gh/the1812/Bilibili-Evolved@preview/registry/dist/components/utils/column-unlock.js)
|
||||
|
||||
**GitHub:** [`Stable`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/components/utils/column-unlock.js) / [`Preview`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/components/utils/column-unlock.js)
|
||||
|
||||
使专栏的文字可以选择.
|
||||
(原名: 专栏文字选择, 现在专栏已经不限制选中文字了)
|
||||
|
||||
避免专栏的文字复制后在最后带上出处信息, 更贴近原生的复制行为.
|
||||
|
||||
### [复制评论链接](../../registry/dist/components/utils/comments/copy-link.js)
|
||||
`copyCommentsLink`
|
||||
@ -1024,6 +1035,33 @@ by FoundTheWOUT
|
||||
在视频播放器右上角显示系统时间.
|
||||
## 插件
|
||||
|
||||
### [搜索栏 - 音频跳转](../../registry/dist/plugins/launch-bar/audio-search.js)
|
||||
`launchBar.actions.audioSearch`
|
||||
|
||||
**jsDelivr:** [`Stable`](https://fastly.jsdelivr.net/gh/the1812/Bilibili-Evolved@master/registry/dist/plugins/launch-bar/audio-search.js) / [`Preview`](https://fastly.jsdelivr.net/gh/the1812/Bilibili-Evolved@preview/registry/dist/plugins/launch-bar/audio-search.js)
|
||||
|
||||
**GitHub:** [`Stable`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/plugins/launch-bar/audio-search.js) / [`Preview`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/plugins/launch-bar/audio-search.js)
|
||||
|
||||
在输入音频的 au 号或播放列表的 am 号时, 提供对应的跳转选项.
|
||||
|
||||
### [搜索栏 - 番剧跳转](../../registry/dist/plugins/launch-bar/bangumi-search.js)
|
||||
`launchBar.actions.bangumiSearch`
|
||||
|
||||
**jsDelivr:** [`Stable`](https://fastly.jsdelivr.net/gh/the1812/Bilibili-Evolved@master/registry/dist/plugins/launch-bar/bangumi-search.js) / [`Preview`](https://fastly.jsdelivr.net/gh/the1812/Bilibili-Evolved@preview/registry/dist/plugins/launch-bar/bangumi-search.js)
|
||||
|
||||
**GitHub:** [`Stable`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/plugins/launch-bar/bangumi-search.js) / [`Preview`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/plugins/launch-bar/bangumi-search.js)
|
||||
|
||||
在输入番剧的 ss 号 / ep 号, 或番剧详情的 md 号时, 提供对应的跳转选项.
|
||||
|
||||
### [搜索栏 - 专栏跳转](../../registry/dist/plugins/launch-bar/cv-search.js)
|
||||
`launchBar.actions.cvSearch`
|
||||
|
||||
**jsDelivr:** [`Stable`](https://fastly.jsdelivr.net/gh/the1812/Bilibili-Evolved@master/registry/dist/plugins/launch-bar/cv-search.js) / [`Preview`](https://fastly.jsdelivr.net/gh/the1812/Bilibili-Evolved@preview/registry/dist/plugins/launch-bar/cv-search.js)
|
||||
|
||||
**GitHub:** [`Stable`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/plugins/launch-bar/cv-search.js) / [`Preview`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/plugins/launch-bar/cv-search.js)
|
||||
|
||||
在输入专栏的 cv 号或专栏文集的 rl 号时, 提供对应的跳转选项.
|
||||
|
||||
### [搜索栏 - 搜索推荐](../../registry/dist/plugins/launch-bar/trending-search.js)
|
||||
`launchBar.trendingSearch`
|
||||
|
||||
@ -1087,6 +1125,15 @@ by FoundTheWOUT
|
||||
|
||||
在快捷键的动作列表里添加一个 "开关弹幕列表".
|
||||
|
||||
### [快捷键扩展 - 开关灯](../../registry/dist/plugins/utils/keymap-toggle-player-light.js)
|
||||
`keymap.actions.togglePlayerLight`
|
||||
|
||||
**jsDelivr:** [`Stable`](https://fastly.jsdelivr.net/gh/the1812/Bilibili-Evolved@master/registry/dist/plugins/utils/keymap-toggle-player-light.js) / [`Preview`](https://fastly.jsdelivr.net/gh/the1812/Bilibili-Evolved@preview/registry/dist/plugins/utils/keymap-toggle-player-light.js)
|
||||
|
||||
**GitHub:** [`Stable`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/plugins/utils/keymap-toggle-player-light.js) / [`Preview`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/plugins/utils/keymap-toggle-player-light.js)
|
||||
|
||||
在快捷键的动作列表里添加一个 "开关灯".
|
||||
|
||||
### [快捷键扩展 - 开关 CC 字幕](../../registry/dist/plugins/utils/keymap-toggle-subtitle.js)
|
||||
`keymap.actions.toggleSubtitle`
|
||||
|
||||
|
||||
@ -111,13 +111,12 @@
|
||||
{
|
||||
"name": "downloader",
|
||||
"displayName": "下载器",
|
||||
"description": "支持下载各种内容.\n\n包含以下功能:\n下载视频, 下载字幕, 下载弹幕, 下载音频, 直播录像下载",
|
||||
"description": "支持下载各种内容.\n\n包含以下功能:\n下载视频, 下载字幕, 下载弹幕, 下载音频",
|
||||
"components": [
|
||||
"downloadVideo",
|
||||
"downloadSubtitle",
|
||||
"downloadDanmaku",
|
||||
"downloadAudio",
|
||||
"downloadLiveRecords"
|
||||
"downloadAudio"
|
||||
],
|
||||
"items": [
|
||||
{
|
||||
@ -151,14 +150,6 @@
|
||||
"description": "开启音频下载支持, 音频页面中可以在功能面板中下载当前音频.\n\n> 需要进入音频的详细信息页面才能下载, 在其他页面中此按钮将不可点击.",
|
||||
"fullRelativePath": "../../registry/dist/components/utils/download-audio.js",
|
||||
"fullAbsolutePath": "registry/dist/components/utils/download-audio.js"
|
||||
},
|
||||
{
|
||||
"type": "component",
|
||||
"name": "downloadLiveRecords",
|
||||
"displayName": "直播录像下载",
|
||||
"description": "在直播录像页面 `live.bilibili.com/record/` 中添加下载支持.",
|
||||
"fullRelativePath": "../../registry/dist/components/live/download-records.js",
|
||||
"fullAbsolutePath": "registry/dist/components/live/download-records.js"
|
||||
}
|
||||
],
|
||||
"type": "pack"
|
||||
@ -166,7 +157,7 @@
|
||||
{
|
||||
"name": "starter",
|
||||
"displayName": "常用功能包",
|
||||
"description": "提供一些常用功能.\n\n包含以下功能:\n使用细滚动条, 自定义顶栏, 删除广告, 专栏文字选择, 网址参数清理, 快捷键扩展, 查看封面, BV 号转换, 删除直播水印, 直播弹幕发送栏, 直播全屏包裹, 展开动态内容, 动态反折叠, 快速收起评论, 禁止跳转动态详情, 展开视频简介, 设置面板 - \"最近使用\" 类别",
|
||||
"description": "提供一些常用功能.\n\n包含以下功能:\n使用细滚动条, 自定义顶栏, 删除广告, 专栏复制优化, 网址参数清理, 快捷键扩展, 查看封面, BV 号转换, 删除直播水印, 直播弹幕发送栏, 直播全屏包裹, 展开动态内容, 动态反折叠, 快速收起评论, 禁止跳转动态详情, 展开视频简介, 设置面板 - \"最近使用\" 类别",
|
||||
"components": [
|
||||
"elegantScrollbar",
|
||||
"customNavbar",
|
||||
@ -216,8 +207,8 @@
|
||||
{
|
||||
"type": "component",
|
||||
"name": "columnUnlock",
|
||||
"displayName": "专栏文字选择",
|
||||
"description": "使专栏的文字可以选择.",
|
||||
"displayName": "专栏复制优化",
|
||||
"description": "(原名: 专栏文字选择, 现在专栏已经不限制选中文字了)\r\n\r\n避免专栏的文字复制后在最后带上出处信息, 更贴近原生的复制行为.\r\n",
|
||||
"fullRelativePath": "../../registry/dist/components/utils/column-unlock.js",
|
||||
"fullAbsolutePath": "registry/dist/components/utils/column-unlock.js"
|
||||
},
|
||||
@ -225,7 +216,7 @@
|
||||
"type": "component",
|
||||
"name": "urlParamsClean",
|
||||
"displayName": "网址参数清理",
|
||||
"description": "自动删除网址中的多余跟踪参数. 请注意这会导致浏览器历史记录出现重复的标题 (分别是转换前后的网址), 并可能导致后退要多退几次.",
|
||||
"description": "自动删除网址中的多余跟踪参数. 请注意这会导致浏览器历史记录出现重复的标题 (分别是转换前后的网址), 并可能导致后退要多退几次.\r\n",
|
||||
"fullRelativePath": "../../registry/dist/components/utils/url-params-clean.js",
|
||||
"fullAbsolutePath": "registry/dist/components/utils/url-params-clean.js"
|
||||
},
|
||||
|
||||
@ -84,7 +84,7 @@ https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist
|
||||
支持下载各种内容.
|
||||
|
||||
包含以下功能:
|
||||
下载视频, 下载字幕, 下载弹幕, 下载音频, 直播录像下载
|
||||
下载视频, 下载字幕, 下载弹幕, 下载音频
|
||||
|
||||
<details>
|
||||
<summary><strong>jsDelivr Stable</strong></summary>
|
||||
@ -94,7 +94,6 @@ https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/
|
||||
https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/components/video/subtitle/download.js
|
||||
https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/components/video/danmaku/download.js
|
||||
https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/components/utils/download-audio.js
|
||||
https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/components/live/download-records.js
|
||||
```
|
||||
|
||||
</details>
|
||||
@ -106,7 +105,6 @@ https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist
|
||||
https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/components/video/subtitle/download.js
|
||||
https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/components/video/danmaku/download.js
|
||||
https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/components/utils/download-audio.js
|
||||
https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/components/live/download-records.js
|
||||
```
|
||||
|
||||
</details>
|
||||
@ -118,7 +116,6 @@ https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/
|
||||
https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/components/video/subtitle/download.js
|
||||
https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/components/video/danmaku/download.js
|
||||
https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/components/utils/download-audio.js
|
||||
https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/components/live/download-records.js
|
||||
```
|
||||
|
||||
</details>
|
||||
@ -130,7 +127,6 @@ https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist
|
||||
https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/components/video/subtitle/download.js
|
||||
https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/components/video/danmaku/download.js
|
||||
https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/components/utils/download-audio.js
|
||||
https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/components/live/download-records.js
|
||||
```
|
||||
|
||||
</details>
|
||||
@ -139,7 +135,7 @@ https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist
|
||||
提供一些常用功能.
|
||||
|
||||
包含以下功能:
|
||||
使用细滚动条, 自定义顶栏, 删除广告, 专栏文字选择, 网址参数清理, 快捷键扩展, 查看封面, BV 号转换, 删除直播水印, 直播弹幕发送栏, 直播全屏包裹, 展开动态内容, 动态反折叠, 快速收起评论, 禁止跳转动态详情, 展开视频简介, 设置面板 - "最近使用" 类别
|
||||
使用细滚动条, 自定义顶栏, 删除广告, 专栏复制优化, 网址参数清理, 快捷键扩展, 查看封面, BV 号转换, 删除直播水印, 直播弹幕发送栏, 直播全屏包裹, 展开动态内容, 动态反折叠, 快速收起评论, 禁止跳转动态详情, 展开视频简介, 设置面板 - "最近使用" 类别
|
||||
|
||||
<details>
|
||||
<summary><strong>jsDelivr Stable</strong></summary>
|
||||
|
||||
@ -51,9 +51,12 @@ const entry = async () => {
|
||||
}
|
||||
const face = dq(
|
||||
clone,
|
||||
'.live-up-img, .bili-dyn-live-users__item__face .bili-awesome-img',
|
||||
'.live-up-img, .bili-dyn-live-users__item__face, .bili-awesome-img',
|
||||
) as HTMLElement
|
||||
face.style.backgroundImage = `url(${it.face})`
|
||||
face.style.backgroundSize = 'cover'
|
||||
const facepic = dq(clone, '.b-img--face') as HTMLElement
|
||||
facepic.style.visibility = 'hidden'
|
||||
const title = dq(clone, '.live-name, .bili-dyn-live-users__item__title') as HTMLElement
|
||||
title.innerHTML = it.title
|
||||
title.title = it.title
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
.bili-album__watch__content {
|
||||
max-height: calc(100vh - 200px);
|
||||
overflow: auto;
|
||||
// overscroll-behavior: contain;
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
在动态里查看图片时 (非全屏), 限制高度不超过一屏, 超过则可滚动, 避免长图看完后动态被顶到很上面.
|
||||
14
registry/lib/components/feeds/image-preview-scroll/index.ts
Normal file
14
registry/lib/components/feeds/image-preview-scroll/index.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { defineComponentMetadata } from '@/components/define'
|
||||
|
||||
export const component = defineComponentMetadata({
|
||||
name: 'imagePreviewScroll',
|
||||
displayName: '动态图片限高',
|
||||
tags: [componentsTags.feeds],
|
||||
instantStyles: [
|
||||
{
|
||||
name: 'imagePreviewScroll',
|
||||
style: () => import('./image-preview-scroll.scss'),
|
||||
},
|
||||
],
|
||||
entry: none,
|
||||
})
|
||||
@ -7,9 +7,7 @@
|
||||
:value="folder"
|
||||
@change="change($event)"
|
||||
>
|
||||
<template #item="{ item }">
|
||||
{{ item.name }}
|
||||
</template>
|
||||
<template #item="{ item }"> {{ item.name }} ({{ item.count }}) </template>
|
||||
</VDropdown>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
</div>
|
||||
<div class="live-info">
|
||||
<div class="live-title" :title="c.title">{{ c.title }}</div>
|
||||
<div class="live-name" :title="c.name">{{ c.upName }}</div>
|
||||
<div class="live-name" :title="c.upName">{{ c.upName }}</div>
|
||||
</div>
|
||||
</a>
|
||||
</transition-group>
|
||||
|
||||
@ -3,10 +3,19 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import type { PropType } from 'vue'
|
||||
import type { NavbarIframeConfig } from './iframe'
|
||||
import { popperMixin } from '../mixins'
|
||||
import { CustomNavbarItem } from '../custom-navbar-item'
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'IframePopup',
|
||||
mixins: [popperMixin],
|
||||
props: {
|
||||
item: {
|
||||
type: CustomNavbarItem as unknown as PropType<CustomNavbarItem & NavbarIframeConfig>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { CustomNavbarItemInit } from '../custom-navbar-item'
|
||||
|
||||
interface NavbarIframeConfig {
|
||||
export interface NavbarIframeConfig {
|
||||
src: string
|
||||
href: string
|
||||
width: number
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<SubscriptionsList type="bangumi"></SubscriptionsList>
|
||||
<SubscriptionsList type="bangumi" :filter="filter"></SubscriptionsList>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import SubscriptionsList from './SubscriptionsList.vue'
|
||||
@ -8,5 +8,11 @@ export default Vue.extend({
|
||||
components: {
|
||||
SubscriptionsList,
|
||||
},
|
||||
props: {
|
||||
filter: {
|
||||
type: [Object, null],
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<SubscriptionsList type="cinema"></SubscriptionsList>
|
||||
<SubscriptionsList type="cinema" :filter="filter"></SubscriptionsList>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import SubscriptionsList from './SubscriptionsList.vue'
|
||||
@ -8,5 +8,11 @@ export default Vue.extend({
|
||||
components: {
|
||||
SubscriptionsList,
|
||||
},
|
||||
props: {
|
||||
filter: {
|
||||
type: [Object, null],
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -1,43 +1,107 @@
|
||||
<template>
|
||||
<div class="navbar-subscriptions">
|
||||
<TabControl ref="tabControl" :tabs="tabs" :more-link="moreLink"></TabControl>
|
||||
<TabControl ref="tabControl" :tabs="tabs" :more-link="moreLink">
|
||||
<template #header-item>
|
||||
<div class="navbar-subscriptions-filter">
|
||||
<VDropdown v-model="selectedFilter" round :items="filterItems" />
|
||||
</div>
|
||||
</template>
|
||||
</TabControl>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { TabControl } from '@/ui'
|
||||
import { TabMappings, TabMapping } from '@/ui/tab-mapping'
|
||||
import { TabControl, VDropdown } from '@/ui'
|
||||
import { TabMapping, TabMappings } from '@/ui/tab-mapping'
|
||||
import { getUID } from '@/core/utils'
|
||||
import { popperMixin } from '../mixins'
|
||||
import { SubscriptionTypes } from './subscriptions'
|
||||
import { SubscriptionStatus, SubscriptionStatusFilter } from './types'
|
||||
|
||||
const filterItems: {
|
||||
name: string
|
||||
value: SubscriptionStatusFilter
|
||||
displayName: string
|
||||
}[] = [
|
||||
{
|
||||
name: 'all',
|
||||
value: {
|
||||
viewAll: true,
|
||||
status: SubscriptionStatus.Viewing,
|
||||
},
|
||||
displayName: '全部',
|
||||
},
|
||||
{
|
||||
name: 'viewing',
|
||||
value: {
|
||||
viewAll: false,
|
||||
status: SubscriptionStatus.Viewing,
|
||||
},
|
||||
displayName: '在看',
|
||||
},
|
||||
{
|
||||
name: 'toView',
|
||||
value: {
|
||||
viewAll: false,
|
||||
status: SubscriptionStatus.ToView,
|
||||
},
|
||||
displayName: '想看',
|
||||
},
|
||||
{
|
||||
name: 'viewed',
|
||||
value: {
|
||||
viewAll: false,
|
||||
status: SubscriptionStatus.Viewed,
|
||||
},
|
||||
displayName: '看过',
|
||||
},
|
||||
]
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
TabControl,
|
||||
VDropdown,
|
||||
},
|
||||
mixins: [popperMixin],
|
||||
data() {
|
||||
const uid = getUID()
|
||||
return {
|
||||
uid,
|
||||
filterItems,
|
||||
selectedFilter: filterItems[0],
|
||||
moreLink: (tab: TabMapping) => `https://space.bilibili.com/${uid}/${tab.name}`,
|
||||
tabs: [
|
||||
filter: {
|
||||
viewAll: true,
|
||||
status: SubscriptionStatus.Viewing,
|
||||
} as SubscriptionStatusFilter,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
tabs(): TabMappings {
|
||||
return [
|
||||
{
|
||||
name: SubscriptionTypes.Bangumi,
|
||||
displayName: '追番',
|
||||
activeLink: `https://space.bilibili.com/${uid}/bangumi`,
|
||||
activeLink: `https://space.bilibili.com/${this.uid}/bangumi`,
|
||||
component: () => import('./BangumiSubscriptions.vue').then(m => m.default),
|
||||
propsData: {
|
||||
filter: this.selectedFilter.value,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: SubscriptionTypes.Cinema,
|
||||
displayName: '追剧',
|
||||
activeLink: `https://space.bilibili.com/${uid}/cinema`,
|
||||
activeLink: `https://space.bilibili.com/${this.uid}/cinema`,
|
||||
component: () => import('./CinemaSubscriptions.vue').then(m => m.default),
|
||||
propsData: {
|
||||
filter: this.selectedFilter.value,
|
||||
},
|
||||
},
|
||||
] as TabMappings,
|
||||
}
|
||||
]
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
<style lang="scss">
|
||||
@import 'common';
|
||||
@import '../popup';
|
||||
|
||||
.navbar-subscriptions {
|
||||
@ -54,5 +118,10 @@ export default Vue.extend({
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
&-filter {
|
||||
@include h-stretch();
|
||||
justify-content: flex-end;
|
||||
height: 26px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -46,12 +46,8 @@ import { logError } from '@/core/utils/log'
|
||||
import { DpiImage, VLoading, VEmpty, VIcon, ScrollTrigger } from '@/ui'
|
||||
import { getJsonWithCredentials } from '@/core/ajax'
|
||||
import { SubscriptionTypes } from './subscriptions'
|
||||
import { SubscriptionItem, SubscriptionStatus, SubscriptionStatusFilter } from './types'
|
||||
|
||||
enum SubscriptionStatus {
|
||||
ToView = 1,
|
||||
Viewing,
|
||||
Viewed,
|
||||
}
|
||||
const getStatusText = (status: SubscriptionStatus) => {
|
||||
switch (status) {
|
||||
case SubscriptionStatus.ToView:
|
||||
@ -63,10 +59,7 @@ const getStatusText = (status: SubscriptionStatus) => {
|
||||
return '看过'
|
||||
}
|
||||
}
|
||||
const subscriptionSorter = (
|
||||
a: { status: SubscriptionStatus },
|
||||
b: { status: SubscriptionStatus },
|
||||
) => {
|
||||
const subscriptionSorter = (a: SubscriptionItem, b: SubscriptionItem) => {
|
||||
let statusA = a.status
|
||||
if (statusA !== SubscriptionStatus.Viewed) {
|
||||
statusA = SubscriptionStatus.Viewed - statusA
|
||||
@ -86,6 +79,10 @@ export default Vue.extend({
|
||||
ScrollTrigger,
|
||||
},
|
||||
props: {
|
||||
filter: {
|
||||
type: [Object, null],
|
||||
default: null,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: SubscriptionTypes.Bangumi,
|
||||
@ -99,12 +96,21 @@ export default Vue.extend({
|
||||
page: 1,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
filter() {
|
||||
this.cards = []
|
||||
this.loading = true
|
||||
this.page = 1
|
||||
this.nextPage()
|
||||
},
|
||||
},
|
||||
async created() {
|
||||
this.nextPage()
|
||||
},
|
||||
methods: {
|
||||
async nextPage() {
|
||||
try {
|
||||
const filter = this.filter as SubscriptionStatusFilter
|
||||
const json = await getJsonWithCredentials(
|
||||
`https://api.bilibili.com/x/space/bangumi/follow/list?type=${
|
||||
this.type !== SubscriptionTypes.Bangumi ? '2' : '1'
|
||||
@ -114,10 +120,10 @@ export default Vue.extend({
|
||||
logError(`加载订阅信息失败: ${json.message}`)
|
||||
return
|
||||
}
|
||||
const cards = lodash
|
||||
const newCards: SubscriptionItem[] = lodash
|
||||
.uniqBy(
|
||||
(this.cards as any[]).concat(
|
||||
(lodash.get(json, 'data.list') as any[]).map(item => ({
|
||||
(lodash.get(json, 'data.list') as any[]).map(
|
||||
(item): SubscriptionItem => ({
|
||||
title: item.title,
|
||||
coverUrl: item.square_cover.replace('http:', 'https:'),
|
||||
latest: item.new_ep.index_show,
|
||||
@ -127,14 +133,20 @@ export default Vue.extend({
|
||||
statusText: getStatusText(item.follow_status),
|
||||
playUrl: `https://www.bilibili.com/bangumi/play/ss${item.season_id}`,
|
||||
mediaUrl: `https://www.bilibili.com/bangumi/media/md${item.media_id}`,
|
||||
})),
|
||||
}),
|
||||
),
|
||||
card => card.id,
|
||||
)
|
||||
.filter(card => {
|
||||
if (filter.viewAll) {
|
||||
return true
|
||||
}
|
||||
return card.status === filter.status
|
||||
})
|
||||
.sort(subscriptionSorter)
|
||||
this.page++
|
||||
this.cards = cards
|
||||
this.hasMorePage = lodash.get(json, 'data.total', 0) > this.cards.length
|
||||
this.cards = this.cards.concat(newCards)
|
||||
this.hasMorePage = newCards.length > 0
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
export enum SubscriptionStatus {
|
||||
ToView = 1,
|
||||
Viewing,
|
||||
Viewed,
|
||||
}
|
||||
export interface SubscriptionStatusFilter {
|
||||
viewAll: boolean
|
||||
status: SubscriptionStatus
|
||||
}
|
||||
export interface SubscriptionItem {
|
||||
title: string
|
||||
coverUrl: string
|
||||
latest: number
|
||||
progress: string
|
||||
id: string
|
||||
status: SubscriptionStatus
|
||||
statusText: string
|
||||
playUrl: string
|
||||
mediaUrl: string
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div class="watchlater-list">
|
||||
<div class="header">
|
||||
<div class="watchlater-list-summary">共 {{ filteredCards.length }} 个</div>
|
||||
<div class="search">
|
||||
<TextBox v-model="search" linear placeholder="搜索"></TextBox>
|
||||
</div>
|
||||
@ -203,13 +204,17 @@ export default Vue.extend({
|
||||
cursor: pointer;
|
||||
}
|
||||
.header {
|
||||
@include h-stretch();
|
||||
@include h-center();
|
||||
justify-content: space-between;
|
||||
align-self: stretch;
|
||||
margin: 16px 12px;
|
||||
.watchlater-list-summary {
|
||||
margin-right: 6px;
|
||||
}
|
||||
.search {
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
align-self: stretch;
|
||||
margin-right: 8px;
|
||||
.be-textbox {
|
||||
height: 100%;
|
||||
|
||||
@ -888,9 +888,133 @@
|
||||
.startlive-icon {
|
||||
@include to-theme('blue');
|
||||
}
|
||||
.right-area .tip-wrap .icon path[fill="#00AEEC"] {
|
||||
.right-area .tip-wrap .icon path[fill='#00AEEC'] {
|
||||
@include theme-fill();
|
||||
}
|
||||
.bpx-player-pbp-videoshot rect[clip-path] {
|
||||
@include theme-fill();
|
||||
}
|
||||
|
||||
[class*='numberListItem_number_list_item'] {
|
||||
@include background-color('5');
|
||||
@include border-color();
|
||||
&:hover {
|
||||
@include theme-border-color();
|
||||
}
|
||||
&[class*='numberListItem_select'] {
|
||||
@include theme-border-color();
|
||||
[class*='numberListItem_title'] {
|
||||
@include theme-color();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[class*='review_review_module'] {
|
||||
@include border-color('4');
|
||||
|
||||
[class*='review_review_item_fill'] {
|
||||
@include background-color('4');
|
||||
}
|
||||
|
||||
[class*='review_module_title'],
|
||||
[class*='review_review_title'] {
|
||||
@include color('e');
|
||||
}
|
||||
|
||||
[class*='review_review_author'] {
|
||||
@include color('e');
|
||||
&[class*='review_is-vip'] {
|
||||
@include vip-color();
|
||||
}
|
||||
}
|
||||
[class*='review_review_content'] {
|
||||
@include color('a');
|
||||
}
|
||||
}
|
||||
|
||||
[class*='epitem_ep_item'] {
|
||||
&:hover {
|
||||
@include background-color('5');
|
||||
}
|
||||
|
||||
a {
|
||||
@include color('e');
|
||||
&:hover {
|
||||
@include theme-color();
|
||||
}
|
||||
}
|
||||
|
||||
&[class*='epitem_cursor'] {
|
||||
@include background-color('5');
|
||||
path {
|
||||
@include theme-fill();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[class*='seasonlist_season_list'] {
|
||||
[class*='seasonlist_ss_title'] {
|
||||
@include color('e');
|
||||
}
|
||||
[class*='seasonlist_expand_more'] {
|
||||
@include background-color('4');
|
||||
@include color('e');
|
||||
}
|
||||
}
|
||||
|
||||
[class*='mediainfo_media_info'] {
|
||||
@include border-color('4');
|
||||
|
||||
[class*='mediainfo_media_desc'] {
|
||||
&,
|
||||
& i {
|
||||
@include color('e');
|
||||
}
|
||||
}
|
||||
[class*='mediainfo_media_title'] {
|
||||
@include color('e');
|
||||
}
|
||||
[class*='mediainfo_av_link'],
|
||||
[class*='mediainfo_home_link'],
|
||||
[class*='mediainfo_media_pub'] {
|
||||
@include color('a');
|
||||
}
|
||||
|
||||
[class*='mediainfo_ellipsis'],
|
||||
[class*='mediainfo_media_desc'] i {
|
||||
@include background();
|
||||
@include theme-color();
|
||||
}
|
||||
}
|
||||
|
||||
[class*='section_ep_section_module'] {
|
||||
@include background-color('4');
|
||||
[class*='section_section_title'] {
|
||||
@include color('e');
|
||||
}
|
||||
}
|
||||
|
||||
[class*='RecommendItem_wrap'] {
|
||||
[class*='RecommendItem_title'] {
|
||||
@include color('e');
|
||||
}
|
||||
}
|
||||
|
||||
[class*='eplist_ep_list_wrapper'] {
|
||||
@include background-color('4');
|
||||
[class*='eplist_list_title'] [class*='eplist_left_wrap'] h4 {
|
||||
@include color('e');
|
||||
}
|
||||
[class*='numberListItem_title'] {
|
||||
@include color('e');
|
||||
}
|
||||
}
|
||||
[class*='operation_split_line'] {
|
||||
@include border-color('4');
|
||||
}
|
||||
[class*='mediainfo_btn_rating'] {
|
||||
@include background-color();
|
||||
}
|
||||
[class*='DanmukuBox_wrap'] {
|
||||
@include background();
|
||||
}
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
.search-panel {
|
||||
> .trending {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
隐藏搜索栏和搜索页面中的 `bilibili 热搜`.
|
||||
27
registry/lib/components/style/hide/trending-search/index.ts
Normal file
27
registry/lib/components/style/hide/trending-search/index.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { defineComponentMetadata } from '@/components/define'
|
||||
import { allMutations } from '@/core/observer'
|
||||
|
||||
export const component = defineComponentMetadata({
|
||||
name: 'hideTrendingSearch',
|
||||
displayName: '隐藏热搜',
|
||||
tags: [componentsTags.style],
|
||||
instantStyles: [
|
||||
{
|
||||
name: 'hideTrendingSearch',
|
||||
style: () => import('./hide-trending-search.scss'),
|
||||
},
|
||||
],
|
||||
entry: async () => {
|
||||
allMutations(records => {
|
||||
records.forEach(record => {
|
||||
if (
|
||||
record.target instanceof HTMLInputElement &&
|
||||
record.target.classList.contains('nav-search-input') &&
|
||||
record.target.placeholder !== '搜索'
|
||||
) {
|
||||
record.target.placeholder = '搜索'
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
@ -0,0 +1,15 @@
|
||||
.video-toolbar-v1 {
|
||||
.share-wrap {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
.video-toolbar-container {
|
||||
.video-share-wrap {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
.player-left-components {
|
||||
[class*="toolbar_share_info"] {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
1
registry/lib/components/style/hide/video/share/index.md
Normal file
1
registry/lib/components/style/hide/video/share/index.md
Normal file
@ -0,0 +1 @@
|
||||
隐藏视频和番剧播放器下方的分享按钮.
|
||||
14
registry/lib/components/style/hide/video/share/index.ts
Normal file
14
registry/lib/components/style/hide/video/share/index.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { defineComponentMetadata } from '@/components/define'
|
||||
|
||||
export const component = defineComponentMetadata({
|
||||
name: 'hideVideoShare',
|
||||
displayName: '隐藏视频分享',
|
||||
tags: [componentsTags.style, componentsTags.video],
|
||||
instantStyles: [
|
||||
{
|
||||
name: 'hideVideoShare',
|
||||
style: () => import('./hide-video-share.scss'),
|
||||
},
|
||||
],
|
||||
entry: none,
|
||||
})
|
||||
@ -193,81 +193,14 @@ $prefix: 'simplifyLiveroom-switch';
|
||||
.gift-section.guard-ent,
|
||||
.seeds-wrap > .dp-i-block > .item:not(.seeds),
|
||||
.gift-control-panel .wish-icon,
|
||||
.gift-control-panel .wish-tip {
|
||||
.gift-control-panel .wish-tip,
|
||||
.gift-control-panel .left-part-ctnr {
|
||||
display: none !important;
|
||||
}
|
||||
.gift-control-section,
|
||||
.gift-control-panel {
|
||||
height: 48px !important;
|
||||
}
|
||||
.gift-left-part {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
gap: 6px !important;
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
padding: 10px 0 0 16px !important;
|
||||
.entry-icon {
|
||||
margin: 0 !important;
|
||||
width: 24px !important;
|
||||
height: 24px !important;
|
||||
}
|
||||
.wait-num {
|
||||
display: none !important;
|
||||
}
|
||||
.time-span {
|
||||
margin: 1px 0 0 0 !important;
|
||||
}
|
||||
}
|
||||
.draw-box,
|
||||
.anchor-lottery-entry,
|
||||
.treasure-box {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
padding: 10px 0 0 16px !important;
|
||||
max-height: 36px !important;
|
||||
.draw-box-pic,
|
||||
.anchor-lot-icon,
|
||||
.box-icon {
|
||||
margin: 0 !important;
|
||||
width: 24px !important;
|
||||
height: 24px !important;
|
||||
background-position: 0 -2.5px !important;
|
||||
&.open {
|
||||
background-position: 0 -1px !important;
|
||||
}
|
||||
}
|
||||
.draw-box-pic {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
#BLRHH_treasure_div_tip br {
|
||||
display: none !important;
|
||||
}
|
||||
#BLRHH_treasure_div {
|
||||
img,
|
||||
canvas {
|
||||
max-height: 24px !important;
|
||||
}
|
||||
}
|
||||
.draw-box-text,
|
||||
.anchor-lot-text,
|
||||
.count-down,
|
||||
#BLRHH_treasure_div_tip,
|
||||
#BLRHH_treasure_div_timer {
|
||||
margin-left: 12px !important;
|
||||
padding: 4px 8px !important;
|
||||
max-width: unset !important;
|
||||
}
|
||||
.awarding-panel {
|
||||
bottom: 42px !important;
|
||||
}
|
||||
}
|
||||
.draw-box {
|
||||
padding-top: 11px !important;
|
||||
}
|
||||
.anchor-lottery-entry {
|
||||
padding-top: 12px !important;
|
||||
}
|
||||
.gift-control-panel .right-part {
|
||||
height: 48px !important;
|
||||
display: flex !important;
|
||||
@ -281,6 +214,29 @@ $prefix: 'simplifyLiveroom-switch';
|
||||
}
|
||||
}
|
||||
}
|
||||
.gift-control-panel {
|
||||
.more-gift-section {
|
||||
position: static !important;
|
||||
margin: 0 10px 0 0 !important;
|
||||
height: 48px !important;
|
||||
}
|
||||
.z-gift-package {
|
||||
margin: auto 0 !important;
|
||||
position: static !important;
|
||||
.gift-package {
|
||||
margin-top: 2px;
|
||||
}
|
||||
}
|
||||
.right-part {
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
.right-section > .gift-section {
|
||||
height: 42px !important;
|
||||
}
|
||||
.battery-pic {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.#{$prefix}-guard {
|
||||
.guard-danmaku::before {
|
||||
|
||||
3
registry/lib/components/utils/column-unlock/index.md
Normal file
3
registry/lib/components/utils/column-unlock/index.md
Normal file
@ -0,0 +1,3 @@
|
||||
(原名: 专栏文字选择, 现在专栏已经不限制选中文字了)
|
||||
|
||||
避免专栏的文字复制后在最后带上出处信息, 更贴近原生的复制行为.
|
||||
@ -22,7 +22,7 @@ const entry = async () => {
|
||||
}
|
||||
export const component = defineComponentMetadata({
|
||||
name: 'columnUnlock',
|
||||
displayName: '专栏文字选择',
|
||||
displayName: '专栏复制优化',
|
||||
entry,
|
||||
reload: entry,
|
||||
unload: async () => {
|
||||
@ -30,8 +30,5 @@ export const component = defineComponentMetadata({
|
||||
enable = false
|
||||
},
|
||||
tags: [componentsTags.utils],
|
||||
description: {
|
||||
'zh-CN': '使专栏的文字可以选择.',
|
||||
},
|
||||
urlInclude: ['//www.bilibili.com/read/'],
|
||||
})
|
||||
|
||||
@ -34,8 +34,6 @@ export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
client: null,
|
||||
// sessions: [],
|
||||
// devRecords: options.devRecords,
|
||||
isConnected: false,
|
||||
}
|
||||
},
|
||||
@ -44,12 +42,10 @@ export default Vue.extend({
|
||||
this.client = devClient
|
||||
this.updateConnectionStatus()
|
||||
devClient.addEventListener(DevClientEvents.ServerChange, this.updateConnectionStatus)
|
||||
// devClient.addEventListener(DevClientEvents.SessionsUpdate, this.updateSessionsStatus)
|
||||
},
|
||||
beforeDestroy() {
|
||||
const devClient = this.client as DevClient
|
||||
devClient.removeEventListener(DevClientEvents.ServerChange, this.updateConnectionStatus)
|
||||
// devClient.removeEventListener(DevClientEvents.SessionsUpdate, this.updateSessionsStatus)
|
||||
},
|
||||
methods: {
|
||||
async connect() {
|
||||
@ -61,9 +57,6 @@ export default Vue.extend({
|
||||
updateConnectionStatus() {
|
||||
this.isConnected = this.client.isConnected
|
||||
},
|
||||
updateSessionsStatus() {
|
||||
this.sessions = [...this.client.sessions]
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -19,7 +19,7 @@ const importSeries = async (sid, uid, csrf) => {
|
||||
|
||||
// 创建收藏夹,获取新收藏夹id
|
||||
let favId = 0
|
||||
while (true) {
|
||||
for (;;) {
|
||||
const response = await fetch('https://api.bilibili.com/x/v3/fav/folder/add', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
@ -43,7 +43,7 @@ const importSeries = async (sid, uid, csrf) => {
|
||||
for (let i = 0; i < seriesVideos.length; i++) {
|
||||
// 做个延迟,防止太快而遭服务器拒绝
|
||||
await delay(500)
|
||||
while (true) {
|
||||
for (;;) {
|
||||
const response = await fetch('https://api.bilibili.com/x/v3/fav/resource/deal', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
@ -72,7 +72,7 @@ const importSeries = async (sid, uid, csrf) => {
|
||||
}
|
||||
|
||||
const importCollection = async (sid, csrf) => {
|
||||
while (true) {
|
||||
for (;;) {
|
||||
const response = await fetch('https://api.bilibili.com/x/v3/fav/season/fav', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
{{ row.displayName }}
|
||||
</div>
|
||||
<div
|
||||
class="row-default-binding"
|
||||
class="row-default-binding row-binding"
|
||||
:class="{
|
||||
overwritten: isOverwrittern(presets[selectedPreset], customKeyBindings),
|
||||
'not-set': presetBase[row.name] === undefined,
|
||||
@ -13,7 +13,7 @@
|
||||
{{ showReadonlyKey(presetBase) }}
|
||||
</div>
|
||||
<div
|
||||
class="row-preset-binding"
|
||||
class="row-preset-binding row-binding"
|
||||
:class="{
|
||||
overwritten: isOverwrittern(customKeyBindings),
|
||||
'not-set': presets[selectedPreset][row.name] === undefined,
|
||||
@ -21,7 +21,7 @@
|
||||
>
|
||||
{{ showReadonlyKey(presets[selectedPreset]) }}
|
||||
</div>
|
||||
<div class="row-custom-binding">
|
||||
<div class="row-custom-binding row-binding">
|
||||
<div v-if="editable" class="custom-binding-edit">
|
||||
<TextBox
|
||||
ref="customBindingTextBox"
|
||||
@ -129,6 +129,12 @@ export default Vue.extend({
|
||||
opacity: 0.25;
|
||||
}
|
||||
}
|
||||
.row-binding {
|
||||
&,
|
||||
& input {
|
||||
font-family: monospace, sans-serif;
|
||||
}
|
||||
}
|
||||
.row-custom-binding {
|
||||
.be-button {
|
||||
padding: 4px;
|
||||
|
||||
@ -84,6 +84,9 @@ body:not(.preserve-feed-goods) .bili-dyn-content__orig__additional:has(.dyn-good
|
||||
}
|
||||
.bili-video-card__wrap {
|
||||
a[href*="cm.bilibili.com"] {
|
||||
pointer-events: none !important;
|
||||
cursor: default !important;
|
||||
|
||||
> * {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
1
registry/lib/components/utils/url-params-clean/index.md
Normal file
1
registry/lib/components/utils/url-params-clean/index.md
Normal file
@ -0,0 +1 @@
|
||||
自动删除网址中的多余跟踪参数. 请注意这会导致浏览器历史记录出现重复的标题 (分别是转换前后的网址), 并可能导致后退要多退几次.
|
||||
@ -49,6 +49,7 @@ const entry = async () => {
|
||||
'vd_source',
|
||||
'is_story_h5',
|
||||
'buvid',
|
||||
'plat_id',
|
||||
]
|
||||
const [blockParams] = registerAndGetData('urlParamsClean.params', builtInBlockParams)
|
||||
const builtInSiteSpecifiedParams = [
|
||||
@ -80,10 +81,11 @@ const entry = async () => {
|
||||
const builtInTailingSlash: { match: string | RegExp }[] = []
|
||||
const [tailingSlash] = registerAndGetData('urlParamsClean.tailingSlash', builtInTailingSlash)
|
||||
|
||||
const clean = () => {
|
||||
const urlParams = window.location.search.substring(1).split('&')
|
||||
const getCleanUrl = (originalUrl: string) => {
|
||||
const url = new URL(originalUrl, location.origin)
|
||||
const urlParams = url.search.substring(1).split('&')
|
||||
if (urlParams.some(param => noClean.some(it => param.includes(it)))) {
|
||||
return
|
||||
return originalUrl
|
||||
}
|
||||
const filteredParams = urlParams.filter(p => {
|
||||
if (blockParams.some(b => p.startsWith(`${b}=`))) {
|
||||
@ -99,19 +101,47 @@ const entry = async () => {
|
||||
return true
|
||||
})
|
||||
const filteredParamsString = filteredParams.join('&')
|
||||
let url = document.URL.replace(window.location.search, '')
|
||||
tailingSlash.forEach(({ match }) => {
|
||||
if (matchPattern(url, match) && url.endsWith('/')) {
|
||||
url = url.slice(0, url.length - 1)
|
||||
const pathName = url.pathname
|
||||
if (matchPattern(pathName, match) && pathName.endsWith('/')) {
|
||||
url.pathname = pathName.slice(0, pathName.length - 1)
|
||||
}
|
||||
})
|
||||
const query = filteredParamsString ? `?${filteredParamsString}` : ''
|
||||
const newUrl = url + query
|
||||
url.search = query
|
||||
return url.toString()
|
||||
}
|
||||
|
||||
const createHistoryHook = (
|
||||
original: (data: unknown, unused: string, url: string, ...restArgs: unknown[]) => void,
|
||||
) => {
|
||||
return function historyHook(
|
||||
data: unknown,
|
||||
unused: string,
|
||||
url: string,
|
||||
...restArgs: unknown[]
|
||||
) {
|
||||
const newUrl = getCleanUrl(url)
|
||||
if (newUrl !== url) {
|
||||
console.log('History API 拦截', url, newUrl)
|
||||
return original.call(this, data, unused, newUrl, ...restArgs)
|
||||
}
|
||||
return original.call(this, data, unused, url, ...restArgs)
|
||||
}
|
||||
}
|
||||
const originalPushState = unsafeWindow.history.pushState
|
||||
unsafeWindow.history.pushState = createHistoryHook(originalPushState)
|
||||
const originalReplaceState = unsafeWindow.history.replaceState
|
||||
unsafeWindow.history.replaceState = createHistoryHook(originalReplaceState)
|
||||
|
||||
const clean = () => {
|
||||
const newUrl = getCleanUrl(document.URL)
|
||||
if (newUrl !== document.URL) {
|
||||
console.log(document.URL, newUrl)
|
||||
console.log('直接清理', document.URL, newUrl)
|
||||
window.history.replaceState(history.state, '', newUrl)
|
||||
}
|
||||
}
|
||||
|
||||
const { fullyLoaded } = await import('@/core/life-cycle')
|
||||
const { urlChange } = await import('@/core/observer')
|
||||
fullyLoaded(() => {
|
||||
@ -122,10 +152,6 @@ export const component = defineComponentMetadata({
|
||||
name: 'urlParamsClean',
|
||||
displayName,
|
||||
entry,
|
||||
description: {
|
||||
'zh-CN':
|
||||
'自动删除网址中的多余跟踪参数. 请注意这会导致浏览器历史记录出现重复的标题 (分别是转换前后的网址), 并可能导致后退要多退几次.',
|
||||
},
|
||||
tags: [componentsTags.utils],
|
||||
urlExclude: [/game\.bilibili\.com\/fgo/, /live\.bilibili\.com\/p\/html\/live-app-hotrank\//],
|
||||
})
|
||||
|
||||
@ -18,6 +18,9 @@ body {
|
||||
line-height: 1.5 !important;
|
||||
}
|
||||
}
|
||||
.video-section-list[style]:not([style*='height: 0px']) {
|
||||
height: auto !important;
|
||||
}
|
||||
}
|
||||
.multi-page-v1,
|
||||
.multi-page {
|
||||
@ -43,7 +46,7 @@ body {
|
||||
.video-sections-content-list {
|
||||
max-height: unset !important;
|
||||
height: auto !important;
|
||||
.video-section-list:not([style*="height: 0px"]) {
|
||||
.video-section-list[style]:not([style*='height: 0px']) {
|
||||
height: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
64
registry/lib/components/video/player/auto-light/animation.ts
Normal file
64
registry/lib/components/video/player/auto-light/animation.ts
Normal file
@ -0,0 +1,64 @@
|
||||
// author: https://github.com/z503722728
|
||||
|
||||
const CreateAnim = (): void => {
|
||||
const biliMainHeader = document.getElementById('biliMainHeader')
|
||||
if (biliMainHeader == null) {
|
||||
return
|
||||
}
|
||||
const mstars1 = document.createElement('div')
|
||||
mstars1.id = 'mstars1'
|
||||
const mstars2 = document.createElement('div')
|
||||
mstars2.id = 'mstars2'
|
||||
biliMainHeader.appendChild(mstars1)
|
||||
biliMainHeader.appendChild(mstars2)
|
||||
|
||||
// 添加一段css 样式到document最后
|
||||
const style = document.createElement('style')
|
||||
// generate random stars
|
||||
function generate(numCtrl) {
|
||||
let star = ''
|
||||
const max = window.innerWidth * window.innerHeight
|
||||
for (let i = 0; i < max / numCtrl; i++) {
|
||||
const x = Math.floor(Math.random() * window.innerWidth * 1.5)
|
||||
const y = Math.floor(Math.random() * (window.innerHeight + 2000))
|
||||
star += `${x}px ${y}px #FFF,`
|
||||
}
|
||||
const x = Math.floor(Math.random() * window.innerWidth * 1.5)
|
||||
const y = Math.floor(Math.random() * (window.innerHeight + 2000))
|
||||
star += `${x}px ${y}px #FFF;`
|
||||
return star
|
||||
}
|
||||
const starNumCtl = 400
|
||||
const stars1Shadow = generate(starNumCtl)
|
||||
const stars2Shadow = generate(starNumCtl * 2)
|
||||
const stars3Shadow = generate(starNumCtl * 4)
|
||||
const stars4Shadow = generate(starNumCtl * 8)
|
||||
style.innerHTML = `
|
||||
#mstars1{z-index: 1009;position: fixed;left:0px; width:1px;height:1px;background:transparent;box-shadow:${stars1Shadow};animation:animStar 50s linear infinite}
|
||||
#mstars1:after{content:' ';position:fixed;left:0px;top:0px;width:1px;height:1px;background:transparent;box-shadow:${stars2Shadow}}
|
||||
#mstars2{z-index: 1009;position: fixed;left:0px;width:2px;height:2px;background:transparent;box-shadow:${stars3Shadow};animation:animStar 100s linear infinite}
|
||||
#mstars2:after{content:' ';position:fixed;left:0px;top:0px;width:2px;height:2px;background:transparent;box-shadow:${stars4Shadow}}
|
||||
@keyframes animStar{from{transform:translateY(-200px)}to{transform:translateY(-2200px)}}
|
||||
`
|
||||
document.body.appendChild(style)
|
||||
}
|
||||
|
||||
export const StarAnim = (on: boolean) => {
|
||||
// 查找id mstars1 的div
|
||||
let mstars1 = document.getElementById('mstars1')
|
||||
let mstars2 = document.getElementById('mstars2')
|
||||
// 如果没有找到id biliMainHeader 的div创建2个id为 mstars1 mstars2 的div
|
||||
if (on) {
|
||||
if (mstars1 == null) {
|
||||
CreateAnim()
|
||||
mstars1 = document.getElementById('mstars1')
|
||||
mstars2 = document.getElementById('mstars2')
|
||||
}
|
||||
// 设置mstars1 mstars2 visible 为true
|
||||
mstars1.style.visibility = 'visible'
|
||||
mstars2.style.visibility = 'visible'
|
||||
} else if (mstars1 != null) {
|
||||
mstars1.style.visibility = 'hidden'
|
||||
mstars2.style.visibility = 'hidden'
|
||||
}
|
||||
}
|
||||
@ -1,44 +1,64 @@
|
||||
import { defineComponentMetadata } from '@/components/define'
|
||||
import { playerAgent } from '@/components/video/player-agent'
|
||||
import { lightOn, lightOff } from '@/components/video/player-light'
|
||||
import { videoChange } from '@/core/observer'
|
||||
import { allVideoUrls } from '@/core/utils/urls'
|
||||
import type { PlayerAgent } from '@/components/video/player-agent/base'
|
||||
import { StarAnim } from './animation'
|
||||
import { defineComponentMetadata } from '@/components/define'
|
||||
|
||||
let playerAgentInstance
|
||||
let playerAgentInstance: PlayerAgent
|
||||
|
||||
export const component = defineComponentMetadata({
|
||||
name: 'playerAutoLight',
|
||||
displayName: '播放时自动关灯',
|
||||
urlInclude: allVideoUrls,
|
||||
tags: [componentsTags.video],
|
||||
options: {
|
||||
starAnimation: {
|
||||
defaultValue: true,
|
||||
displayName: '启用星光动画',
|
||||
},
|
||||
},
|
||||
description: {
|
||||
'zh-CN': '在视频播放时自动关灯, 暂停或结束时再自动打开.',
|
||||
},
|
||||
entry: async () => {
|
||||
entry: async ({ settings }) => {
|
||||
const { isEmbeddedPlayer } = await import('@/core/utils')
|
||||
const { lightOn, lightOff } = await import('@/components/video/player-light')
|
||||
|
||||
if (isEmbeddedPlayer()) {
|
||||
return
|
||||
}
|
||||
|
||||
const makeLightOn = () => {
|
||||
lightOn()
|
||||
StarAnim(false)
|
||||
}
|
||||
|
||||
const makeLightOff = () => {
|
||||
lightOff()
|
||||
if (settings.options.starAnimation) {
|
||||
StarAnim(true)
|
||||
}
|
||||
}
|
||||
|
||||
videoChange(async () => {
|
||||
if (playerAgentInstance != null) {
|
||||
const oldVideo = await playerAgentInstance.query.video.element()
|
||||
oldVideo.removeEventListener('ended', lightOn)
|
||||
oldVideo.removeEventListener('pause', lightOn)
|
||||
oldVideo.removeEventListener('play', lightOff)
|
||||
oldVideo.removeEventListener('ended', makeLightOn)
|
||||
oldVideo.removeEventListener('pause', makeLightOn)
|
||||
oldVideo.removeEventListener('play', makeLightOff)
|
||||
}
|
||||
|
||||
playerAgentInstance = playerAgent
|
||||
const video = await playerAgentInstance.query.video.element()
|
||||
|
||||
if (playerAgentInstance.isAutoPlay()) {
|
||||
lightOff()
|
||||
makeLightOff()
|
||||
}
|
||||
|
||||
video.addEventListener('ended', lightOn)
|
||||
video.addEventListener('pause', lightOn)
|
||||
video.addEventListener('play', lightOff)
|
||||
video.addEventListener('ended', makeLightOn)
|
||||
video.addEventListener('pause', makeLightOn)
|
||||
video.addEventListener('play', makeLightOff)
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
@ -444,6 +444,7 @@ export class ExtendSpeedComponent extends EntrySpeedComponent<Options> {
|
||||
setTimeout(() => this.forceUpdateStyle(value))
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
protected readonly filterNativeSpeed =
|
||||
() =>
|
||||
({ subscribe, next }: PublishContext<number>) => {
|
||||
|
||||
@ -9,7 +9,6 @@ export const pack: Package = {
|
||||
'downloadSubtitle',
|
||||
'downloadDanmaku',
|
||||
'downloadAudio',
|
||||
'downloadLiveRecords',
|
||||
],
|
||||
|
||||
}
|
||||
|
||||
1
registry/lib/plugins/launch-bar/audio-search/index.md
Normal file
1
registry/lib/plugins/launch-bar/audio-search/index.md
Normal file
@ -0,0 +1 @@
|
||||
在输入音频的 au 号或播放列表的 am 号时, 提供对应的跳转选项.
|
||||
36
registry/lib/plugins/launch-bar/audio-search/index.ts
Normal file
36
registry/lib/plugins/launch-bar/audio-search/index.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import type { LaunchBarActionProvider } from '@/components/launch-bar/launch-bar-action'
|
||||
import type { PluginMetadata } from '@/plugins/plugin'
|
||||
import { getJson } from '@/core/ajax'
|
||||
import { createLinkAction, matchInput } from '../common'
|
||||
|
||||
export const plugin: PluginMetadata = {
|
||||
name: 'launchBar.actions.audioSearch',
|
||||
displayName: '搜索栏 - 音频跳转',
|
||||
async setup({ addData }) {
|
||||
addData('launchBar.actions', (providers: LaunchBarActionProvider[]) => {
|
||||
providers.push({
|
||||
name: 'audioSearchProvider',
|
||||
getActions: async input => {
|
||||
const { match, type, id, indexer } = matchInput(input, /^(a[um])(\d+)$/)
|
||||
if (!match) {
|
||||
return []
|
||||
}
|
||||
const json = await getJson(
|
||||
type === 'am'
|
||||
? `https://www.bilibili.com/audio/music-service-c/web/menu/info?sid=${id}`
|
||||
: `https://www.bilibili.com/audio/music-service-c/web/song/info?sid=${id}`,
|
||||
)
|
||||
const { title } = lodash.get(json, 'data', {})
|
||||
return [
|
||||
createLinkAction({
|
||||
name: title,
|
||||
description: type === 'am' ? '播放列表跳转' : '音频跳转',
|
||||
link: `https://www.bilibili.com/audio/${indexer}`,
|
||||
indexer,
|
||||
}),
|
||||
]
|
||||
},
|
||||
})
|
||||
})
|
||||
},
|
||||
}
|
||||
1
registry/lib/plugins/launch-bar/bangumi-search/index.md
Normal file
1
registry/lib/plugins/launch-bar/bangumi-search/index.md
Normal file
@ -0,0 +1 @@
|
||||
在输入番剧的 ss 号 / ep 号, 或番剧详情的 md 号时, 提供对应的跳转选项.
|
||||
60
registry/lib/plugins/launch-bar/bangumi-search/index.ts
Normal file
60
registry/lib/plugins/launch-bar/bangumi-search/index.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import type { LaunchBarActionProvider } from '@/components/launch-bar/launch-bar-action'
|
||||
import type { PluginMetadata } from '@/plugins/plugin'
|
||||
import { getJson } from '@/core/ajax'
|
||||
import { createLinkAction, matchInput } from '../common'
|
||||
|
||||
export const plugin: PluginMetadata = {
|
||||
name: 'launchBar.actions.bangumiSearch',
|
||||
displayName: '搜索栏 - 番剧跳转',
|
||||
async setup({ addData }) {
|
||||
addData('launchBar.actions', (providers: LaunchBarActionProvider[]) => {
|
||||
providers.push({
|
||||
name: 'bangumiSearchProvider',
|
||||
getActions: async input => {
|
||||
const { match, type, id, indexer } = matchInput(input, /^(md|ss|ep)(\d+)$/)
|
||||
if (!match) {
|
||||
return []
|
||||
}
|
||||
if (type === 'md') {
|
||||
const json = await getJson(`https://api.bilibili.com/pgc/review/user?media_id=${id}`)
|
||||
const { title } = lodash.get(json, 'result.media', {})
|
||||
return [
|
||||
createLinkAction({
|
||||
name: title,
|
||||
description: '番剧详情跳转',
|
||||
link: `https://www.bilibili.com/bangumi/media/${indexer}`,
|
||||
indexer,
|
||||
}),
|
||||
]
|
||||
}
|
||||
if (type === 'ep') {
|
||||
const json = await getJson(`https://api.bilibili.com/pgc/view/web/season?ep_id=${id}`)
|
||||
const episodes = lodash.get(json, 'result.episodes', []) as {
|
||||
id: number
|
||||
share_copy: string
|
||||
}[]
|
||||
const episode = episodes.find(e => e.id.toString() === id)
|
||||
return [
|
||||
createLinkAction({
|
||||
name: episode?.share_copy,
|
||||
description: '番剧跳转',
|
||||
link: `https://www.bilibili.com/bangumi/play/${indexer}`,
|
||||
indexer,
|
||||
}),
|
||||
]
|
||||
}
|
||||
const json = await getJson(`https://api.bilibili.com/pgc/view/web/season?season_id=${id}`)
|
||||
const { title } = lodash.get(json, 'result', {})
|
||||
return [
|
||||
createLinkAction({
|
||||
name: title,
|
||||
description: '番剧跳转',
|
||||
link: `https://www.bilibili.com/bangumi/play/${indexer}`,
|
||||
indexer,
|
||||
}),
|
||||
]
|
||||
},
|
||||
})
|
||||
})
|
||||
},
|
||||
}
|
||||
34
registry/lib/plugins/launch-bar/common.ts
Normal file
34
registry/lib/plugins/launch-bar/common.ts
Normal file
@ -0,0 +1,34 @@
|
||||
export const matchInput = (input: string, pattern: RegExp) => {
|
||||
const match = input.match(pattern)
|
||||
if (!match) {
|
||||
return {}
|
||||
}
|
||||
const type = match[1]
|
||||
const id = match[2]
|
||||
const indexer = `${type}${id}`
|
||||
return {
|
||||
match,
|
||||
type,
|
||||
id,
|
||||
indexer,
|
||||
}
|
||||
}
|
||||
|
||||
export const createLinkAction = (input: {
|
||||
name: string
|
||||
description?: string
|
||||
indexer: string
|
||||
link: string
|
||||
}) => {
|
||||
const { name, description, indexer, link } = input
|
||||
return {
|
||||
name: name || indexer,
|
||||
icon: 'mdi-open-in-new',
|
||||
indexer,
|
||||
description,
|
||||
action: () => {
|
||||
window.open(link, '_blank')
|
||||
},
|
||||
order: 0,
|
||||
}
|
||||
}
|
||||
1
registry/lib/plugins/launch-bar/cv-search/index.md
Normal file
1
registry/lib/plugins/launch-bar/cv-search/index.md
Normal file
@ -0,0 +1 @@
|
||||
在输入专栏的 cv 号或专栏文集的 rl 号时, 提供对应的跳转选项.
|
||||
46
registry/lib/plugins/launch-bar/cv-search/index.ts
Normal file
46
registry/lib/plugins/launch-bar/cv-search/index.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import type { LaunchBarActionProvider } from '@/components/launch-bar/launch-bar-action'
|
||||
import type { PluginMetadata } from '@/plugins/plugin'
|
||||
import { getJson } from '@/core/ajax'
|
||||
import { createLinkAction, matchInput } from '../common'
|
||||
|
||||
export const plugin: PluginMetadata = {
|
||||
name: 'launchBar.actions.cvSearch',
|
||||
displayName: '搜索栏 - 专栏跳转',
|
||||
async setup({ addData }) {
|
||||
addData('launchBar.actions', (providers: LaunchBarActionProvider[]) => {
|
||||
providers.push({
|
||||
name: 'cvSearchProvider',
|
||||
getActions: async input => {
|
||||
const { match, type, id, indexer } = matchInput(input, /^(cv|rl)(\d+)$/)
|
||||
if (!match) {
|
||||
return []
|
||||
}
|
||||
if (type === 'rl') {
|
||||
const json = await getJson(
|
||||
`https://api.bilibili.com/x/article/list/web/articles?id=${id}`,
|
||||
)
|
||||
const { name } = lodash.get(json, 'data.list', {})
|
||||
return [
|
||||
createLinkAction({
|
||||
name,
|
||||
description: '文集跳转',
|
||||
link: `https://www.bilibili.com/read/readlist/${indexer}`,
|
||||
indexer,
|
||||
}),
|
||||
]
|
||||
}
|
||||
const json = await getJson(`https://api.bilibili.com/x/article/viewinfo?id=${id}`)
|
||||
const { title } = lodash.get(json, 'data', {})
|
||||
return [
|
||||
createLinkAction({
|
||||
name: title,
|
||||
description: '专栏跳转',
|
||||
link: `https://www.bilibili.com/read/${indexer}`,
|
||||
indexer,
|
||||
}),
|
||||
]
|
||||
},
|
||||
})
|
||||
})
|
||||
},
|
||||
}
|
||||
@ -16,8 +16,16 @@ export const plugin: PluginMetadata = {
|
||||
},
|
||||
}
|
||||
})
|
||||
addData('keymap.presets', (presetBase: Record<string, string>) => {
|
||||
presetBase.toggleDanmakuList = 'shift d'
|
||||
})
|
||||
addData(
|
||||
'keymap.presets',
|
||||
(
|
||||
presetBase: Record<string, string>,
|
||||
builtInPresets: Record<string, Record<string, string>>,
|
||||
) => {
|
||||
presetBase.toggleDanmakuList = 'shift d'
|
||||
builtInPresets.HTML5Player.toggleDanmakuList = ''
|
||||
builtInPresets.PotPlayer.toggleDanmakuList = ''
|
||||
},
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
@ -0,0 +1,22 @@
|
||||
import { KeyBindingAction } from 'registry/lib/components/utils/keymap/bindings'
|
||||
import { PluginMetadata } from '@/plugins/plugin'
|
||||
import { toggleLight } from '@/components/video/player-light'
|
||||
|
||||
export const plugin: PluginMetadata = {
|
||||
name: 'keymap.actions.togglePlayerLight',
|
||||
displayName: '快捷键扩展 - 开关灯',
|
||||
description: '在快捷键的动作列表里添加一个 "开关灯".',
|
||||
setup: ({ addData }) => {
|
||||
addData('keymap.actions', (actions: Record<string, KeyBindingAction>) => {
|
||||
actions.togglePlayerLight = {
|
||||
displayName: '开关灯',
|
||||
run: async () => {
|
||||
toggleLight()
|
||||
},
|
||||
}
|
||||
})
|
||||
addData('keymap.presets', (presetBase: Record<string, string>) => {
|
||||
presetBase.togglePlayerLight = 'shift l'
|
||||
})
|
||||
},
|
||||
}
|
||||
@ -17,8 +17,17 @@ export const plugin: PluginMetadata = {
|
||||
},
|
||||
}
|
||||
})
|
||||
addData('keymap.presets', (presetBase: Record<string, string>) => {
|
||||
presetBase.toggleSubtitle = 'shift c'
|
||||
})
|
||||
addData(
|
||||
'keymap.presets',
|
||||
(
|
||||
presetBase: Record<string, string>,
|
||||
builtInPresets: Record<string, Record<string, string>>,
|
||||
) => {
|
||||
presetBase.toggleSubtitle = 'shift c'
|
||||
builtInPresets.YouTube.toggleSubtitle = 'c'
|
||||
builtInPresets.YouTube.coin = ''
|
||||
builtInPresets.PotPlayer.toggleSubtitle = 'alt h'
|
||||
},
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "2.6.3",
|
||||
"version": "2.7.0",
|
||||
"author": "Grant Howard, Coulomb-G",
|
||||
"copyright": "[year], Grant Howard (https://github.com/the1812) & Coulomb-G (https://github.com/Coulomb-G)",
|
||||
"license": "MIT",
|
||||
|
||||
@ -34,6 +34,11 @@ export const init = async () => {
|
||||
compatibilityPatch()
|
||||
})
|
||||
|
||||
if (unsafeWindow.bangumi_area_limit_hack) {
|
||||
console.log('BALH detected, Bilibili Evolved is disabled.')
|
||||
return
|
||||
}
|
||||
|
||||
const { coreApis, externalApis } = await import('@/core/core-apis')
|
||||
unsafeWindow.bilibiliEvolved = externalApis
|
||||
/** sand-boxed window, safe to use original name */
|
||||
|
||||
69
src/components/bisector/DialogContent.vue
Normal file
69
src/components/bisector/DialogContent.vue
Normal file
@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<div class="bisector-dialog-content-container">
|
||||
<div class="bisector-dialog-content-text">
|
||||
组件二等分进行中,预计剩余
|
||||
<b style="color: var(--theme-color)">{{ rouge }} </b>轮次,请确认当前情况:
|
||||
</div>
|
||||
<div class="bisector-dialog-button-group">
|
||||
<VButton @click="onGood">正常</VButton>
|
||||
<VButton @click="onBad">不正常</VButton>
|
||||
<VButton type="primary" @click="onAbort">终止</VButton>
|
||||
</div>
|
||||
<div class="bisector-dialog-content-text weak">
|
||||
*可以点击×号关闭对话框,刷新页面后,会再次询问
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
import { VButton } from '@/ui'
|
||||
|
||||
export default Vue.extend({
|
||||
components: { VButton },
|
||||
props: {
|
||||
rouge: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
onAbort: {
|
||||
type: Function,
|
||||
default: none,
|
||||
},
|
||||
onGood: {
|
||||
type: Function,
|
||||
default: none,
|
||||
},
|
||||
onBad: {
|
||||
type: Function,
|
||||
default: none,
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.bisector-dialog-content-container {
|
||||
padding: 0 16px;
|
||||
|
||||
.bisector-dialog-content-text {
|
||||
&.weak {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.bisector-dialog-content-text,
|
||||
.bisector-dialog-button-group {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.bisector-dialog-button-group {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
.be-button:last-child {
|
||||
grid-column: span 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
43
src/components/bisector/DialogTitle.vue
Normal file
43
src/components/bisector/DialogTitle.vue
Normal file
@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<div>
|
||||
组件二等分
|
||||
<div
|
||||
class="peek"
|
||||
title="透视"
|
||||
style="margin-left: auto"
|
||||
@mouseover="peek = true"
|
||||
@mouseout="peek = false"
|
||||
>
|
||||
<VIcon icon="eye" :size="18" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
import { VIcon } from '@/ui'
|
||||
|
||||
export default Vue.extend({
|
||||
components: { VIcon },
|
||||
data() {
|
||||
return { peek: false }
|
||||
},
|
||||
watch: {
|
||||
peek(value) {
|
||||
this.$el.closest('.be-dialog').style.opacity = value ? '0.1' : '1'
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import 'common';
|
||||
|
||||
div {
|
||||
@include h-center(8px);
|
||||
}
|
||||
|
||||
.peek {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
54
src/components/bisector/ResultToastContent.vue
Normal file
54
src/components/bisector/ResultToastContent.vue
Normal file
@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<div>
|
||||
<div>可能出问题的组件是:{{ displayName }}({{ name }})</div>
|
||||
<div>
|
||||
<span class="link" @click="restore">恢复原状({{ countdown }} 秒)</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
|
||||
export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
countdown: 30,
|
||||
userComponent: undefined,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
displayName() {
|
||||
return this.userComponent?.metadata?.displayName
|
||||
},
|
||||
name() {
|
||||
return this.userComponent?.metadata?.name
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
countdown(value) {
|
||||
if (value === 0) {
|
||||
this.restore()
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.interval = setInterval(() => {
|
||||
if (this.countdown > 0) {
|
||||
this.countdown--
|
||||
} else {
|
||||
clearInterval(this.interval)
|
||||
}
|
||||
}, 1e3)
|
||||
},
|
||||
destroyed() {
|
||||
clearInterval(this.interval)
|
||||
},
|
||||
methods: {
|
||||
restore() {
|
||||
clearInterval(this.interval)
|
||||
this.$emit('restore')
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
198
src/components/bisector/api.ts
Normal file
198
src/components/bisector/api.ts
Normal file
@ -0,0 +1,198 @@
|
||||
import { DialogInstance, showDialog } from '@/core/dialog'
|
||||
import type { Settings } from '@/core/settings/types'
|
||||
import { Toast } from '@/core/toast'
|
||||
import { getRandomId, mountVueComponent, delay } from '@/core/utils'
|
||||
import { useScopedConsole } from '@/core/utils/log'
|
||||
import type { RecordValue } from '../types'
|
||||
import type { BisectNext, BisectReturn } from './bisect'
|
||||
import { bisect } from './bisect'
|
||||
import { BisectorOptions } from './options'
|
||||
import ResultToastContent from './ResultToastContent.vue'
|
||||
|
||||
type UserComponent = RecordValue<Settings['userComponents']>
|
||||
|
||||
let bisectorOptions: BisectorOptions
|
||||
let scopedConsole: ReturnType<typeof useScopedConsole>
|
||||
let bisectorGenerator: Generator<BisectNext<UserComponent>, BisectReturn<UserComponent>>
|
||||
let groupedComponents: Awaited<ReturnType<typeof classifyComponents>>
|
||||
let dialog: DialogInstance
|
||||
|
||||
export const setOptions = (options: BisectorOptions) => {
|
||||
if (!bisectorOptions) {
|
||||
bisectorOptions = options
|
||||
} else {
|
||||
Object.assign(bisectorOptions, options)
|
||||
}
|
||||
}
|
||||
|
||||
export const setConsole = (console: ReturnType<typeof useScopedConsole>) => {
|
||||
scopedConsole = console
|
||||
}
|
||||
|
||||
const classifyComponents = async () => {
|
||||
const { settings } = await import('@/core/settings')
|
||||
|
||||
const { userComponents } = settings
|
||||
const configurableUserComponents = lodash.pickBy(
|
||||
userComponents,
|
||||
v => v.metadata.configurable || true,
|
||||
)
|
||||
const { targetComponents, keepDisabledComponents, keepEnabledComponents } = lodash.transform(
|
||||
configurableUserComponents,
|
||||
(result, v, k) => {
|
||||
if (bisectorOptions.keepEnabledComponents?.includes(k)) {
|
||||
result.keepEnabledComponents.push(v)
|
||||
return
|
||||
}
|
||||
if (bisectorOptions.keepDisabledComponents?.includes(k)) {
|
||||
result.keepDisabledComponents.push(v)
|
||||
return
|
||||
}
|
||||
result.targetComponents.push(v)
|
||||
},
|
||||
{
|
||||
targetComponents: [] as UserComponent[],
|
||||
keepDisabledComponents: [] as UserComponent[],
|
||||
keepEnabledComponents: [] as UserComponent[],
|
||||
},
|
||||
)
|
||||
|
||||
return {
|
||||
userComponents,
|
||||
configurableUserComponents,
|
||||
targetComponents,
|
||||
keepDisabledComponents,
|
||||
keepEnabledComponents,
|
||||
}
|
||||
}
|
||||
|
||||
const setComponentsEnabled = (components: UserComponent[], enabled: boolean) =>
|
||||
components.forEach(component => (component.settings.enabled = enabled))
|
||||
|
||||
const getComponentNames = (components: UserComponent[]) =>
|
||||
components
|
||||
.map(component => `${component.metadata.displayName}(${component.metadata.name})`)
|
||||
.join(', ') || '无'
|
||||
|
||||
export const isRecover = () => !lodash.isEmpty(bisectorOptions.bisectInitialState)
|
||||
|
||||
export const stop = async () => {
|
||||
scopedConsole?.log('stop - 准备停止组件二等分')
|
||||
dialog?.close()
|
||||
const { configurableUserComponents } = await classifyComponents()
|
||||
const unmatchedComponents: UserComponent[] = []
|
||||
for (const [componentName, componentSettings] of Object.entries(configurableUserComponents)) {
|
||||
const originalStatus = bisectorOptions.originalComponentEnableState?.[componentName]
|
||||
if (originalStatus == null) {
|
||||
unmatchedComponents.push(componentSettings)
|
||||
continue
|
||||
}
|
||||
componentSettings.settings.enabled = originalStatus
|
||||
}
|
||||
if (unmatchedComponents.length) {
|
||||
const msg = `部分组件未能还原状态:${getComponentNames(unmatchedComponents)}`
|
||||
scopedConsole?.warn(`stop - ${msg}`)
|
||||
Toast.error(msg, '组件二等分')
|
||||
await delay(3e3)
|
||||
}
|
||||
scopedConsole?.log('stop - 清理状态')
|
||||
bisectorGenerator = null
|
||||
bisectorOptions.bisectInitialState = {}
|
||||
bisectorOptions.originalComponentEnableState = {}
|
||||
scopedConsole?.log('stop - 重载页面')
|
||||
location.reload()
|
||||
}
|
||||
|
||||
export const next = async (seeingBad?: boolean, autoReload?: boolean) => {
|
||||
dialog?.close()
|
||||
scopedConsole?.log(
|
||||
`next - 当前工作状态:${
|
||||
// eslint-disable-next-line no-nested-ternary
|
||||
seeingBad == null ? '未知' : seeingBad ? '异常' : '正常'
|
||||
}`,
|
||||
)
|
||||
const { done, value } = bisectorGenerator.next(seeingBad)
|
||||
if (done) {
|
||||
const { low, high } = value
|
||||
bisectorOptions.bisectInitialState = { low, high }
|
||||
const elementId = `bisector-result-toast-content-${getRandomId()}`
|
||||
Toast.info(/* html */ `<div id="${elementId}"></div>`, '组件二等分结果')
|
||||
await delay()
|
||||
const vm = mountVueComponent<{ userComponent: UserComponent }>(
|
||||
ResultToastContent,
|
||||
`#${elementId}`,
|
||||
)
|
||||
vm.userComponent = value.target
|
||||
vm.$on('restore', () => {
|
||||
stop()
|
||||
})
|
||||
} else {
|
||||
const { slice, low, high } = value as BisectNext<UserComponent>
|
||||
const needEnabled = slice
|
||||
const needDisabled = lodash.difference(groupedComponents.targetComponents, slice)
|
||||
bisectorOptions.bisectInitialState = { low, high }
|
||||
scopedConsole?.log(`next - 关闭组件:${getComponentNames(needDisabled)}`)
|
||||
scopedConsole?.log(`next - 开启组件:${getComponentNames(needEnabled)}`)
|
||||
setComponentsEnabled(needDisabled, false)
|
||||
setComponentsEnabled(needEnabled, true)
|
||||
if (autoReload) {
|
||||
scopedConsole?.log('next - 重载页面')
|
||||
location.reload()
|
||||
}
|
||||
}
|
||||
return { done, value }
|
||||
}
|
||||
|
||||
export const recover = async () => {
|
||||
scopedConsole?.log('recover - 准备恢复组件二等分')
|
||||
groupedComponents = await classifyComponents()
|
||||
const { targetComponents, keepDisabledComponents, keepEnabledComponents } = groupedComponents
|
||||
setComponentsEnabled(keepEnabledComponents, true)
|
||||
setComponentsEnabled(keepDisabledComponents, false)
|
||||
scopedConsole?.log(`recover - 保持关闭组件:${getComponentNames(keepDisabledComponents)}`)
|
||||
scopedConsole?.log(`recover - 保持开启组件:${getComponentNames(keepEnabledComponents)}`)
|
||||
scopedConsole?.log(`recover - 全部目标组件:${getComponentNames(targetComponents)}`)
|
||||
bisectorGenerator = bisect(targetComponents, bisectorOptions.bisectInitialState)
|
||||
const { done, value } = await next()
|
||||
if (!done) {
|
||||
const { rouge } = value as BisectNext<UserComponent>
|
||||
dialog = showDialog({
|
||||
title: () => import('./DialogTitle.vue'),
|
||||
content: () => import('./DialogContent.vue'),
|
||||
contentProps: {
|
||||
rouge,
|
||||
onGood: () => next(false, true),
|
||||
onBad: () => next(true, true),
|
||||
onAbort: () => stop(),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const start = async () => {
|
||||
if (isRecover()) {
|
||||
await recover()
|
||||
return
|
||||
}
|
||||
scopedConsole?.log('start - 准备开始组件二等分')
|
||||
groupedComponents = await classifyComponents()
|
||||
const {
|
||||
configurableUserComponents,
|
||||
targetComponents,
|
||||
keepDisabledComponents,
|
||||
keepEnabledComponents,
|
||||
} = groupedComponents
|
||||
scopedConsole?.log('start - 保存组件初始启用状态')
|
||||
bisectorOptions.originalComponentEnableState = lodash.mapValues(
|
||||
configurableUserComponents,
|
||||
v => v.settings.enabled,
|
||||
)
|
||||
setComponentsEnabled(targetComponents, true)
|
||||
setComponentsEnabled(keepEnabledComponents, true)
|
||||
setComponentsEnabled(keepDisabledComponents, false)
|
||||
scopedConsole?.log(`start - 保持关闭组件:${getComponentNames(keepDisabledComponents)}`)
|
||||
scopedConsole?.log(`start - 保持开启组件:${getComponentNames(keepEnabledComponents)}`)
|
||||
scopedConsole?.log(`start - 启用全部目标组件:${getComponentNames(targetComponents)}`)
|
||||
bisectorGenerator = bisect(targetComponents, bisectorOptions.bisectInitialState)
|
||||
await next(undefined, true)
|
||||
}
|
||||
92
src/components/bisector/bisect.ts
Normal file
92
src/components/bisector/bisect.ts
Normal file
@ -0,0 +1,92 @@
|
||||
/* eslint-disable no-bitwise */
|
||||
|
||||
export interface BisectNext<O> {
|
||||
low: number
|
||||
high: number
|
||||
mid: number
|
||||
slice: O[]
|
||||
rouge: number
|
||||
}
|
||||
|
||||
export interface BisectReturn<O> extends BisectNext<O> {
|
||||
target: O
|
||||
}
|
||||
|
||||
export interface InitialState {
|
||||
low?: number
|
||||
high?: number
|
||||
}
|
||||
|
||||
export function* bisectLeft<O>(
|
||||
data: readonly O[],
|
||||
initialState?: InitialState,
|
||||
): Generator<BisectNext<O>, BisectReturn<O>> {
|
||||
let low = initialState?.low ?? 0
|
||||
let high = initialState?.high ?? data.length
|
||||
let mid: number
|
||||
|
||||
while (low + 1 < high) {
|
||||
mid = (low + high) >>> 1
|
||||
|
||||
const seeingBad = yield {
|
||||
low,
|
||||
high,
|
||||
mid,
|
||||
slice: data.slice(low, mid),
|
||||
rouge: Math.trunc(Math.log2(high - low)),
|
||||
}
|
||||
|
||||
if (seeingBad) {
|
||||
high = mid
|
||||
} else {
|
||||
low = mid
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
low,
|
||||
high,
|
||||
mid,
|
||||
slice: data.slice(low, mid),
|
||||
rouge: Math.trunc(Math.log2(high - low)),
|
||||
target: data[low],
|
||||
}
|
||||
}
|
||||
|
||||
export function* bisectRight<O>(
|
||||
data: readonly O[],
|
||||
initialState?: InitialState,
|
||||
): Generator<BisectNext<O>, BisectReturn<O>> {
|
||||
let low = initialState?.low ?? 0
|
||||
let high = initialState?.high ?? data.length
|
||||
let mid = (low + high) >>> 1
|
||||
|
||||
while (low + 1 < high) {
|
||||
mid = (low + high) >>> 1
|
||||
|
||||
const seeingBad = yield {
|
||||
low,
|
||||
high,
|
||||
mid,
|
||||
slice: data.slice(mid, high),
|
||||
rouge: Math.trunc(Math.log2(high - low)),
|
||||
}
|
||||
|
||||
if (seeingBad) {
|
||||
low = mid
|
||||
} else {
|
||||
high = mid
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
low,
|
||||
high,
|
||||
mid,
|
||||
slice: data.slice(low, mid),
|
||||
rouge: Math.trunc(Math.log2(high - low)),
|
||||
target: data[low],
|
||||
}
|
||||
}
|
||||
|
||||
export const bisect = bisectLeft
|
||||
45
src/components/bisector/index.ts
Normal file
45
src/components/bisector/index.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { defineComponentMetadata } from '@/components/define'
|
||||
import { bisectorOptionsMetadata } from './options'
|
||||
import { LifeCycleEventTypes } from '@/core/life-cycle'
|
||||
import { componentsTags } from '@/components/types'
|
||||
import * as bisector from './api'
|
||||
import { useScopedConsole } from '@/core/utils/log'
|
||||
import type { LaunchBarActionProvider } from '../launch-bar/launch-bar-action'
|
||||
|
||||
export const component = defineComponentMetadata({
|
||||
name: 'bisector',
|
||||
displayName: '组件二等分',
|
||||
tags: [componentsTags.general, componentsTags.utils],
|
||||
hidden: true,
|
||||
configurable: false,
|
||||
entry: async ({ settings: { options } }) => {
|
||||
bisector.setOptions(options)
|
||||
bisector.setConsole(useScopedConsole('组件二等分'))
|
||||
unsafeWindow.addEventListener(LifeCycleEventTypes.ComponentsLoaded, () => {
|
||||
if (bisector.isRecover()) {
|
||||
bisector.recover()
|
||||
}
|
||||
})
|
||||
},
|
||||
options: bisectorOptionsMetadata,
|
||||
plugin: {
|
||||
displayName: '组件二等分 - 功能扩展',
|
||||
setup: ({ addData }) => {
|
||||
addData('launchBar.actions', (providers: LaunchBarActionProvider[]) => {
|
||||
providers.push({
|
||||
name: 'bisector-start',
|
||||
getActions: async () => [
|
||||
{
|
||||
name: '开始 / 继续组件二等分',
|
||||
description: 'Start/Continue component bisection',
|
||||
icon: 'mdi-view-split-horizontal',
|
||||
action: async () => {
|
||||
await bisector.start()
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
},
|
||||
},
|
||||
})
|
||||
31
src/components/bisector/options.ts
Normal file
31
src/components/bisector/options.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { defineOptionsMetadata, OptionsOfMetadata } from '@/components/define'
|
||||
import { getComponentSettings } from '@/core/settings'
|
||||
import type { InitialState } from './bisect'
|
||||
|
||||
export const bisectorOptionsMetadata = defineOptionsMetadata({
|
||||
// 原始的组件启用状态
|
||||
originalComponentEnableState: {
|
||||
defaultValue: {} as Record<string, boolean>,
|
||||
hidden: true,
|
||||
},
|
||||
// 保持禁用状态的组件内部名称数组
|
||||
keepDisabledComponents: {
|
||||
defaultValue: [] as string[],
|
||||
hidden: true,
|
||||
},
|
||||
// 保持启用状态的组件内部名称数组
|
||||
keepEnabledComponents: {
|
||||
defaultValue: [] as string[],
|
||||
hidden: true,
|
||||
},
|
||||
// bisect 生成器的初始状态
|
||||
bisectInitialState: {
|
||||
defaultValue: {} as Partial<InitialState>,
|
||||
hidden: true,
|
||||
},
|
||||
})
|
||||
|
||||
export const getBisectorOptions = () =>
|
||||
getComponentSettings<OptionsOfMetadata<typeof bisectorOptionsMetadata>>('bisector').options
|
||||
|
||||
export type BisectorOptions = ReturnType<typeof getBisectorOptions>
|
||||
@ -4,6 +4,7 @@ import { component as LaunchBar } from './launch-bar'
|
||||
import { component as I18n } from './i18n'
|
||||
import { component as AutoUpdate } from './auto-update'
|
||||
import { component as NotifyNewVersion } from './notify-new-version'
|
||||
import { component as Bisector } from './bisector'
|
||||
|
||||
export const getBuiltInComponents = (): ComponentMetadata[] => [
|
||||
SettingsPanel,
|
||||
@ -11,6 +12,7 @@ export const getBuiltInComponents = (): ComponentMetadata[] => [
|
||||
I18n,
|
||||
AutoUpdate,
|
||||
NotifyNewVersion,
|
||||
Bisector,
|
||||
]
|
||||
|
||||
export const isBuiltInComponent = (name: string) =>
|
||||
|
||||
@ -41,8 +41,13 @@ const getText = (dynamicModule: any, cardType: FeedsCardType) => {
|
||||
case feedsCardTypes.column:
|
||||
case feedsCardTypes.video: {
|
||||
const target = major.archive ?? major.pgc ?? major.article
|
||||
const { title, desc } = target
|
||||
typeText = combineText(title, desc)
|
||||
if (target) {
|
||||
const { title, desc } = target
|
||||
typeText = combineText(title, desc)
|
||||
} else if (major.opus) {
|
||||
const { title, summary } = major.opus
|
||||
typeText = combineText(title, summary.text)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,11 +12,15 @@ export class Translator {
|
||||
static map: Map<string, any>
|
||||
static regex: [RegExp, string][]
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
protected accepts = (node: Node) => node.nodeType === Node.ELEMENT_NODE
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
protected getValue = (node: Node) => node.nodeValue
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
protected setValue = (node: Node, value: string) => {
|
||||
node.nodeValue = value
|
||||
}
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
protected getElement = (node: Node) => node as Element
|
||||
translate(node: Node) {
|
||||
let value = this.getValue(node)
|
||||
@ -99,17 +103,23 @@ export class Translator {
|
||||
}
|
||||
}
|
||||
export class TextNodeTranslator extends Translator {
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
accepts = (node: Node) => node.nodeType === Node.TEXT_NODE
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
getElement = (node: Node) => node.parentElement
|
||||
}
|
||||
export class TitleTranslator extends Translator {
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
getValue = (node: Node) => (node as Element).getAttribute('title')
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
setValue = (node: Node, value: string) => {
|
||||
;(node as Element).setAttribute('title', value)
|
||||
}
|
||||
}
|
||||
export class PlaceholderTranslator extends Translator {
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
getValue = (node: Node) => (node as Element).getAttribute('placeholder')
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
setValue = (node: Node, value: string) => {
|
||||
;(node as Element).setAttribute('placeholder', value)
|
||||
}
|
||||
|
||||
@ -50,10 +50,12 @@ export default Vue.extend({
|
||||
if (this.config.getSettings) {
|
||||
this.settings = this.config.getSettings(this.config.item)
|
||||
}
|
||||
Toast.mini(this.$refs.removeConfirmTemplate, this.$refs.removeIcon, {
|
||||
trigger: 'click',
|
||||
hideOnClick: true,
|
||||
})
|
||||
if (this.config.isUserItem) {
|
||||
Toast.mini(this.$refs.removeConfirmTemplate, this.$refs.removeIcon, {
|
||||
trigger: 'click',
|
||||
hideOnClick: true,
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async removeItem() {
|
||||
|
||||
@ -2,10 +2,12 @@
|
||||
* Switch Options API
|
||||
* @module src/components/switch-options
|
||||
*
|
||||
* 通过包装原始的 ComponentMetadata 为组件提供一系列开关选项。
|
||||
* 通过包装原始的 {@link ComponentMetadata} 为组件提供一系列开关选项。
|
||||
* 如果组件未定义 Widget,还会提供一个默认的有相同效果的 Widget。
|
||||
*
|
||||
* API 主要函数是 {@link newSwitchComponentWrapper}。
|
||||
* 包装器使用 {@link newSwitchComponentWrapper} 创建。
|
||||
*
|
||||
* 可以通过 {@link SwitchOptionsOfMetadata} 或 {@link SwitchOptionsOfSwitchMetadata} 从包装器的设置中,获取被包装后的组件的 options 类型
|
||||
*/
|
||||
|
||||
import { getComponentSettings, addComponentListener } from '@/core/settings'
|
||||
@ -39,14 +41,14 @@ export type SwitchItemsMetadata<S extends string> = {
|
||||
}
|
||||
|
||||
/**
|
||||
* 可用于单独定义 SwitchItemsMetadata
|
||||
* 可用于单独定义 {@link SwitchItemsMetadata}
|
||||
*/
|
||||
export const defineSwitchItemsMetadata = <S extends string>(
|
||||
c: SwitchItemsMetadata<S>,
|
||||
): SwitchItemsMetadata<S> => c
|
||||
|
||||
/**
|
||||
* 用于配置 API 的行为。使用 {@link defineSwitchMetadata} 定义。
|
||||
* 用于配置 API 的行为。可以使用 {@link defineSwitchMetadata} 辅助定义。
|
||||
*/
|
||||
export interface SwitchMetadata<N extends string, S extends string> {
|
||||
/**
|
||||
@ -82,7 +84,7 @@ export interface SwitchMetadata<N extends string, S extends string> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 定义一个 {@link SwitchMetadata}
|
||||
* 辅助定义 {@link SwitchMetadata}
|
||||
*/
|
||||
export const defineSwitchMetadata = <N extends string, S extends string>(
|
||||
c: SwitchMetadata<N, S>,
|
||||
@ -113,7 +115,7 @@ export interface SwitchMetadataOption<N extends string, S extends string> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个 SwitchMetadataOption
|
||||
* 创建一个 {@link SwitchMetadataOption}
|
||||
*/
|
||||
const newSwitchMetadataOption = <N extends string, S extends string>(
|
||||
metadata: SwitchMetadata<N, S>,
|
||||
@ -151,7 +153,7 @@ export type SwitchOptions<O extends UnknownOptions, N extends string, S extends
|
||||
} & SwitchItemOptions<S>
|
||||
|
||||
/**
|
||||
* 提取 SwitchOptions 类型
|
||||
* 提取 {@link SwitchOptions} 类型
|
||||
*/
|
||||
export type SwitchOptionsOfSwitchMetadata<
|
||||
O extends UnknownOptions,
|
||||
@ -159,7 +161,7 @@ export type SwitchOptionsOfSwitchMetadata<
|
||||
> = C extends SwitchMetadata<infer N, infer S> ? SwitchOptions<O, N, S> : never
|
||||
|
||||
/**
|
||||
* 提取 SwitchOptions 类型
|
||||
* 提取 {@link SwitchOptions} 类型
|
||||
*/
|
||||
export type SwitchOptionsOfMetadata<
|
||||
M extends OptionsMetadata,
|
||||
@ -178,7 +180,7 @@ export type SwitchOptionsMetadata<
|
||||
/**
|
||||
* 向传入的 `options` 注入 API 所需的内容
|
||||
*
|
||||
* 该函数会直接修改传入的 `options` 自身,并将其返回。
|
||||
* 该函数会返回注入后新 `options` 对象。
|
||||
*/
|
||||
type SwitchOptionsMetadataExtender<S extends string> = <O extends UnknownOptions, N extends string>(
|
||||
options: OptionsMetadata<O>,
|
||||
@ -267,14 +269,12 @@ export type SwitchComponentMetadata<
|
||||
> = ComponentMetadata<SwitchOptions<O, N, S>>
|
||||
|
||||
/**
|
||||
* 用于包装组件元数据,生成开关选项。
|
||||
* 用于包装组件元数据,根据配置生成一系列开关
|
||||
*
|
||||
* 为保障组件功能的正常运行,请勿使用以 'switch-' 开头的 option。
|
||||
*
|
||||
* 若传入组件未定义 Widget,则会注入一个用于控制各开关的 Widget。
|
||||
* 此 Widget 的功能与注入组件的 options 功能相同。
|
||||
*
|
||||
* 被包装后的元数据,其 `entry` 函数是原函数的包装,用于实现相关功能。
|
||||
* 修改内容包括
|
||||
* - 注入一系列 options,具体细节参考 {@link SwitchOptions}
|
||||
* - 若传入组件未定义 Widget,则会注入一个用于控制各开关的 Widget。此 Widget 的功能与注入组件的 options 功能相同
|
||||
* - 包装 `entry`,用于实现相关功能
|
||||
*
|
||||
* 该函数会直接修改传入参数自身,并将其返回。
|
||||
*
|
||||
@ -288,9 +288,7 @@ export type SwitchComponentWrapper<N extends string, S extends string> = <O exte
|
||||
/**
|
||||
* 创建一个 {@link SwitchComponentWrapper}
|
||||
*
|
||||
* 传入参数请使用 {@link defineSwitchMetadata} 定义。
|
||||
*
|
||||
* @param metadata - 相关配置元数据
|
||||
* @param metadata - 相关配置元数据。可使用 {@link defineSwitchMetadata} 在外部定义
|
||||
* @returns 组件包装器
|
||||
*/
|
||||
export const newSwitchComponentWrapper = <N extends string, S extends string>(
|
||||
|
||||
@ -216,3 +216,6 @@ export interface ComponentMetadata<O extends UnknownOptions = UnknownOptions>
|
||||
|
||||
/** 用户组件的非函数基本信息, 用于直接保存为 JSON */
|
||||
export type UserComponentMetadata = Omit<ComponentMetadata, keyof FunctionalMetadata>
|
||||
|
||||
/** 推断 Record 的 Value 类型 */
|
||||
export type RecordValue<R> = R extends Record<any, infer V> ? V : never
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import { componentToSettings } from '@/core/settings'
|
||||
import { isBuiltInComponent } from './built-in-components'
|
||||
import { ComponentMetadata, componentsMap } from './component'
|
||||
import * as bisector from './bisector/api'
|
||||
import { BisectorOptions } from './bisector/options'
|
||||
|
||||
/**
|
||||
* 安装自定义组件
|
||||
@ -132,3 +134,16 @@ export const toggleComponent = async (nameOrDisplayName: string) => {
|
||||
const { displayName } = userComponent.metadata
|
||||
return `已${enabled ? '开启' : '关闭'}组件'${displayName}', 可能需要刷新后才能生效`
|
||||
}
|
||||
|
||||
/**
|
||||
* 二等分自定义组件的开关状态
|
||||
*
|
||||
* @param options 二等分选项
|
||||
* @returns
|
||||
*/
|
||||
export const bisectComponent = async (options?: BisectorOptions) => {
|
||||
if (options) {
|
||||
bisector.setOptions(options)
|
||||
}
|
||||
return bisector
|
||||
}
|
||||
|
||||
@ -70,8 +70,13 @@ export abstract class PlayerAgent {
|
||||
}
|
||||
|
||||
/** true 开灯,false 关灯 */
|
||||
async toggleLight(on: boolean) {
|
||||
async toggleLight(on?: boolean) {
|
||||
const checkbox = (await this.query.control.settings.lightOff()) as HTMLInputElement
|
||||
// 无指定参数, 直接 toggle
|
||||
if (on === undefined) {
|
||||
checkbox.click()
|
||||
return
|
||||
}
|
||||
// 关灯状态 && 要开灯 -> 开灯
|
||||
checkbox.checked && on && checkbox.click()
|
||||
// 开灯状态 && 要关灯 -> 关灯
|
||||
|
||||
@ -5,7 +5,7 @@ import { playerAgent } from './player-agent'
|
||||
|
||||
// let initialized = false
|
||||
|
||||
const setLight = (on: boolean) => {
|
||||
const setLight = (on?: boolean) => {
|
||||
if (!playerUrls.some(url => matchUrlPattern(url))) {
|
||||
return none
|
||||
}
|
||||
@ -27,3 +27,4 @@ const setLight = (on: boolean) => {
|
||||
|
||||
export const lightOn = setLight(true)
|
||||
export const lightOff = setLight(false)
|
||||
export const toggleLight = setLight()
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import * as ajax from '@/core/ajax'
|
||||
import * as cdnTypes from '@/core/cdn-types'
|
||||
import * as download from '@/core/download'
|
||||
import * as dialog from '@/core/dialog'
|
||||
import * as externalInput from '@/core/external-input'
|
||||
import * as filePicker from '@/core/file-picker'
|
||||
import * as installFeature from '@/core/install-feature'
|
||||
@ -40,6 +41,7 @@ export const coreApis = {
|
||||
ajax,
|
||||
cdnTypes,
|
||||
download,
|
||||
dialog,
|
||||
externalInput,
|
||||
filePicker,
|
||||
installFeature,
|
||||
@ -80,6 +82,7 @@ export const externalApis = {
|
||||
ajax,
|
||||
...cdnTypes,
|
||||
...download,
|
||||
...dialog,
|
||||
...externalInput,
|
||||
...filePicker,
|
||||
...installFeature,
|
||||
|
||||
@ -10,7 +10,7 @@ export interface DialogInputs {
|
||||
}
|
||||
export interface DialogInstance extends Required<DialogInputs> {
|
||||
open: boolean
|
||||
close: Promise<void>
|
||||
close(): Promise<void>
|
||||
closeListeners: (() => void)[]
|
||||
}
|
||||
export const showDialog = (inputs: DialogInputs) => {
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
export class LoadFeatureCodeError extends Error {}
|
||||
|
||||
interface CodeSandbox {
|
||||
/**
|
||||
* feature 代码运行沙箱
|
||||
*/
|
||||
type CodeSandbox = {
|
||||
/**
|
||||
* 在沙箱中执行代码
|
||||
*
|
||||
@ -15,11 +18,11 @@ interface CodeSandbox {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取代码运行沙箱
|
||||
* 创建 feature 代码的运行沙箱
|
||||
*
|
||||
* @returns 一个函数:接受代码,返回。
|
||||
* @returns 一个 `CodeSandbox`。
|
||||
*/
|
||||
const getSandbox = lodash.once((): CodeSandbox => {
|
||||
const createCodeSandbox = (): CodeSandbox => {
|
||||
// 需要被注入到 `sandbox` 中的键值对
|
||||
const injection = new Map([
|
||||
// 加固,防止逃逸
|
||||
@ -55,8 +58,9 @@ const getSandbox = lodash.once((): CodeSandbox => {
|
||||
return [exported, returned]
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let staticCodeSandbox: CodeSandbox | undefined
|
||||
/**
|
||||
* 执行 feature (component, plugin, style) 的代码,并尝试获取其导出元数据
|
||||
*
|
||||
@ -69,14 +73,15 @@ const getSandbox = lodash.once((): CodeSandbox => {
|
||||
*
|
||||
* 代码默认以非严格模式执行,启用需自行添加 `use strict`。(从本项目中打包的 feature 自带严格模式)
|
||||
*
|
||||
* 全局对象为脚本管理器提供的 `window`,支持访问 `unsafeWindow`。
|
||||
* 代码执行时的全局对象为脚本管理器提供的 `window`。代码中支持访问 `unsafeWindow`。
|
||||
*
|
||||
* @param code - 被执行的代码
|
||||
* @returns 导出的元数据(不检测正确性)
|
||||
* @returns 导出的元数据(不检测是否为正确的 feature)
|
||||
* @throws {@link LoadFeatureCodeError}
|
||||
* 代码包含语法错误或代码执行时产生了异常
|
||||
*/
|
||||
export const loadFeatureCode = (code: string): unknown => {
|
||||
const [exported, returned] = getSandbox().run(code)
|
||||
staticCodeSandbox || (staticCodeSandbox = createCodeSandbox())
|
||||
const [exported, returned] = staticCodeSandbox.run(code)
|
||||
return exported || returned
|
||||
}
|
||||
|
||||
@ -49,8 +49,7 @@ export default Vue.extend({
|
||||
async mounted() {
|
||||
await this.$nextTick()
|
||||
const appendTarget = containerMap[this.container]
|
||||
this.toast = createMiniToast(this.message, this.$refs.content, {
|
||||
content: this.$refs.toast,
|
||||
this.toast = createMiniToast(this.$refs.toast, this.$refs.content, {
|
||||
placement: this.placement,
|
||||
showOnCreate: this.show,
|
||||
trigger: 'mouseenter focusin',
|
||||
|
||||
@ -407,6 +407,7 @@ export class DoubleClickEvent {
|
||||
singleClickHandler: (e: MouseEvent) => void = none
|
||||
|
||||
private clickedOnce = false
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
private readonly stopPropagationHandler = (e: MouseEvent) => {
|
||||
e.stopImmediatePropagation()
|
||||
}
|
||||
@ -543,6 +544,9 @@ export const retrieveImageUrl = (element: HTMLElement) => {
|
||||
url = element.getAttribute('data-src')
|
||||
} else if (element instanceof HTMLImageElement) {
|
||||
url = element.src
|
||||
} else if (dq(element, 'picture img')) {
|
||||
const image = dq(element, 'picture img') as HTMLImageElement
|
||||
url = image.src
|
||||
} else {
|
||||
const { backgroundImage } = element.style
|
||||
if (!backgroundImage) {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { LaunchBarActionProvider, LaunchBarAction } from '@/components/launch-bar/launch-bar-action'
|
||||
import { PluginMetadata } from '../plugin'
|
||||
import { IdSearchProvider } from './types'
|
||||
|
||||
const getCopyItem = async (name: string, id: string, original: string) => {
|
||||
const item: LaunchBarAction = {
|
||||
@ -17,78 +18,82 @@ const getCopyItem = async (name: string, id: string, original: string) => {
|
||||
}
|
||||
return [item]
|
||||
}
|
||||
const idMatches = [
|
||||
const idMatches: IdSearchProvider[] = [
|
||||
{
|
||||
pattern: /^av([\d]+)$/i,
|
||||
name: (match: RegExpMatchArray) => `av${match[1]}`,
|
||||
badge: 'av号跳转',
|
||||
link: (match: RegExpMatchArray) => `https://www.bilibili.com/av${match[1]}`,
|
||||
extend: async (match: RegExpMatchArray) => {
|
||||
getActions: async match => {
|
||||
const { getJsonWithCredentials } = await import('@/core/ajax')
|
||||
const json = await getJsonWithCredentials(
|
||||
`https://api.bilibili.com/x/web-interface/view?aid=${match[1]}`,
|
||||
)
|
||||
const bv = lodash.get(json, 'data.bvid', null)
|
||||
if (bv === null) {
|
||||
return []
|
||||
const data = lodash.get(json, 'data', {})
|
||||
const { bvid, title } = data
|
||||
const indexer = `av${match[1]}`
|
||||
|
||||
return {
|
||||
name: title || indexer,
|
||||
description: 'av号跳转',
|
||||
indexer,
|
||||
link: `https://www.bilibili.com/av${match[1]}`,
|
||||
extraActions: bvid ? await getCopyItem('BV号', bvid, indexer) : [],
|
||||
}
|
||||
return getCopyItem('BV号', bv, `av${match[1]}`)
|
||||
},
|
||||
},
|
||||
{
|
||||
pattern: /^bv([\da-zA-Z]+)$/i,
|
||||
name: (match: RegExpMatchArray) => `BV${match[1]}`,
|
||||
badge: 'BV号跳转',
|
||||
link: (match: RegExpMatchArray) => `https://www.bilibili.com/BV${match[1]}`,
|
||||
extend: async (match: RegExpMatchArray) => {
|
||||
getActions: async match => {
|
||||
const { getJsonWithCredentials } = await import('@/core/ajax')
|
||||
const json = await getJsonWithCredentials(
|
||||
`https://api.bilibili.com/x/web-interface/view?bvid=${match[1]}`,
|
||||
)
|
||||
const av = lodash.get(json, 'data.aid', null)
|
||||
if (av === null) {
|
||||
return []
|
||||
const data = lodash.get(json, 'data', {})
|
||||
const { aid, title } = data
|
||||
const indexer = `BV${match[1]}`
|
||||
|
||||
return {
|
||||
name: title || indexer,
|
||||
description: 'BV号跳转',
|
||||
indexer,
|
||||
link: `https://www.bilibili.com/BV${match[1]}`,
|
||||
extraActions: aid ? await getCopyItem('av号', `av${aid}`, indexer) : [],
|
||||
}
|
||||
return getCopyItem('av号', `av${av}`, `BV${match[1]}`)
|
||||
},
|
||||
},
|
||||
]
|
||||
export const plugin: PluginMetadata = {
|
||||
name: 'launchBar.actions.IDSearch',
|
||||
displayName: 'ID搜索快速跳转',
|
||||
displayName: '搜索栏 - 视频跳转',
|
||||
async setup() {
|
||||
const { addData } = await import('../data')
|
||||
const { LaunchBarActionProviders } = await import('@/components/launch-bar/launch-bar-action')
|
||||
addData(LaunchBarActionProviders, (providers: LaunchBarActionProvider[]) => {
|
||||
providers.push({
|
||||
name: 'IDSearchProvider',
|
||||
// getEnterAction: input => {
|
||||
// for (const it of idMatches) {
|
||||
// const match = input.match(it.pattern)
|
||||
// if (match) {
|
||||
// return () => window.open(it.link(match), '_blank')
|
||||
// }
|
||||
// }
|
||||
// return null
|
||||
// },
|
||||
getActions: async input => {
|
||||
const results: LaunchBarAction[] = []
|
||||
for (const it of idMatches) {
|
||||
const match = input.match(it.pattern)
|
||||
if (match) {
|
||||
results.push({
|
||||
name: it.name(match),
|
||||
icon: 'mdi-open-in-new',
|
||||
description: it.badge,
|
||||
action: () => {
|
||||
window.open(it.link(match), '_blank')
|
||||
const {
|
||||
name,
|
||||
description = '',
|
||||
indexer,
|
||||
link,
|
||||
extraActions = [],
|
||||
} = await it.getActions(match)
|
||||
results.push(
|
||||
{
|
||||
name,
|
||||
icon: 'mdi-open-in-new',
|
||||
indexer,
|
||||
description,
|
||||
action: () => {
|
||||
window.open(link, '_blank')
|
||||
},
|
||||
order: 0,
|
||||
},
|
||||
order: 0,
|
||||
})
|
||||
if (it.extend) {
|
||||
const actions = await it.extend(match)
|
||||
results.push(...actions)
|
||||
}
|
||||
...extraActions,
|
||||
)
|
||||
}
|
||||
}
|
||||
return results
|
||||
|
||||
19
src/plugins/id-search/types.ts
Normal file
19
src/plugins/id-search/types.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import type { LaunchBarAction } from '@/components/launch-bar/launch-bar-action'
|
||||
|
||||
export interface IdSearchProvider {
|
||||
/** 匹配的正则 */
|
||||
pattern: RegExp
|
||||
/** 匹配时运行此函数获取结果 */
|
||||
getActions: (match: RegExpMatchArray) => Promise<{
|
||||
/** 显示的选项名称 */
|
||||
name: string
|
||||
/** 选择此选项后打开的链接 */
|
||||
link: string
|
||||
/** 提供给搜索栏过滤的关键词 */
|
||||
indexer: string
|
||||
/** 显示的选项描述 */
|
||||
description?: string
|
||||
/** 额外提供一些自定义的 LaunchBarAction */
|
||||
extraActions?: LaunchBarAction[]
|
||||
}>
|
||||
}
|
||||
@ -8,7 +8,8 @@
|
||||
<VIcon :size="48" icon="mdi-close"></VIcon>
|
||||
</div>
|
||||
<a target="_blank" class="copy-link image-viewer-icon" title="复制原链接" @click="copyLink()">
|
||||
<VIcon :size="48" icon="mdi-link"></VIcon>
|
||||
<VIcon v-if="copiedTimer" :size="48" icon="mdi-check"></VIcon>
|
||||
<VIcon v-else :size="48" icon="mdi-link"></VIcon>
|
||||
</a>
|
||||
<a target="_blank" class="new-tab image-viewer-icon" title="在新标签页打开" @click="newTab()">
|
||||
<VIcon :size="48" icon="mdi-open-in-new"></VIcon>
|
||||
@ -49,6 +50,7 @@ export default Vue.extend({
|
||||
open: false,
|
||||
blobUrl: '',
|
||||
keyHandler: null,
|
||||
copiedTimer: 0,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@ -85,6 +87,12 @@ export default Vue.extend({
|
||||
methods: {
|
||||
async copyLink() {
|
||||
await navigator.clipboard.writeText(this.image)
|
||||
if (this.copiedTimer) {
|
||||
window.clearTimeout(this.copiedTimer)
|
||||
}
|
||||
this.copiedTimer = window.setTimeout(() => {
|
||||
this.copiedTimer = 0
|
||||
}, 2000)
|
||||
},
|
||||
newTab() {
|
||||
window.open(this.image, '_blank')
|
||||
|
||||
@ -84,18 +84,24 @@ export default Vue.extend({
|
||||
},
|
||||
},
|
||||
data() {
|
||||
const tabs = this.tabs as TabMappings
|
||||
return {
|
||||
selectedTab: (this.tabs.find((t: TabMapping) => t.name === this.defaultTab) ??
|
||||
this.tabs[0]) as TabMapping,
|
||||
selectedTabName:
|
||||
tabs.find((t: TabMapping) => t.name === this.defaultTab)?.name ?? tabs[0].name,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
selectedTab() {
|
||||
return this.tabs.find((t: TabMapping) => t.name === this.selectedTabName)
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$emit('change', this.selectedTab.activeLink)
|
||||
},
|
||||
methods: {
|
||||
selectTab(tab: TabMapping) {
|
||||
if (this.selectedTab !== tab) {
|
||||
this.selectedTab = tab
|
||||
if (this.selectedTabName !== tab.name) {
|
||||
this.selectedTabName = tab.name
|
||||
tab.count = 0
|
||||
this.$emit('change', this.selectedTab.activeLink)
|
||||
} else if (tab.activeLink) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user