Merge branch 'preview-features' into preview

This commit is contained in:
the1812 2023-09-24 20:35:53 +08:00
commit 35578ac474
33 changed files with 868 additions and 53 deletions

View File

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

View File

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

1
.gitignore vendored
View File

@ -8,3 +8,4 @@ dist/profile.json
dist/bilibili-evolved.dev.user.js dist/bilibili-evolved.dev.user.js
registry/dist/doc.js registry/dist/doc.js
dev-tools/**/dist/ dev-tools/**/dist/
.idea

View File

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

View File

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

View File

@ -191,6 +191,14 @@
"fullRelativePath": "../../registry/dist/components/style/clear-home.js", "fullRelativePath": "../../registry/dist/components/style/clear-home.js",
"fullAbsolutePath": "registry/dist/components/style/clear-home.js" "fullAbsolutePath": "registry/dist/components/style/clear-home.js"
}, },
{
"type": "component",
"name": "customFontFamily",
"displayName": "自定义字体",
"description": "by [@Tinhone](https://github.com/Tinhone)\n\n使用组件提供的字体设置覆盖原版的主站字体并使主站字体可被自定义。字体设置写法请参考 [MDN](https://developer.mozilla.org/zh-CN/docs/Web/CSS/font-family) 、默认设置与设置说明",
"fullRelativePath": "../../registry/dist/components/style/custom-font-family.js",
"fullAbsolutePath": "registry/dist/components/style/custom-font-family.js"
},
{ {
"type": "component", "type": "component",
"name": "customNavbar", "name": "customNavbar",
@ -779,7 +787,7 @@
"type": "component", "type": "component",
"name": "playerIntersectionActions", "name": "playerIntersectionActions",
"displayName": "播放器位置动作", "displayName": "播放器位置动作",
"description": "by [@FoundTheWOUT](https://github.com/FoundTheWOUT)\n\n设置当播放器移出视图的位置变化时执行的动作.", "description": "by [@Waua](https://github.com/FoundTheWOUT)\n\n设置当播放器移出视图的位置变化时执行的动作.",
"fullRelativePath": "../../registry/dist/components/video/player/intersection-actions.js", "fullRelativePath": "../../registry/dist/components/video/player/intersection-actions.js",
"fullAbsolutePath": "registry/dist/components/video/player/intersection-actions.js" "fullAbsolutePath": "registry/dist/components/video/player/intersection-actions.js"
}, },

View File

@ -233,6 +233,17 @@ by [@RieN7](https://github.com/rien7)
删除首页特定类型的卡片 删除首页特定类型的卡片
### [自定义字体](../../registry/dist/components/style/custom-font-family.js)
`customFontFamily`
**jsDelivr:** [`Stable`](https://cdn.jsdelivr.net/gh/the1812/Bilibili-Evolved@master/registry/dist/components/style/custom-font-family.js) / [`Preview`](https://cdn.jsdelivr.net/gh/the1812/Bilibili-Evolved@preview/registry/dist/components/style/custom-font-family.js)
**GitHub:** [`Stable`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/components/style/custom-font-family.js) / [`Preview`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/components/style/custom-font-family.js)
by [@Tinhone](https://github.com/Tinhone)
使用组件提供的字体设置覆盖原版的主站字体,并使主站字体可被自定义。字体设置写法请参考 [MDN](https://developer.mozilla.org/zh-CN/docs/Web/CSS/font-family) 、默认设置与设置说明
### [自定义顶栏](../../registry/dist/components/style/custom-navbar.js) ### [自定义顶栏](../../registry/dist/components/style/custom-navbar.js)
`customNavbar` `customNavbar`
@ -1050,7 +1061,7 @@ by [@JLoeve](https://github.com/LonelySteve)
**GitHub:** [`Stable`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/components/video/player/intersection-actions.js) / [`Preview`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/components/video/player/intersection-actions.js) **GitHub:** [`Stable`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/registry/dist/components/video/player/intersection-actions.js) / [`Preview`](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/registry/dist/components/video/player/intersection-actions.js)
by [@FoundTheWOUT](https://github.com/FoundTheWOUT) by [@Waua](https://github.com/FoundTheWOUT)
设置当播放器移出视图的位置变化时执行的动作. 设置当播放器移出视图的位置变化时执行的动作.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -769,3 +769,7 @@
} }
} }
} }
#{contains('live-info-content')} {
background: transparent !important;
}

View File

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

View File

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

View File

@ -1,6 +1,7 @@
import { defineOptionsMetadata } from '@/components/define' import { defineOptionsMetadata } from '@/components/define'
import { getNumberValidator } from '@/core/utils' import { getNumberValidator } from '@/core/utils'
import type { FreshLayoutItemSettings } from './layouts/fresh-layout-item' import type { FreshLayoutItemSettings } from './layouts/fresh-layout-item'
import { areaPrimaryTitleColorEnum } from './init-dropdown-options/area-primary-title-color'
export enum RankListMode { export enum RankListMode {
Default = 'default', Default = 'default',
@ -48,6 +49,11 @@ export const freshHomeOptionsMetadata = defineOptionsMetadata({
displayName: '启用横向滚动', displayName: '启用横向滚动',
defaultValue: false, defaultValue: false,
}, },
areaPrimaryTitleColor: {
displayName: '栏目主标题颜色',
dropdownEnum: areaPrimaryTitleColorEnum,
defaultValue: areaPrimaryTitleColorEnum.Dark,
},
maxWidth: { maxWidth: {
displayName: '最大宽度 (px)', displayName: '最大宽度 (px)',
defaultValue: 1440, defaultValue: 1440,

View File

@ -25,6 +25,7 @@ $prefix: 'simplifyLiveroom-switch';
&-enterPrompt .chat-history-panel .brush-prompt, &-enterPrompt .chat-history-panel .brush-prompt,
&-enterPrompt .chat-item.convention-msg, &-enterPrompt .chat-item.convention-msg,
&-pk .chaos-pk, &-pk .chaos-pk,
&-pk .awesome-pk-box,
&-topRank .chat-item.top3-notice, &-topRank .chat-item.top3-notice,
&-topRank .chat-item .rank-icon { &-topRank .chat-item .rank-icon {
display: none !important; display: none !important;

View File

@ -11,9 +11,11 @@ import {
DownloadVideoInputItem, DownloadVideoInputItem,
} from '../types' } from '../types'
import { bangumiApi, videoApi } from './url' import { bangumiApi, videoApi } from './url'
import { Options } from '..'
import { getComponentSettings } from '@/core/settings'
/** dash 格式更明确的扩展名 */ /** dash 格式更明确的扩展名 */
export const DashExtensions = { export const DefaultDashExtensions = {
video: '.mp4', video: '.mp4',
audio: '.m4a', audio: '.m4a',
flacAudio: '.flac', flacAudio: '.flac',
@ -27,7 +29,7 @@ export enum DashCodec {
Av1 = 'AV1', Av1 = 'AV1',
} }
export interface Dash { export interface Dash {
type: keyof typeof DashExtensions type: keyof typeof DefaultDashExtensions
bandWidth: number bandWidth: number
codecs: string codecs: string
codecId: number codecId: number
@ -50,12 +52,25 @@ export interface DashFilters {
video?: (dash: VideoDash) => boolean video?: (dash: VideoDash) => boolean
audio?: (dash: AudioDash) => boolean audio?: (dash: AudioDash) => boolean
} }
const getDashExtensions = (type: keyof typeof DefaultDashExtensions): string => {
const { options } = getComponentSettings<Options>('downloadVideo')
if (type === 'video') {
return options.dashVideoExtension
}
if (type === 'audio') {
return options.dashAudioExtension
}
if (type === 'flacAudio') {
return options.dashFlacAudioExtension
}
return DefaultDashExtensions[type] ?? DashFragmentExtension
}
const dashToFragment = (dash: Dash): DownloadVideoFragment => ({ const dashToFragment = (dash: Dash): DownloadVideoFragment => ({
url: dash.downloadUrl, url: dash.downloadUrl,
backupUrls: dash.backupUrls, backupUrls: dash.backupUrls,
length: dash.duration, length: dash.duration,
size: Math.trunc((dash.bandWidth * dash.duration) / 8), size: Math.trunc((dash.bandWidth * dash.duration) / 8),
extension: DashExtensions[dash.type] ?? DashFragmentExtension, extension: getDashExtensions(dash.type),
}) })
export const dashToFragments = (info: { export const dashToFragments = (info: {
videoDashes: VideoDash[] videoDashes: VideoDash[]

View File

@ -1,6 +1,31 @@
import { defineComponentMetadata } from '@/components/define' import {
OptionsOfMetadata,
defineComponentMetadata,
defineOptionsMetadata,
} from '@/components/define'
import { hasVideo } from '@/core/spin-query' import { hasVideo } from '@/core/spin-query'
import { DefaultDashExtensions } from './apis/dash'
const options = defineOptionsMetadata({
basicConfig: {
defaultValue: {},
displayName: '基础配置',
hidden: true,
},
dashVideoExtension: {
defaultValue: DefaultDashExtensions.video,
displayName: 'DASH 视频扩展名',
},
dashAudioExtension: {
defaultValue: DefaultDashExtensions.audio,
displayName: 'DASH 普通音频扩展名',
},
dashFlacAudioExtension: {
defaultValue: DefaultDashExtensions.flacAudio,
displayName: 'DASH FLAC 音频扩展名',
},
})
export type Options = OptionsOfMetadata<typeof options>
export const component = defineComponentMetadata({ export const component = defineComponentMetadata({
name: 'downloadVideo', name: 'downloadVideo',
displayName: '下载视频', displayName: '下载视频',
@ -12,12 +37,6 @@ export const component = defineComponentMetadata({
condition: () => hasVideo(), condition: () => hasVideo(),
}, },
tags: [componentsTags.video], tags: [componentsTags.video],
options: { options,
basicConfig: {
defaultValue: {},
displayName: '基础配置',
hidden: true,
},
},
// plugin, // plugin,
}) })

View File

@ -37,6 +37,7 @@ export const component = defineComponentMetadata({
} }
Promise.race([ Promise.race([
select('.multi-page-v1 .head-left h3'), select('.multi-page-v1 .head-left h3'),
select('.video-sections-v1 .first-line-title'),
select('.base-video-sections-v1 .first-line-title'), select('.base-video-sections-v1 .first-line-title'),
]).then(titleElement => { ]).then(titleElement => {
if (!titleElement) { if (!titleElement) {

View File

@ -5,9 +5,12 @@ import { allVideoUrls } from '@/core/utils/urls'
let cancel: () => void let cancel: () => void
export const disableScrollVolume = () => { export const disableScrollVolume = () => {
return preventEvent(unsafeWindow, 'mousewheel', () => { return preventEvent(unsafeWindow, 'mousewheel', () => {
const isFullscreen = ['player-mode-full', 'player-fullscreen-fix', 'player-full-win'].some( const isFullscreen = [
token => document.body.classList.contains(token), 'player-mode-full',
) 'player-mode-web',
'player-fullscreen-fix',
'player-full-win',
].some(token => document.body.classList.contains(token))
return isFullscreen return isFullscreen
}) })
} }

View File

@ -1,5 +1,5 @@
{ {
"version": "2.8.4", "version": "2.8.5",
"author": "Grant Howard, Coulomb-G", "author": "Grant Howard, Coulomb-G",
"copyright": "[year], Grant Howard (https://github.com/the1812) & Coulomb-G (https://github.com/Coulomb-G)", "copyright": "[year], Grant Howard (https://github.com/the1812) & Coulomb-G (https://github.com/Coulomb-G)",
"license": "MIT", "license": "MIT",

View File

@ -1,6 +1,6 @@
import { getUID, pascalCase } from '@/core/utils' import { getUID, pascalCase } from '@/core/utils'
import { getJsonWithCredentials } from '@/core/ajax' import { getJsonWithCredentials } from '@/core/ajax'
import { formatCount, formatDuration, parseDuration } from '@/core/utils/formatters' import { formatCount, formatDuration, parseCount, parseDuration } from '@/core/utils/formatters'
import { watchlaterList } from '@/components/video/watchlater' import { watchlaterList } from '@/components/video/watchlater'
import { getData, registerData } from '@/plugins/data' import { getData, registerData } from '@/plugins/data'
import { descendingStringSort } from '@/core/utils/sort' import { descendingStringSort } from '@/core/utils/sort'
@ -140,8 +140,8 @@ export const getVideoFeeds = withContentFilter(
like: formatCount(stat.like.count), like: formatCount(stat.like.count),
duration: parseDuration(archive.duration_text), duration: parseDuration(archive.duration_text),
durationText: formatDuration(parseDuration(archive.duration_text)), durationText: formatDuration(parseDuration(archive.duration_text)),
playCount: formatCount(archive.stat.play), playCount: formatCount(parseCount(archive.stat.play)),
danmakuCount: formatCount(archive.stat.danmaku), danmakuCount: formatCount(parseCount(archive.stat.danmaku)),
watchlater: watchlaterList.includes(archive.aid), watchlater: watchlaterList.includes(archive.aid),
} }
}), }),
@ -164,8 +164,8 @@ export const getVideoFeeds = withContentFilter(
time: new Date(author.pub_ts * 1000), time: new Date(author.pub_ts * 1000),
like: formatCount(stat.like.count), like: formatCount(stat.like.count),
durationText: '', durationText: '',
playCount: formatCount(pgc.stat.play), playCount: formatCount(parseCount(pgc.stat.play)),
danmakuCount: formatCount(pgc.stat.danmaku), danmakuCount: formatCount(parseCount(pgc.stat.danmaku)),
watchlater: false, watchlater: false,
} }
}) })

View File

@ -48,26 +48,33 @@ const getText = (dynamicModule: any, cardType: FeedsCardType) => {
return text return text
} }
const { desc: mainDesc, major } = dynamicModule const { desc: mainDesc, major } = dynamicModule
const mainText = mainDesc?.text ?? '' const mainText = (() => {
let typeText = '' if (major?.opus) {
switch (cardType) { return lodash.get(major.opus, 'summary.text')
default: {
break
} }
case feedsCardTypes.bangumi: return mainDesc?.text ?? ''
case feedsCardTypes.column: })()
case feedsCardTypes.video: { const typeText = (() => {
const target = major.archive ?? major.pgc ?? major.article switch (cardType) {
if (target) { default: {
const { title, desc } = target return ''
typeText = combineText(title, desc) }
} else if (major.opus) { case feedsCardTypes.bangumi:
const { title, summary } = major.opus case feedsCardTypes.column:
typeText = combineText(title, summary.text) case feedsCardTypes.video: {
const target = major.archive ?? major.pgc ?? major.article
if (target) {
const { title, desc } = target
return combineText(title, desc)
}
if (major.opus) {
const { title, summary } = major.opus
return combineText(title, summary.text)
}
return ''
} }
break
} }
} })()
return combineText(mainText, typeText) return combineText(mainText, typeText)
} }
const parseCard = async (element: HTMLElement): Promise<FeedsCard> => { const parseCard = async (element: HTMLElement): Promise<FeedsCard> => {

View File

@ -51,6 +51,21 @@ export const formatDuration = (time: number, fixed = 0) => {
} }
return `${hour}:${minute.padStart(2, '0')}:${second.padStart(secondTotalLength, '0')}` return `${hour}:${minute.padStart(2, '0')}:${second.padStart(secondTotalLength, '0')}`
} }
export const parseCount = (countText: string | number) => {
if (typeof countText === 'number') {
return countText
}
const unit = (() => {
if (countText.match(/亿$/)) {
return 1e8
}
if (countText.match(/万$/)) {
return 1e4
}
return 1
})()
return parseFloat(countText) * unit
}
const formatCountData = (count: number | string) => { const formatCountData = (count: number | string) => {
if (typeof count === 'string') { if (typeof count === 'string') {
count = parseInt(count) count = parseInt(count)