Format registry folder

This commit is contained in:
the1812 2022-10-12 23:27:58 +08:00
parent 8455bb3f41
commit 56d1f0b0d6
262 changed files with 3114 additions and 3539 deletions

View File

@ -25,8 +25,5 @@ export const component: ComponentMetadata = {
}, },
entry, entry,
urlInclude: feedsUrls, urlInclude: feedsUrls,
tags: [ tags: [componentsTags.feeds, componentsTags.utils],
componentsTags.feeds,
componentsTags.utils,
],
} }

View File

@ -18,37 +18,50 @@ const entry = async () => {
addStyle() addStyle()
const disableDetails = (card: FeedsCard) => { const disableDetails = (card: FeedsCard) => {
const { element } = card const { element } = card
element.addEventListener('click', e => { element.addEventListener(
if (e.ctrlKey || !enabled) { 'click',
return e => {
} if (e.ctrlKey || !enabled) {
const contents = dqa(element, '.content, .bili-dyn-content [data-module="desc"] .bili-rich-text') return
const target = e.target as HTMLElement }
if (target.hasAttribute('click-title')) { const contents = dqa(
return element,
} '.content, .bili-dyn-content [data-module="desc"] .bili-rich-text',
if ([ )
'bili-rich-text__action', const target = e.target as HTMLElement
'bili-rich-text-topic', if (target.hasAttribute('click-title')) {
'bili-rich-text-module', return
'bili-rich-text-link', }
].some(className => target.classList.contains(className))) { if (
return [
} 'bili-rich-text__action',
const popups = dqa(element, '.im-popup') 'bili-rich-text-topic',
if (popups.some(p => p.contains(target))) { 'bili-rich-text-module',
return 'bili-rich-text-link',
} ].some(className => target.classList.contains(className))
if (contents.some(c => c === target || c.contains(target))) { ) {
e.stopImmediatePropagation() return
} }
}, { capture: true }) const popups = dqa(element, '.im-popup')
if (popups.some(p => p.contains(target))) {
return
}
if (contents.some(c => c === target || c.contains(target))) {
e.stopImmediatePropagation()
}
},
{ capture: true },
)
const postContent = dq(element, '.post-content, .bili-dyn-content') const postContent = dq(element, '.post-content, .bili-dyn-content')
if (!postContent) { if (!postContent) {
return return
} }
const hasCardContainer = ['.video-container', '.bangumi-container', '.media-list', '.article-container'] const hasCardContainer = [
.some(type => dq(postContent, type)) '.video-container',
'.bangumi-container',
'.media-list',
'.article-container',
].some(type => dq(postContent, type))
if (hasCardContainer) { if (hasCardContainer) {
return return
} }
@ -63,7 +76,7 @@ const entry = async () => {
const details = document.createElement('div') const details = document.createElement('div')
details.classList.add('details') details.classList.add('details')
details.setAttribute('click-title', '详情') details.setAttribute('click-title', '详情')
details.innerHTML = /* html */` details.innerHTML = /* html */ `
<i class="mdi mdi-chevron-right" click-title></i> <i class="mdi mdi-chevron-right" click-title></i>
` `
contents.insertAdjacentElement('beforeend', details) contents.insertAdjacentElement('beforeend', details)
@ -76,9 +89,7 @@ const entry = async () => {
export const component: ComponentMetadata = { export const component: ComponentMetadata = {
name: 'disableFeedsDetails', name: 'disableFeedsDetails',
displayName: '禁止跳转动态详情', displayName: '禁止跳转动态详情',
tags: [ tags: [componentsTags.feeds],
componentsTags.feeds,
],
urlInclude: feedsUrls, urlInclude: feedsUrls,
description: { description: {
'zh-CN': '禁止动态点击后跳转详情页, 方便选择其中的文字.', 'zh-CN': '禁止动态点击后跳转详情页, 方便选择其中的文字.',

View File

@ -13,14 +13,17 @@ interface LiveInfo {
} }
const entry = async () => { const entry = async () => {
const { select } = await import('@/core/spin-query') const { select } = await import('@/core/spin-query')
const liveList = await select('.live-up-list, .bili-dyn-live-users__body') as HTMLElement const liveList = (await select('.live-up-list, .bili-dyn-live-users__body')) as HTMLElement
if (liveList === null) { if (liveList === null) {
return return
} }
const pageSize = 24 const pageSize = 24
const { getPages, getJsonWithCredentials } = await import('@/core/ajax') const { getPages, getJsonWithCredentials } = await import('@/core/ajax')
const fullList: LiveInfo[] = await getPages({ const fullList: LiveInfo[] = await getPages({
api: page => getJsonWithCredentials(`https://api.live.bilibili.com/relation/v1/feed/feed_list?page=${page}&pagesize=${pageSize}`), api: page =>
getJsonWithCredentials(
`https://api.live.bilibili.com/relation/v1/feed/feed_list?page=${page}&pagesize=${pageSize}`,
),
getList: json => lodash.get(json, 'data.list', []), getList: json => lodash.get(json, 'data.list', []),
getTotal: json => lodash.get(json, 'data.results', 0), getTotal: json => lodash.get(json, 'data.results', 0),
}) })
@ -46,7 +49,10 @@ const entry = async () => {
window.open(url, '_blank') window.open(url, '_blank')
}) })
} }
const face = dq(clone, '.live-up-img, .bili-dyn-live-users__item__face .bili-awesome-img') as HTMLElement const face = dq(
clone,
'.live-up-img, .bili-dyn-live-users__item__face .bili-awesome-img',
) as HTMLElement
face.style.backgroundImage = `url(${it.face})` face.style.backgroundImage = `url(${it.face})`
const title = dq(clone, '.live-name, .bili-dyn-live-users__item__title') as HTMLElement const title = dq(clone, '.live-name, .bili-dyn-live-users__item__title') as HTMLElement
title.innerHTML = it.title title.innerHTML = it.title
@ -70,11 +76,6 @@ export const component: ComponentMetadata = {
'zh-CN': '在动态的`正在直播`中, 为每一个直播间加上标题, 并且能够显示超过10个的直播间.', 'zh-CN': '在动态的`正在直播`中, 为每一个直播间加上标题, 并且能够显示超过10个的直播间.',
}, },
entry: styledComponentEntry(() => import('./extend-feeds-live.scss'), entry), entry: styledComponentEntry(() => import('./extend-feeds-live.scss'), entry),
tags: [ tags: [componentsTags.feeds, componentsTags.live],
componentsTags.feeds, urlInclude: [/^https:\/\/t\.bilibili\.com\/$/],
componentsTags.live,
],
urlInclude: [
/^https:\/\/t\.bilibili\.com\/$/,
],
} }

View File

@ -6,12 +6,7 @@
</div> </div>
<h2>类型</h2> <h2>类型</h2>
<div class="filter-types"> <div class="filter-types">
<FilterTypeSwitch <FilterTypeSwitch v-for="[name, type] of allTypes" :key="type.id" :name="name" :type="type" />
v-for="[name, type] of allTypes"
:key="type.id"
:name="name"
:type="type"
/>
</div> </div>
<h2>关键词</h2> <h2>关键词</h2>
<div class="filter-patterns"> <div class="filter-patterns">
@ -45,10 +40,7 @@
@click="toggleBlockSide(id)" @click="toggleBlockSide(id)"
> >
<label :class="{ disabled: sideDisabled(id) }"> <label :class="{ disabled: sideDisabled(id) }">
<span <span class="name" :class="{ disabled: sideDisabled(id) }">{{ type.displayName }}</span>
class="name"
:class="{ disabled: sideDisabled(id) }"
>{{ type.displayName }}</span>
<VIcon :size="16" class="disabled" icon="mdi-cancel"></VIcon> <VIcon :size="16" class="disabled" icon="mdi-cancel"></VIcon>
<VIcon :size="16" icon="mdi-check"></VIcon> <VIcon :size="16" icon="mdi-check"></VIcon>
</label> </label>
@ -62,11 +54,7 @@ import { FeedsCard, FeedsCardType } from '@/components/feeds/api'
import { getComponentSettings } from '@/core/settings' import { getComponentSettings } from '@/core/settings'
import { select } from '@/core/spin-query' import { select } from '@/core/spin-query'
import { attributes } from '@/core/observer' import { attributes } from '@/core/observer'
import { import { VIcon, TextBox, VButton } from '@/ui'
VIcon,
TextBox,
VButton,
} from '@/ui'
import { FeedsFilterOptions } from '.' import { FeedsFilterOptions } from '.'
import { hasBlockedPattern } from './pattern' import { hasBlockedPattern } from './pattern'
@ -158,14 +146,9 @@ export default Vue.extend({
}, },
}) })
if (cardsManager.managerType === 'v1') { if (cardsManager.managerType === 'v1') {
const tab = tabBar.querySelector( const tab = tabBar.querySelector('.tab:nth-child(1) .tab-text') as HTMLAnchorElement
'.tab:nth-child(1) .tab-text',
) as HTMLAnchorElement
attributes(tab, () => { attributes(tab, () => {
document.body.classList.toggle( document.body.classList.toggle('by-type', !tab.classList.contains('selected'))
'by-type',
!tab.classList.contains('selected'),
)
}) })
} }
if (cardsManager.managerType === 'v2') { if (cardsManager.managerType === 'v2') {
@ -206,9 +189,7 @@ export default Vue.extend({
updateBlockSide() { updateBlockSide() {
Object.entries(sideCards).forEach(([id, type]) => { Object.entries(sideCards).forEach(([id, type]) => {
const name = sideBlock + type.className const name = sideBlock + type.className
document.body.classList[ document.body.classList[this.blockSideCards.includes(id) ? 'add' : 'remove'](name)
this.blockSideCards.includes(id) ? 'add' : 'remove'
](name)
}) })
}, },
toggleBlockSide(id: number) { toggleBlockSide(id: number) {
@ -231,8 +212,8 @@ export default Vue.extend({
</script> </script>
<style lang="scss"> <style lang="scss">
@import "common"; @import 'common';
@import "./blocker"; @import './blocker';
body.enable-feeds-filter:not(.disable-feeds-filter) { body.enable-feeds-filter:not(.disable-feeds-filter) {
@include type-block(); @include type-block();

View File

@ -11,9 +11,7 @@
<script lang="ts"> <script lang="ts">
import { getComponentSettings } from '@/core/settings' import { getComponentSettings } from '@/core/settings'
import { import { VIcon } from '@/ui'
VIcon,
} from '@/ui'
const { options } = getComponentSettings('feedsFilter') const { options } = getComponentSettings('feedsFilter')
export default Vue.extend({ export default Vue.extend({
@ -48,9 +46,7 @@ export default Vue.extend({
}, },
methods: { methods: {
setFilter(disabled: boolean, updateSettings = true) { setFilter(disabled: boolean, updateSettings = true) {
document.body.classList[disabled ? 'add' : 'remove']( document.body.classList[disabled ? 'add' : 'remove'](`feeds-filter-block-${this.name}`)
`feeds-filter-block-${this.name}`,
)
if (!updateSettings) { if (!updateSettings) {
return return
} }

View File

@ -57,9 +57,7 @@ export const component: ComponentMetadata = {
'zh-CN': '按照类型或者关键词过滤动态首页的内容, 也可以移除动态页的一些侧边卡片.', 'zh-CN': '按照类型或者关键词过滤动态首页的内容, 也可以移除动态页的一些侧边卡片.',
}, },
entry, entry,
tags: [ tags: [componentsTags.feeds],
componentsTags.feeds,
],
options, options,
reload: () => document.body.classList.remove('disable-feeds-filter'), reload: () => document.body.classList.remove('disable-feeds-filter'),
unload: () => document.body.classList.add('disable-feeds-filter'), unload: () => document.body.classList.add('disable-feeds-filter'),

View File

@ -15,10 +15,7 @@ export interface BlockableCard {
export const hasBlockedPattern = (pattern: string, card: BlockableCard) => { export const hasBlockedPattern = (pattern: string, card: BlockableCard) => {
const upNameMatch = pattern.match(/(.+) up:([^ ]+)/) const upNameMatch = pattern.match(/(.+) up:([^ ]+)/)
if (upNameMatch) { if (upNameMatch) {
return ( return testPattern(upNameMatch[1], card.text) && testPattern(upNameMatch[2], card.username)
testPattern(upNameMatch[1], card.text)
&& testPattern(upNameMatch[2], card.username)
)
} }
return testPattern(pattern, card.text) return testPattern(pattern, card.text)
} }

View File

@ -14,11 +14,7 @@ const feedField = {
username: ['username', 'repostUsername'], username: ['username', 'repostUsername'],
text: ['text', 'repostText'], text: ['text', 'repostText'],
} }
const filterableFields = [ const filterableFields = [bangumiFields, videoField, feedField]
bangumiFields,
videoField,
feedField,
]
export const feedsFilterPlugin: PluginMetadata = { export const feedsFilterPlugin: PluginMetadata = {
name: 'feeds.contentFilters.patterns', name: 'feeds.contentFilters.patterns',
displayName: '动态关键词过滤', displayName: '动态关键词过滤',
@ -32,18 +28,24 @@ export const feedsFilterPlugin: PluginMetadata = {
patterns: string[] patterns: string[]
} }
return items.filter(item => { return items.filter(item => {
const field = filterableFields.find(it => ( const field = filterableFields.find(it =>
Object.values(it).every(fields => { Object.values(it).every(fields => {
if (Array.isArray(fields)) { if (Array.isArray(fields)) {
return fields.some(f => f in item) return fields.some(f => f in item)
} }
return fields in item return fields in item
}) }),
)) )
const card = Object.fromEntries( const card = Object.fromEntries(
Object.entries(field).map(([key, value]) => { Object.entries(field).map(([key, value]) => {
if (Array.isArray(value)) { if (Array.isArray(value)) {
return [key, value.map(v => item[v] ?? '').join('\n').trim()] return [
key,
value
.map(v => item[v] ?? '')
.join('\n')
.trim(),
]
} }
return [key, item[value].trim() as string] return [key, item[value].trim() as string]
}), }),

View File

@ -19,11 +19,7 @@ export const component: ComponentMetadata = {
description: { description: {
'zh-CN': '强制固定动态主页的顶栏和所有侧栏.', 'zh-CN': '强制固定动态主页的顶栏和所有侧栏.',
}, },
tags: [ tags: [componentsTags.feeds],
componentsTags.feeds,
],
entry, entry,
urlInclude: [ urlInclude: [/^https:\/\/t\.bilibili\.com\/$/],
/^https:\/\/t\.bilibili\.com\/$/,
],
} }

View File

@ -83,8 +83,6 @@ export const component: ComponentMetadata = {
'zh-CN': '动态里查看评论区时, 在底部添加一个`收起评论`按钮, 这样就不用再回到上面收起了.', 'zh-CN': '动态里查看评论区时, 在底部添加一个`收起评论`按钮, 这样就不用再回到上面收起了.',
}, },
urlInclude: feedsUrlsWithoutDetail, urlInclude: feedsUrlsWithoutDetail,
tags: [ tags: [componentsTags.feeds],
componentsTags.feeds,
],
entry: styledComponentEntry(() => import('./fold-comment.scss'), entry), entry: styledComponentEntry(() => import('./fold-comment.scss'), entry),
} }

View File

@ -8,9 +8,6 @@ export const component: ComponentMetadata = {
description: { description: {
'zh-CN': '不管内容多长, 总是完全展开动态的内容.', 'zh-CN': '不管内容多长, 总是完全展开动态的内容.',
}, },
tags: [ tags: [componentsTags.style, componentsTags.feeds],
componentsTags.style,
componentsTags.feeds,
],
urlInclude: feedsUrlsWithoutDetail, urlInclude: feedsUrlsWithoutDetail,
} }

View File

@ -22,11 +22,7 @@
</li> </li>
</ul> </ul>
</VPopup> </VPopup>
<DefaultWidget <DefaultWidget ref="medalButton" icon="mdi-medal" @click="medalOpen = !medalOpen">
ref="medalButton"
icon="mdi-medal"
@click="medalOpen = !medalOpen"
>
<span>更换勋章</span> <span>更换勋章</span>
</DefaultWidget> </DefaultWidget>
@ -48,11 +44,7 @@
</li> </li>
</ul> </ul>
</VPopup> </VPopup>
<DefaultWidget <DefaultWidget ref="titleButton" icon="mdi-script-outline" @click="titleOpen = !titleOpen">
ref="titleButton"
icon="mdi-script-outline"
@click="titleOpen = !titleOpen"
>
<span>更换头衔</span> <span>更换头衔</span>
</DefaultWidget> </DefaultWidget>
</div> </div>
@ -61,13 +53,8 @@
<script lang="ts"> <script lang="ts">
import { addComponentListener, getComponentSettings } from '@/core/settings' import { addComponentListener, getComponentSettings } from '@/core/settings'
import { descendingSort } from '@/core/utils/sort' import { descendingSort } from '@/core/utils/sort'
import { import { DefaultWidget, VPopup } from '@/ui'
DefaultWidget, import { Medal, Title, Badge, getMedalList, getTitleList } from './badge'
VPopup,
} from '@/ui'
import {
Medal, Title, Badge, getMedalList, getTitleList,
} from './badge'
const { options } = getComponentSettings('badgeHelper') const { options } = getComponentSettings('badgeHelper')
export default Vue.extend({ export default Vue.extend({
@ -85,9 +72,13 @@ export default Vue.extend({
} }
}, },
async mounted() { async mounted() {
addComponentListener('badgeHelper.grayEffect', (enable: boolean) => { addComponentListener(
this.grayEffect = enable 'badgeHelper.grayEffect',
}, true) (enable: boolean) => {
this.grayEffect = enable
},
true,
)
const init = async () => { const init = async () => {
const medal = this.loadMedalList() const medal = this.loadMedalList()
await Title.getImageMap() await Title.getImageMap()
@ -142,7 +133,7 @@ export default Vue.extend({
</script> </script>
<style lang="scss"> <style lang="scss">
@import "common"; @import 'common';
.badge-popup { .badge-popup {
top: 50%; top: 50%;
left: calc(100% + 8px); left: calc(100% + 8px);

View File

@ -1,4 +1,9 @@
import { getJsonWithCredentials, getPages, getTextWithCredentials, postTextWithCredentials } from '@/core/ajax' import {
getJsonWithCredentials,
getPages,
getTextWithCredentials,
postTextWithCredentials,
} from '@/core/ajax'
import { formData, getCsrf } from '@/core/utils' import { formData, getCsrf } from '@/core/utils'
import { logError } from '@/core/utils/log' import { logError } from '@/core/utils/log'
@ -12,13 +17,16 @@ const validateJson = (json: any, errorMessage: string) => {
} }
export abstract class Badge { export abstract class Badge {
constructor(public isActive: boolean = false, public id: number = 0) { } constructor(public isActive: boolean = false, public id: number = 0) {}
/** @deprecated */ /** @deprecated */
static parseJson<T>(text: string, actions: { static parseJson<T>(
successAction: (json: any) => T, text: string,
errorMessage: string, actions: {
errorAction: (json: any) => T successAction: (json: any) => T
}) { errorMessage: string
errorAction: (json: any) => T
},
) {
const json = JSON.parse(text) const json = JSON.parse(text)
if (json.code !== 0) { if (json.code !== 0) {
logError(`${actions.errorMessage} 错误码:${json.code} ${json.message || ''}`) logError(`${actions.errorMessage} 错误码:${json.code} ${json.message || ''}`)
@ -37,19 +45,9 @@ export class Medal extends Badge {
isLighted: boolean isLighted: boolean
constructor(json: any) { constructor(json: any) {
const { const {
medal: { medal: { medal_id, level, medal_name, wearing_status, is_lighted },
medal_id, anchor_info: { nick_name },
level, room_info: { room_id },
medal_name,
wearing_status,
is_lighted,
},
anchor_info: {
nick_name,
},
room_info: {
room_id,
},
} = json } = json
super(wearing_status === 1, medal_id) super(wearing_status === 1, medal_id)
this.level = level this.level = level
@ -59,20 +57,26 @@ export class Medal extends Badge {
this.isLighted = is_lighted this.isLighted = is_lighted
} }
async activate() { async activate() {
const text = await postTextWithCredentials('https://api.live.bilibili.com/xlive/web-room/v1/fansMedal/wear', formData({ const text = await postTextWithCredentials(
medal_id: this.id, 'https://api.live.bilibili.com/xlive/web-room/v1/fansMedal/wear',
csrf_token: getCsrf(), formData({
csrf: getCsrf(), medal_id: this.id,
})) csrf_token: getCsrf(),
csrf: getCsrf(),
}),
)
const result = validateJson(JSON.parse(text), '佩戴勋章失败.') const result = validateJson(JSON.parse(text), '佩戴勋章失败.')
this.isActive = true this.isActive = true
return result return result
} }
async deactivate() { async deactivate() {
const text = await postTextWithCredentials('https://api.live.bilibili.com/xlive/web-room/v1/fansMedal/take_off', formData({ const text = await postTextWithCredentials(
csrf_token: getCsrf(), 'https://api.live.bilibili.com/xlive/web-room/v1/fansMedal/take_off',
csrf: getCsrf(), formData({
})) csrf_token: getCsrf(),
csrf: getCsrf(),
}),
)
const result = validateJson(JSON.parse(text), '卸下勋章失败.') const result = validateJson(JSON.parse(text), '卸下勋章失败.')
this.isActive = false this.isActive = false
return result return result
@ -80,13 +84,13 @@ export class Medal extends Badge {
} }
export const getMedalList = async () => { export const getMedalList = async () => {
const pages = await getPages({ const pages = await getPages({
api: page => getJsonWithCredentials(`https://api.live.bilibili.com/xlive/app-ucenter/v1/fansMedal/panel?page=${page}&page_size=50`), api: page =>
getJsonWithCredentials(
`https://api.live.bilibili.com/xlive/app-ucenter/v1/fansMedal/panel?page=${page}&page_size=50`,
),
getList: json => { getList: json => {
validateJson(json, '无法获取勋章列表.') validateJson(json, '无法获取勋章列表.')
return [ return [...lodash.get(json, 'data.list', []), ...lodash.get(json, 'data.special_list', [])]
...lodash.get(json, 'data.list', []),
...lodash.get(json, 'data.special_list', []),
]
}, },
getTotal: json => lodash.get(json, 'data.total_number', 0), getTotal: json => lodash.get(json, 'data.total_number', 0),
}) })
@ -100,9 +104,7 @@ export class Title extends Badge {
source: string source: string
imageUrl: string imageUrl: string
constructor(json: any) { constructor(json: any) {
const { const { id, cid, wear, css, name, source } = json
id, cid, wear, css, name, source,
} = json
super(wear, css) super(wear, css)
this.tid = id this.tid = id
this.cid = cid this.cid = cid
@ -134,7 +136,10 @@ export class Title extends Badge {
} }
async activate() { async activate() {
return Badge.parseJson( return Badge.parseJson(
await postTextWithCredentials('https://api.live.bilibili.com/i/ajaxWearTitle', `id=${this.tid}&cid=${this.cid}&csrf=${getCsrf()}&csrf_token=${getCsrf()}`), await postTextWithCredentials(
'https://api.live.bilibili.com/i/ajaxWearTitle',
`id=${this.tid}&cid=${this.cid}&csrf=${getCsrf()}&csrf_token=${getCsrf()}`,
),
{ {
successAction: () => { successAction: () => {
this.isActive = true this.isActive = true
@ -147,8 +152,10 @@ export class Title extends Badge {
} }
async deactivate() { async deactivate() {
return Badge.parseJson( return Badge.parseJson(
await postTextWithCredentials('https://api.live.bilibili.com/i/ajaxCancelWearTitle', await postTextWithCredentials(
`csrf=${getCsrf()}&csrf_token=${getCsrf()}`), 'https://api.live.bilibili.com/i/ajaxCancelWearTitle',
`csrf=${getCsrf()}&csrf_token=${getCsrf()}`,
),
{ {
successAction: () => { successAction: () => {
this.isActive = false this.isActive = false
@ -160,11 +167,15 @@ export class Title extends Badge {
) )
} }
} }
export const getTitleList = async () => Badge.parseJson( export const getTitleList = async () =>
await getTextWithCredentials('https://api.live.bilibili.com/i/api/ajaxTitleInfo?page=1&pageSize=256&had=1'), Badge.parseJson(
{ await getTextWithCredentials(
successAction: json => lodash.get(json, 'data.list', []).map((it: any) => new Title(it)) as Title[], 'https://api.live.bilibili.com/i/api/ajaxTitleInfo?page=1&pageSize=256&had=1',
errorAction: () => [] as Title[], ),
errorMessage: '无法获取头衔列表.', {
}, successAction: json =>
) lodash.get(json, 'data.list', []).map((it: any) => new Title(it)) as Title[],
errorAction: () => [] as Title[],
errorMessage: '无法获取头衔列表.',
},
)

View File

@ -6,14 +6,13 @@ export const component = defineComponentMetadata({
name: 'badgeHelper', name: 'badgeHelper',
displayName: '直播勋章快速更换', displayName: '直播勋章快速更换',
description: { description: {
'zh-CN': '在直播区中, 可从功能面板中直接切换勋章和头衔. 默认显示 256 个 (同时也是上限), 可在选项中修改.', 'zh-CN':
'在直播区中, 可从功能面板中直接切换勋章和头衔. 默认显示 256 个 (同时也是上限), 可在选项中修改.',
}, },
entry: () => autoMatchMedal(), entry: () => autoMatchMedal(),
reload: none, reload: none,
unload: none, unload: none,
tags: [ tags: [componentsTags.live],
componentsTags.live,
],
widget: { widget: {
component: () => import('./BadgeHelper.vue').then(m => m.default), component: () => import('./BadgeHelper.vue').then(m => m.default),
condition: () => Boolean(getUID()), condition: () => Boolean(getUID()),
@ -38,7 +37,5 @@ export const component = defineComponentMetadata({
defaultValue: true, defaultValue: true,
}, },
}, },
urlInclude: [ urlInclude: ['//live.bilibili.com'],
'//live.bilibili.com',
],
}) })

View File

@ -25,10 +25,14 @@ export default Vue.extend({
} }
}, },
async mounted() { async mounted() {
const originalTextArea = await select(originalTextAreaSelector) as HTMLTextAreaElement const originalTextArea = (await select(originalTextAreaSelector)) as HTMLTextAreaElement
const sendButton = await select(sendButtonSelector) as HTMLButtonElement const sendButton = (await select(sendButtonSelector)) as HTMLButtonElement
if (!originalTextArea || !sendButton) { if (!originalTextArea || !sendButton) {
throw new Error(`[danmakuSendBar] ref elements not found. originalTextArea = ${originalTextArea === null} sendButton = ${sendButton === null}`) throw new Error(
`[danmakuSendBar] ref elements not found. originalTextArea = ${
originalTextArea === null
} sendButton = ${sendButton === null}`,
)
} }
// console.log(originalTextArea, sendButton) // console.log(originalTextArea, sendButton)
this.originalTextArea = originalTextArea this.originalTextArea = originalTextArea
@ -37,9 +41,7 @@ export default Vue.extend({
originalTextArea.addEventListener('input', this.listenChange) originalTextArea.addEventListener('input', this.listenChange)
originalTextArea.addEventListener('change', this.listenChange) originalTextArea.addEventListener('change', this.listenChange)
if (!changeEventHook) { if (!changeEventHook) {
const original = Object.getOwnPropertyDescriptors( const original = Object.getOwnPropertyDescriptors(HTMLTextAreaElement.prototype).value
HTMLTextAreaElement.prototype,
).value
Object.defineProperty(originalTextArea, 'value', { Object.defineProperty(originalTextArea, 'value', {
...original, ...original,
set(value: string) { set(value: string) {

View File

@ -30,9 +30,7 @@ const entry = async () => {
export const component = defineComponentMetadata({ export const component = defineComponentMetadata({
name: 'liveDanmakuSendbar', name: 'liveDanmakuSendbar',
displayName: '直播弹幕发送栏', displayName: '直播弹幕发送栏',
tags: [ tags: [componentsTags.live],
componentsTags.live,
],
description: { description: {
'zh-CN': '在直播的网页全屏和全屏模式状态下, 在底部显示弹幕栏.', 'zh-CN': '在直播的网页全屏和全屏模式状态下, 在底部显示弹幕栏.',
}, },

View File

@ -1,3 +1,4 @@
export const originalTextAreaSelector = '.control-panel-ctnr .chat-input-ctnr .chat-input' export const originalTextAreaSelector = '.control-panel-ctnr .chat-input-ctnr .chat-input'
export const sendButtonSelector = '.control-panel-ctnr .chat-input-ctnr ~ .bottom-actions .bl-button--primary' export const sendButtonSelector =
'.control-panel-ctnr .chat-input-ctnr ~ .bottom-actions .bl-button--primary'
export const leftControllerSelector = '.left-area' export const leftControllerSelector = '.left-area'

View File

@ -43,7 +43,12 @@ export default Vue.extend({
return return
} }
const links: string[] = json.data.list.map((it: { url: string }) => it.url) const links: string[] = json.data.list.map((it: { url: string }) => it.url)
Toast.success(links.map(l => `<a class="download-link" target="_blank" href="${l}">${l}</a>`).join('\n'), '下载录像') Toast.success(
links
.map(l => `<a class="download-link" target="_blank" href="${l}">${l}</a>`)
.join('\n'),
'下载录像',
)
} finally { } finally {
this.disabled = false this.disabled = false
} }

View File

@ -6,14 +6,10 @@ export const component = defineComponentMetadata({
description: { description: {
'zh-CN': '在直播录像页面 `live.bilibili.com/record/` 中添加下载支持.', 'zh-CN': '在直播录像页面 `live.bilibili.com/record/` 中添加下载支持.',
}, },
tags: [ tags: [componentsTags.live],
componentsTags.live,
],
entry: none, entry: none,
widget: { widget: {
component: () => import('./DownloadRecords.vue').then(m => m.default), component: () => import('./DownloadRecords.vue').then(m => m.default),
}, },
urlInclude: [ urlInclude: [/^https:\/\/live\.bilibili\.com\/record\/(.+)/],
/^https:\/\/live\.bilibili\.com\/record\/(.+)/,
],
}) })

View File

@ -46,7 +46,7 @@ let stopObservingMouseLeavePlayer: StopObservingCallback | null = null
interface FullWinToggleCallback { interface FullWinToggleCallback {
( (
// 标识监听到的动作是启动操作还是关闭操作 // 标识监听到的动作是启动操作还是关闭操作
isStarted: boolean isStarted: boolean,
): void ): void
} }
@ -75,9 +75,7 @@ function isFullWin(): boolean {
* @param callback * @param callback
* @returns * @returns
*/ */
function observeFullWinToggle( function observeFullWinToggle(onToggle: FullWinToggleCallback): StopObservingCallback {
onToggle: FullWinToggleCallback,
): StopObservingCallback {
/** /**
* *
* @param {MutationRecord} mutation body * @param {MutationRecord} mutation body
@ -113,10 +111,7 @@ function observeFullWinToggle(
} }
// 将包裹按钮移动到控制条上 // 将包裹按钮移动到控制条上
function moveGiftPackageToControlBar( function moveGiftPackageToControlBar(controlBar: Element, giftBtn0: Element) {
controlBar: Element,
giftBtn0: Element,
) {
// console.debug(`[${componentName}] moving gift button to control bar...`) // console.debug(`[${componentName}] moving gift button to control bar...`)
const rightArea = dq(controlBar, '.right-area') const rightArea = dq(controlBar, '.right-area')
if (rightArea) { if (rightArea) {
@ -159,14 +154,9 @@ function onFullWinClose(giftBtn0: Element, originGiftBtnParent: Element) {
} }
// 每当全屏模式切换时执行操作 // 每当全屏模式切换时执行操作
function doOnFullWinToggle( function doOnFullWinToggle(giftBtn0: Element, originGiftBtnParent: Element): StopObservingCallback {
giftBtn0: Element,
originGiftBtnParent: Element,
): StopObservingCallback {
return observeFullWinToggle(isStarted => { return observeFullWinToggle(isStarted => {
isStarted isStarted ? onFullWinStart(giftBtn0) : onFullWinClose(giftBtn0, originGiftBtnParent)
? onFullWinStart(giftBtn0)
: onFullWinClose(giftBtn0, originGiftBtnParent)
}) })
} }
@ -195,7 +185,7 @@ async function reload() {
addStyle(componentStyle, componentName) addStyle(componentStyle, componentName)
const giftBtnParent = await queryGiftBtnParent() const giftBtnParent = await queryGiftBtnParent()
giftBtn = (giftBtnParent?.children[0]) giftBtn = giftBtnParent?.children[0]
if (giftBtnParent && giftBtn) { if (giftBtnParent && giftBtn) {
stopObservingFullWinToggle = doOnFullWinToggle(giftBtn, giftBtnParent) stopObservingFullWinToggle = doOnFullWinToggle(giftBtn, giftBtnParent)

View File

@ -6,21 +6,23 @@ const liveHome = /^https:\/\/live\.bilibili\.com\/(index\.html)?$/
export const component = defineComponentMetadata({ export const component = defineComponentMetadata({
name: 'liveHomeMute', name: 'liveHomeMute',
displayName: '直播首页静音', displayName: '直播首页静音',
tags: [ tags: [componentsTags.live],
componentsTags.live,
],
description: { description: {
'zh-CN': '禁止直播首页的推荐直播间自动开始播放.', 'zh-CN': '禁止直播首页的推荐直播间自动开始播放.',
}, },
entry: async ({ metadata }) => { entry: async ({ metadata }) => {
const styleID = 'hide-home-live' const styleID = 'hide-home-live'
addComponentListener(`${metadata.name}.hide`, (value: boolean) => { addComponentListener(
if (value) { `${metadata.name}.hide`,
addStyle('.player-area-ctnr,#player-header { display: none !important }', styleID) (value: boolean) => {
} else { if (value) {
removeStyle(styleID) addStyle('.player-area-ctnr,#player-header { display: none !important }', styleID)
} } else {
}, true) removeStyle(styleID)
}
},
true,
)
}, },
options: { options: {
hide: { hide: {
@ -28,9 +30,7 @@ export const component = defineComponentMetadata({
defaultValue: false, defaultValue: false,
}, },
}, },
urlInclude: [ urlInclude: [liveHome],
liveHome,
],
plugin: { plugin: {
displayName: '直播首页静音 - 提前执行', displayName: '直播首页静音 - 提前执行',
description: { description: {

View File

@ -1,9 +1,6 @@
<template> <template>
<a :href="href" tabindex="-1"> <a :href="href" tabindex="-1">
<DefaultWidget <DefaultWidget name="返回原版直播间" icon="mdi-arrow-left-circle-outline" />
name="返回原版直播间"
icon="mdi-arrow-left-circle-outline"
/>
</a> </a>
</template> </template>
<script lang="ts"> <script lang="ts">

View File

@ -4,7 +4,8 @@ import { matchUrlPattern } from '@/core/utils'
export const component = defineComponentMetadata({ export const component = defineComponentMetadata({
name: 'originalLiveroom', name: 'originalLiveroom',
displayName: '返回原版直播间', displayName: '返回原版直播间',
description: '在直播间中提供返回原版直播间的按钮, 原版直播间将无视活动皮肤, 强制使用标准的直播页面.', description:
'在直播间中提供返回原版直播间的按钮, 原版直播间将无视活动皮肤, 强制使用标准的直播页面.',
tags: [componentsTags.live], tags: [componentsTags.live],
entry: none, entry: none,
urlInclude: [ urlInclude: [

View File

@ -5,10 +5,7 @@ import { liveUrls } from '@/core/utils/urls'
export const component = defineComponentMetadata({ export const component = defineComponentMetadata({
...toggleStyle('removeLiveWatermark', () => import('./remove-watermark.scss')), ...toggleStyle('removeLiveWatermark', () => import('./remove-watermark.scss')),
displayName: '删除直播水印', displayName: '删除直播水印',
tags: [ tags: [componentsTags.live, componentsTags.style],
componentsTags.live,
componentsTags.style,
],
description: { description: {
'zh-CN': '删除观看直播时角落的水印.', 'zh-CN': '删除观看直播时角落的水印.',
}, },

View File

@ -5,13 +5,16 @@ const id = 'dpi-live-showgirl'
const entry = async () => { const entry = async () => {
const { addStyle } = await import('@/core/style') const { addStyle } = await import('@/core/style')
if (document.getElementById(id) === null) { if (document.getElementById(id) === null) {
addStyle(` addStyle(
`
.haruna-ctnr, .haruna-ctnr,
.avatar-btn .avatar-btn
{ {
transform: scale(${1 / window.devicePixelRatio}) !important; transform: scale(${1 / window.devicePixelRatio}) !important;
} }
`, id) `,
id,
)
} }
} }
export const component = defineComponentMetadata({ export const component = defineComponentMetadata({
@ -21,10 +24,7 @@ export const component = defineComponentMetadata({
description: { description: {
'zh-CN': '根据屏幕 DPI 缩放直播看板娘的大小, 避免像素锯齿.', 'zh-CN': '根据屏幕 DPI 缩放直播看板娘的大小, 避免像素锯齿.',
}, },
tags: [ tags: [componentsTags.live, componentsTags.style],
componentsTags.live,
componentsTags.style,
],
entry, entry,
reload: entry, reload: entry,
unload: () => { unload: () => {

View File

@ -12,9 +12,6 @@ export const component = defineComponentMetadata({
], ],
displayName: '自动收起直播侧栏', displayName: '自动收起直播侧栏',
description: '自动收起直播间右边偏下的侧栏. (上面有个 "关注" 的面板)', description: '自动收起直播间右边偏下的侧栏. (上面有个 "关注" 的面板)',
tags: [ tags: [componentsTags.live, componentsTags.style],
componentsTags.live,
componentsTags.style,
],
urlInclude: liveUrls, urlInclude: liveUrls,
}) })

View File

@ -5,9 +5,13 @@ import { getNumberValidator } from '@/core/utils'
export const component = defineComponentMetadata({ export const component = defineComponentMetadata({
name: 'autoHideSidebar', name: 'autoHideSidebar',
entry: () => { entry: () => {
addComponentListener('autoHideSidebar.triggerWidth', (value: number) => { addComponentListener(
document.documentElement.style.setProperty('--auto-hide-sidebar-width', `${value}px`) 'autoHideSidebar.triggerWidth',
}, true) (value: number) => {
document.documentElement.style.setProperty('--auto-hide-sidebar-width', `${value}px`)
},
true,
)
}, },
displayName: '自动隐藏侧栏', displayName: '自动隐藏侧栏',
instantStyles: [ instantStyles: [
@ -26,6 +30,7 @@ export const component = defineComponentMetadata({
}, },
}, },
description: { description: {
'zh-CN': '自动隐藏脚本的侧栏 (功能和设置图标). 设置面板停靠在右侧时不建议使用, 因为网页的滚动条会占用右边缘的触发区域.', 'zh-CN':
'自动隐藏脚本的侧栏 (功能和设置图标). 设置面板停靠在右侧时不建议使用, 因为网页的滚动条会占用右边缘的触发区域.',
}, },
}) })

View File

@ -2,11 +2,7 @@
<div class="custom-navbar" :class="styles" role="navigation"> <div class="custom-navbar" :class="styles" role="navigation">
<div class="left-pad padding"></div> <div class="left-pad padding"></div>
<div class="custom-navbar-items" role="list"> <div class="custom-navbar-items" role="list">
<NavbarItem <NavbarItem v-for="item of items" :key="item.name" :item="item"></NavbarItem>
v-for="item of items"
:key="item.name"
:item="item"
></NavbarItem>
</div> </div>
<div class="right-pad padding"></div> <div class="right-pad padding"></div>
</div> </div>
@ -63,9 +59,13 @@ export default Vue.extend({
}, },
}, },
async mounted() { async mounted() {
addComponentListener('customNavbar.height', (value: number) => { addComponentListener(
document.documentElement.style.setProperty('--navbar-height', `${value}px`) 'customNavbar.height',
}, true) (value: number) => {
document.documentElement.style.setProperty('--navbar-height', `${value}px`)
},
true,
)
await checkTransparentFill(this) await checkTransparentFill(this)
}, },
methods: { methods: {
@ -214,12 +214,7 @@ body.fixed-navbar {
left: 0; left: 0;
width: 100%; width: 100%;
height: calc(2 * var(--navbar-height)); height: calc(2 * var(--navbar-height));
background-image: linear-gradient( background-image: linear-gradient(to bottom, #000a 0, #0004 65%, transparent 100%);
to bottom,
#000a 0,
#0004 65%,
transparent 100%
);
pointer-events: none; pointer-events: none;
} }
} }

View File

@ -111,7 +111,10 @@ export default Vue.extend({
if (!popup) { if (!popup) {
return return
} }
const allowRefresh = CustomNavbarItem.navbarOptions.refreshOnPopup && popup.popupRefresh && typeof popup.popupRefresh === 'function' const allowRefresh =
CustomNavbarItem.navbarOptions.refreshOnPopup &&
popup.popupRefresh &&
typeof popup.popupRefresh === 'function'
if (!initialPopup && allowRefresh) { if (!initialPopup && allowRefresh) {
popup.popupRefresh() popup.popupRefresh()
} }

View File

@ -1,9 +1,5 @@
<template> <template>
<a <a v-bind="$attrs" :target="newTab ? '_blank' : null" v-on="$listeners">
v-bind="$attrs"
:target="newTab ? '_blank' : null"
v-on="$listeners"
>
<slot /> <slot />
</a> </a>
</template> </template>

View File

@ -4,19 +4,9 @@ import { ranking } from './ranking/ranking'
import { userInfo } from './user-info/user-info' import { userInfo } from './user-info/user-info'
import { logo } from './logo/logo' import { logo } from './logo/logo'
import { home } from './home/home' import { home } from './home/home'
import { import { gamesIframe, livesIframe, mangaIframe } from './iframe/iframe'
gamesIframe,
livesIframe,
mangaIframe,
} from './iframe/iframe'
import { blanks } from './flexible-blank/flexible-blank' import { blanks } from './flexible-blank/flexible-blank'
import { import { bangumi, music, drawing, shop, match } from './simple-links/simple-links'
bangumi,
music,
drawing,
shop,
match,
} from './simple-links/simple-links'
import { upload } from './upload/upload' import { upload } from './upload/upload'
import { search } from './search/search' import { search } from './search/search'
import { feeds } from './feeds/feeds' import { feeds } from './feeds/feeds'

View File

@ -83,9 +83,13 @@ export class CustomNavbarItem implements Required<CustomNavbarItemInit> {
throw new Error('Missing CustomNavbarItem content') throw new Error('Missing CustomNavbarItem content')
} }
addComponentListener('customNavbar.touch', (value: boolean) => { addComponentListener(
this.touch = value ? init.touch : false 'customNavbar.touch',
}, true) (value: boolean) => {
this.touch = value ? init.touch : false
},
true,
)
this.hidden = CustomNavbarItem.navbarOptions.hidden.includes(this.name) this.hidden = CustomNavbarItem.navbarOptions.hidden.includes(this.name)
const orderMap = CustomNavbarItem.navbarOptions.order const orderMap = CustomNavbarItem.navbarOptions.order
this.order = orderMap[this.name] || 0 this.order = orderMap[this.name] || 0

View File

@ -1,47 +1,46 @@
import { ComponentEntry } from '@/components/types' import { ComponentEntry } from '@/components/types'
import { addComponentListener } from '@/core/settings' import { addComponentListener } from '@/core/settings'
import { import { isIframe, isNotHtml, matchUrlPattern, mountVueComponent } from '@/core/utils'
isIframe,
isNotHtml,
matchUrlPattern,
mountVueComponent,
} from '@/core/utils'
export const entry: ComponentEntry = async ({ metadata: { name } }) => { export const entry: ComponentEntry = async ({ metadata: { name } }) => {
// const url = document.URL.replace(location.search, '') // const url = document.URL.replace(location.search, '')
// const isHome = url === 'https://www.bilibili.com/' || url === 'https://www.bilibili.com/index.html' // const isHome = url === 'https://www.bilibili.com/' || url === 'https://www.bilibili.com/index.html'
if ( if (
isIframe() isIframe() ||
// (getComponentSettings('bilibiliSimpleNewHomeCompatible').enabled && isHome) || // (getComponentSettings('bilibiliSimpleNewHomeCompatible').enabled && isHome) ||
|| isNotHtml() isNotHtml()
) { ) {
return return
} }
addComponentListener(`${name}.padding`, value => { addComponentListener(
document.documentElement.style.setProperty('--navbar-bounds-padding', `${value}%`) `${name}.padding`,
}, true) value => {
document.documentElement.style.setProperty('--navbar-bounds-padding', `${value}%`)
},
true,
)
const globalFixedExclude = [ const globalFixedExclude = [
'https://space.bilibili.com', 'https://space.bilibili.com',
'https://www.bilibili.com/read', 'https://www.bilibili.com/read',
'https://www.bilibili.com/account/history', 'https://www.bilibili.com/account/history',
] ]
if (!globalFixedExclude.some(p => matchUrlPattern(p))) { if (!globalFixedExclude.some(p => matchUrlPattern(p))) {
addComponentListener(`${name}.globalFixed`, value => { addComponentListener(
document.body.classList.toggle('fixed-navbar', value) `${name}.globalFixed`,
}, true) value => {
document.body.classList.toggle('fixed-navbar', value)
},
true,
)
} }
const CustomNavbar = await import('./CustomNavbar.vue') const CustomNavbar = await import('./CustomNavbar.vue')
const customNavbar: Vue & { const customNavbar: Vue & {
styles: string[] styles: string[]
toggleStyle: (value: boolean, style: string) => void toggleStyle: (value: boolean, style: string) => void
} = mountVueComponent(CustomNavbar) } = mountVueComponent(CustomNavbar)
document.body.insertAdjacentElement('beforeend', customNavbar.$el); document.body.insertAdjacentElement('beforeend', customNavbar.$el)
['fill', 'shadow', 'blur'].forEach(style => { ;['fill', 'shadow', 'blur'].forEach(style => {
addComponentListener( addComponentListener(`${name}.${style}`, value => customNavbar.toggleStyle(value, style), true)
`${name}.${style}`,
value => customNavbar.toggleStyle(value, style),
true,
)
}) })
} }

View File

@ -13,9 +13,7 @@
</VDropdown> </VDropdown>
</template> </template>
<script lang="ts"> <script lang="ts">
import { import { VDropdown } from '@/ui'
VDropdown,
} from '@/ui'
import { getUID } from '@/core/utils' import { getUID } from '@/core/utils'
import { getJsonWithCredentials } from '@/core/ajax' import { getJsonWithCredentials } from '@/core/ajax'
import { getComponentSettings } from '@/core/settings' import { getComponentSettings } from '@/core/settings'
@ -51,17 +49,14 @@ export default Vue.extend({
if (json.code !== 0) { if (json.code !== 0) {
throw new Error(`获取收藏夹列表失败: ${json.message}`) throw new Error(`获取收藏夹列表失败: ${json.message}`)
} }
this.folders = lodash.get(json, 'data.list', []).map((item: { this.folders = lodash.get(json, 'data.list', []).map(
id: number (item: { id: number; title: string; media_count: number }) =>
title: string ({
media_count: number id: item.id,
}) => ( name: item.title,
{ count: item.media_count,
id: item.id, } as FavoritesFolder),
name: item.title, )
count: item.media_count,
} as FavoritesFolder
))
if (this.folders.length > 0 && this.folder.id === notSelectedFolder.id) { if (this.folders.length > 0 && this.folder.id === notSelectedFolder.id) {
const { lastFavoriteFolder } = navbarOptions const { lastFavoriteFolder } = navbarOptions
const folder = this.folders.find((f: FavoritesFolder) => f.id === lastFavoriteFolder) const folder = this.folders.find((f: FavoritesFolder) => f.id === lastFavoriteFolder)

View File

@ -39,7 +39,8 @@
target="_blank" target="_blank"
:href="'https://www.bilibili.com/video/' + card.bvid" :href="'https://www.bilibili.com/video/' + card.bvid"
:title="card.title" :title="card.title"
>{{ card.title }}</a> >{{ card.title }}</a
>
<a <a
v-if="card.upID" v-if="card.upID"
class="up" class="up"
@ -47,18 +48,10 @@
:href="'https://space.bilibili.com/' + card.upID" :href="'https://space.bilibili.com/' + card.upID"
:title="card.upName" :title="card.upName"
> >
<DpiImage <DpiImage placeholder-image class="face" :src="card.upFaceUrl" :size="20"></DpiImage>
placeholder-image
class="face"
:src="card.upFaceUrl"
:size="20"
></DpiImage>
<div class="name">{{ card.upName }}</div> <div class="name">{{ card.upName }}</div>
</a> </a>
<div <div v-else class="description">
v-else
class="description"
>
{{ card.description }} {{ card.description }}
</div> </div>
</div> </div>
@ -72,15 +65,7 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { import { VLoading, VEmpty, VIcon, VButton, TextBox, DpiImage, ScrollTrigger } from '@/ui'
VLoading,
VEmpty,
VIcon,
VButton,
TextBox,
DpiImage,
ScrollTrigger,
} from '@/ui'
import { formatDate, formatDuration } from '@/core/utils/formatters' import { formatDate, formatDuration } from '@/core/utils/formatters'
import { getUID } from '@/core/utils' import { getUID } from '@/core/utils'
import { getJsonWithCredentials } from '@/core/ajax' import { getJsonWithCredentials } from '@/core/ajax'
@ -131,8 +116,12 @@ async function searchAllList() {
} }
try { try {
this.loading = true this.loading = true
const jsonCurrent = await getJsonWithCredentials(`https://api.bilibili.com/x/v3/fav/resource/list?media_id=${this.folder.id}&pn=${this.searchPage}&ps=${MaxPageSize}&keyword=${this.search}&order=mtime&type=0&tid=0&platform=web`) const jsonCurrent = await getJsonWithCredentials(
const jsonAll = await getJsonWithCredentials(`https://api.bilibili.com/x/v3/fav/resource/list?media_id=${this.folder.id}&pn=${this.searchPage}&ps=${MaxPageSize}&keyword=${this.search}&order=mtime&type=1&tid=0&platform=web`) `https://api.bilibili.com/x/v3/fav/resource/list?media_id=${this.folder.id}&pn=${this.searchPage}&ps=${MaxPageSize}&keyword=${this.search}&order=mtime&type=0&tid=0&platform=web`,
)
const jsonAll = await getJsonWithCredentials(
`https://api.bilibili.com/x/v3/fav/resource/list?media_id=${this.folder.id}&pn=${this.searchPage}&ps=${MaxPageSize}&keyword=${this.search}&order=mtime&type=1&tid=0&platform=web`,
)
if (jsonCurrent.code !== 0 && jsonAll.code !== 0) { if (jsonCurrent.code !== 0 && jsonAll.code !== 0) {
return return
} }
@ -222,8 +211,7 @@ export default Vue.extend({
this.hasMoreSearchPage = true this.hasMoreSearchPage = true
this.searchPage = 1 this.searchPage = 1
this.filteredCards = (this.cards as FavoritesItemInfo[]).filter( this.filteredCards = (this.cards as FavoritesItemInfo[]).filter(
it => it.title.toLowerCase().includes(keyword) it => it.title.toLowerCase().includes(keyword) || it.upName.toLowerCase().includes(keyword),
|| it.upName.toLowerCase().includes(keyword),
) )
}, },
}, },
@ -238,9 +226,7 @@ export default Vue.extend({
// //
return [] return []
} }
return json.data.medias return json.data.medias.filter(favoriteItemFilter).map(favoriteItemMapper)
.filter(favoriteItemFilter)
.map(favoriteItemMapper)
}, },
async changeList() { async changeList() {
if (this.folder.id === 0) { if (this.folder.id === 0) {
@ -284,8 +270,8 @@ export default Vue.extend({
}) })
</script> </script>
<style lang="scss"> <style lang="scss">
@import "common"; @import 'common';
@import "../popup"; @import '../popup';
.custom-navbar .favorites-list { .custom-navbar .favorites-list {
width: 380px; width: 380px;

View File

@ -1,10 +1,6 @@
<template> <template>
<div class="navbar-feeds"> <div class="navbar-feeds">
<TabControl <TabControl ref="tabControl" :tabs="tabs" more-link="https://t.bilibili.com/">
ref="tabControl"
:tabs="tabs"
more-link="https://t.bilibili.com/"
>
<template #more-link> <template #more-link>
所有动态 所有动态
<VIcon icon="feeds" :size="18"></VIcon> <VIcon icon="feeds" :size="18"></VIcon>
@ -13,10 +9,7 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { import { TabControl, VIcon } from '@/ui'
TabControl,
VIcon,
} from '@/ui'
import { feedsCardTypes } from '@/components/feeds/api' import { feedsCardTypes } from '@/components/feeds/api'
import { getNotifyCount } from '@/components/feeds/notify' import { getNotifyCount } from '@/components/feeds/notify'
import { popperMixin } from '../mixins' import { popperMixin } from '../mixins'
@ -61,7 +54,7 @@ export default Vue.extend({
}) })
</script> </script>
<style lang="scss"> <style lang="scss">
@import "../popup"; @import '../popup';
.navbar-feeds { .navbar-feeds {
width: 380px; width: 380px;

View File

@ -3,13 +3,7 @@
<VLoading v-if="loading"></VLoading> <VLoading v-if="loading"></VLoading>
<VEmpty v-else-if="!loading && cards.length === 0"></VEmpty> <VEmpty v-else-if="!loading && cards.length === 0"></VEmpty>
<transition-group name="cards" tag="div" class="live-feeds-content"> <transition-group name="cards" tag="div" class="live-feeds-content">
<a <a v-for="c of cards" :key="c.id" class="live-card" target="_blank" :href="c.url">
v-for="c of cards"
:key="c.id"
class="live-card"
target="_blank"
:href="c.url"
>
<div class="face-container"> <div class="face-container">
<DpiImage class="face" :size="48" :src="c.upFaceUrl"></DpiImage> <DpiImage class="face" :size="48" :src="c.upFaceUrl"></DpiImage>
</div> </div>
@ -22,11 +16,7 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { import { VLoading, VEmpty, DpiImage } from '@/ui'
VLoading,
VEmpty,
DpiImage,
} from '@/ui'
import { responsiveGetPages, getJsonWithCredentials } from '@/core/ajax' import { responsiveGetPages, getJsonWithCredentials } from '@/core/ajax'
import { LiveFeedItem } from './live-feed-item' import { LiveFeedItem } from './live-feed-item'
@ -57,9 +47,10 @@ export default Vue.extend({
}, },
async created() { async created() {
const [responsive] = responsiveGetPages({ const [responsive] = responsiveGetPages({
api: page => getJsonWithCredentials( api: page =>
`https://api.live.bilibili.com/relation/v1/feed/feed_list?page=${page}&pagesize=24`, getJsonWithCredentials(
), `https://api.live.bilibili.com/relation/v1/feed/feed_list?page=${page}&pagesize=24`,
),
getList: json => lodash.get(json, 'data.list', []), getList: json => lodash.get(json, 'data.list', []),
getTotal: json => lodash.get(json, 'data.results', 0), getTotal: json => lodash.get(json, 'data.results', 0),
}) })
@ -69,7 +60,7 @@ export default Vue.extend({
}) })
</script> </script>
<style lang="scss"> <style lang="scss">
@import "common"; @import 'common';
.live-feeds { .live-feeds {
width: 100%; width: 100%;
@include v-center(); @include v-center();
@ -132,7 +123,7 @@ export default Vue.extend({
color: var(--theme-color); color: var(--theme-color);
} }
.live-name { .live-name {
opacity: .75; opacity: 0.75;
padding: 0 12px; padding: 0 12px;
@include single-line(); @include single-line();
line-height: normal; line-height: normal;

View File

@ -2,7 +2,8 @@
* *
* () * ()
*/ */
export const getBangumiFeedsMockup = async () => JSON.parse(`{ export const getBangumiFeedsMockup = async () =>
JSON.parse(`{
"code": 0, "code": 0,
"msg": "", "msg": "",
"message": "", "message": "",

View File

@ -7,11 +7,7 @@ import {
import { descendingStringSort } from '@/core/utils/sort' import { descendingStringSort } from '@/core/utils/sort'
import { logError } from '@/core/utils/log' import { logError } from '@/core/utils/log'
import { setLatestID } from '@/components/feeds/notify' import { setLatestID } from '@/components/feeds/notify'
import { import { VLoading, VEmpty, ScrollTrigger } from '@/ui'
VLoading,
VEmpty,
ScrollTrigger,
} from '@/ui'
/** /**
* Vue Mixin * Vue Mixin
@ -21,7 +17,8 @@ import {
export const nextPageMixin = <MappedItem extends { id: string }, RawItem>( export const nextPageMixin = <MappedItem extends { id: string }, RawItem>(
type: FeedsCardType, type: FeedsCardType,
jsonMapper: (obj: RawItem) => MappedItem, jsonMapper: (obj: RawItem) => MappedItem,
) => (Vue.extend({ ) =>
Vue.extend({
components: { components: {
VLoading, VLoading,
VEmpty, VEmpty,
@ -44,7 +41,7 @@ export const nextPageMixin = <MappedItem extends { id: string }, RawItem>(
const cards = this.sortedCards as MappedItem[] const cards = this.sortedCards as MappedItem[]
if (cards.length > 0) { if (cards.length > 0) {
setLatestID(cards[0].id) setLatestID(cards[0].id)
// console.log('setLatestID', cards[0].id) // console.log('setLatestID', cards[0].id)
} }
}, },
methods: { methods: {
@ -77,7 +74,8 @@ export const nextPageMixin = <MappedItem extends { id: string }, RawItem>(
this.hasMorePage = false this.hasMorePage = false
return return
} }
this.hasMorePage = lastCardID === 0 ? true : Boolean(lodash.get(json, 'data.has_more', true)) this.hasMorePage =
lastCardID === 0 ? true : Boolean(lodash.get(json, 'data.has_more', true))
} catch (error) { } catch (error) {
logError(error) logError(error)
} finally { } finally {
@ -85,4 +83,4 @@ export const nextPageMixin = <MappedItem extends { id: string }, RawItem>(
} }
}, },
}, },
})) })

View File

@ -6,10 +6,7 @@
<TextBox v-model="search" placeholder="搜索" linear></TextBox> <TextBox v-model="search" placeholder="搜索" linear></TextBox>
</div> </div>
<div class="operations"> <div class="operations">
<div <div class="operation" @click="toggleHistoryPause">
class="operation"
@click="toggleHistoryPause"
>
<VButton v-if="!paused" title="暂停记录历史" round> <VButton v-if="!paused" title="暂停记录历史" round>
<VIcon icon="mdi-pause" :size="14"></VIcon> <VIcon icon="mdi-pause" :size="14"></VIcon>
</VButton> </VButton>
@ -17,11 +14,7 @@
<VIcon icon="mdi-play" :size="14"></VIcon> <VIcon icon="mdi-play" :size="14"></VIcon>
</VButton> </VButton>
</div> </div>
<a <a class="operation" target="_blank" href="https://www.bilibili.com/account/history">
class="operation"
target="_blank"
href="https://www.bilibili.com/account/history"
>
<VButton title="查看更多" round> <VButton title="查看更多" round>
<VIcon icon="mdi-dots-horizontal" :size="18"></VIcon> <VIcon icon="mdi-dots-horizontal" :size="18"></VIcon>
</VButton> </VButton>
@ -29,9 +22,7 @@
</div> </div>
</div> </div>
<div class="header-row"> <div class="header-row">
<div class="row-title"> <div class="row-title">过滤:</div>
过滤:
</div>
<div class="type-filters"> <div class="type-filters">
<div v-for="t of types" :key="t.name" class="type-filter"> <div v-for="t of types" :key="t.name" class="type-filter">
<VButton <VButton
@ -55,11 +46,7 @@
<div class="time-group-name"> <div class="time-group-name">
{{ g.name }} {{ g.name }}
</div> </div>
<transition-group <transition-group name="time-group" tag="div" class="time-group-items">
name="time-group"
tag="div"
class="time-group-items"
>
<div v-for="h of g.items" :key="h.id" class="time-group-item"> <div v-for="h of g.items" :key="h.id" class="time-group-item">
<a class="cover-container" target="_blank" :href="h.url"> <a class="cover-container" target="_blank" :href="h.url">
<DpiImage <DpiImage
@ -73,26 +60,21 @@
class="progress" class="progress"
:style="{ width: h.progress * 100 + '%' }" :style="{ width: h.progress * 100 + '%' }"
></div> ></div>
<div <div v-if="h.progressText" class="floating progress-number">
v-if="h.progressText" {{ h.progress >= 1 ? '已看完' : h.progressText }}
class="floating progress-number" </div>
>{{ h.progress >= 1 ? '已看完' : h.progressText }}</div>
<div <div
v-if="h.liveStatus !== undefined" v-if="h.liveStatus !== undefined"
class="floating duration live-status" class="floating duration live-status"
:class="{ on: h.liveStatus === 1 }" :class="{ on: h.liveStatus === 1 }"
>{{ h.liveStatus === 1 ? '直播中': '未开播' }}</div> >
<div {{ h.liveStatus === 1 ? '直播中' : '未开播' }}
v-if="h.durationText" </div>
class="floating duration" <div v-if="h.durationText" class="floating duration">{{ h.durationText }}</div>
>{{ h.durationText }}</div>
</a> </a>
<a <a class="title" target="_blank" :href="h.url" :title="h.title">{{
class="title" h.title || h.upName + '的直播间'
target="_blank" }}</a>
:href="h.url"
:title="h.title"
>{{ h.title || h.upName + '的直播间' }}</a>
<a <a
class="up" class="up"
target="_blank" target="_blank"
@ -107,11 +89,7 @@
></DpiImage> ></DpiImage>
<div class="up-name">{{ h.upName }}</div> <div class="up-name">{{ h.upName }}</div>
</a> </a>
<div <div v-if="h.timeText" class="time" :title="new Date(h.viewAt).toLocaleString()">
v-if="h.timeText"
class="time"
:title="new Date(h.viewAt).toLocaleString()"
>
{{ h.timeText }} {{ h.timeText }}
</div> </div>
</div> </div>
@ -130,19 +108,9 @@
import { bilibiliApi, getJsonWithCredentials, postTextWithCredentials } from '@/core/ajax' import { bilibiliApi, getJsonWithCredentials, postTextWithCredentials } from '@/core/ajax'
import { formData, getCsrf } from '@/core/utils' import { formData, getCsrf } from '@/core/utils'
import { descendingSort } from '@/core/utils/sort' import { descendingSort } from '@/core/utils/sort'
import { import { VButton, VIcon, TextBox, VLoading, VEmpty, ScrollTrigger, DpiImage } from '@/ui'
VButton,
VIcon,
TextBox,
VLoading,
VEmpty,
ScrollTrigger,
DpiImage,
} from '@/ui'
import { popperMixin } from '../mixins' import { popperMixin } from '../mixins'
import { import { types, TypeFilter, HistoryItem, getHistoryItems, group } from './types'
types, TypeFilter, HistoryItem, getHistoryItems, group,
} from './types'
export default Vue.extend({ export default Vue.extend({
components: { components: {
@ -170,10 +138,10 @@ export default Vue.extend({
computed: { computed: {
canNextPage() { canNextPage() {
return ( return (
this.search === '' this.search === '' &&
&& !this.loading !this.loading &&
&& this.hasMorePage this.hasMorePage &&
&& this.types.every((t: TypeFilter) => t.checked) this.types.every((t: TypeFilter) => t.checked)
) )
}, },
}, },
@ -184,10 +152,7 @@ export default Vue.extend({
}, },
async created() { async created() {
try { try {
await Promise.all([ await Promise.all([this.nextPage(), this.updateHistoryPauseState()])
this.nextPage(),
this.updateHistoryPauseState(),
])
} finally { } finally {
this.loading = false this.loading = false
} }
@ -202,8 +167,8 @@ export default Vue.extend({
return false return false
} }
if ( if (
!item.title.toLowerCase().includes(this.search.toLowerCase()) !item.title.toLowerCase().includes(this.search.toLowerCase()) &&
&& !item.upName.toLowerCase().includes(this.search.toLowerCase()) !item.upName.toLowerCase().includes(this.search.toLowerCase())
) { ) {
return false return false
} }
@ -215,9 +180,7 @@ export default Vue.extend({
async nextPage() { async nextPage() {
const items = await getHistoryItems(this.viewTime) const items = await getHistoryItems(this.viewTime)
const cards: HistoryItem[] = lodash.uniqBy( const cards: HistoryItem[] = lodash.uniqBy(
this.cards this.cards.concat(items).sort(descendingSort((item: HistoryItem) => item.viewAt)),
.concat(items)
.sort(descendingSort((item: HistoryItem) => item.viewAt)),
item => item.id, item => item.id,
) )
this.cards = cards this.cards = cards
@ -228,7 +191,9 @@ export default Vue.extend({
this.hasMorePage = cards.length !== 0 this.hasMorePage = cards.length !== 0
}, },
async updateHistoryPauseState() { async updateHistoryPauseState() {
const result = await bilibiliApi(getJsonWithCredentials('https://api.bilibili.com/x/v2/history/shadow')) const result = await bilibiliApi(
getJsonWithCredentials('https://api.bilibili.com/x/v2/history/shadow'),
)
/* /*
result == true: 暂停 result == true: 暂停
result == {}: 没暂停 result == {}: 没暂停
@ -254,8 +219,8 @@ export default Vue.extend({
}) })
</script> </script>
<style lang="scss"> <style lang="scss">
@import "common"; @import 'common';
@import "../popup"; @import '../popup';
.custom-navbar-history-list { .custom-navbar-history-list {
width: 350px; width: 350px;

View File

@ -95,9 +95,18 @@ const formatTime = (date: Date) => {
const { yesterday } = getTimeData() const { yesterday } = getTimeData()
const timestamp = Number(date) const timestamp = Number(date)
if (timestamp >= yesterday) { if (timestamp >= yesterday) {
return `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}` return `${date.getHours().toString().padStart(2, '0')}:${date
.getMinutes()
.toString()
.padStart(2, '0')}`
} }
return `${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}` return `${(date.getMonth() + 1).toString().padStart(2, '0')}-${date
.getDate()
.toString()
.padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date
.getMinutes()
.toString()
.padStart(2, '0')}`
} }
const parseHistoryItem = (item: any): HistoryItem => { const parseHistoryItem = (item: any): HistoryItem => {
if (item.history.business === 'article') { if (item.history.business === 'article') {
@ -110,7 +119,7 @@ const parseHistoryItem = (item: any): HistoryItem => {
oid, // 直播 房间号 / 专栏 cv 号 oid, // 直播 房间号 / 专栏 cv 号
} = item.history } = item.history
const progressParam = item.progress > 0 ? `t=${item.progress}` : 't=0' const progressParam = item.progress > 0 ? `t=${item.progress}` : 't=0'
const progress = item.progress === -1 ? 1 : (item.progress / item.duration) const progress = item.progress === -1 ? 1 : item.progress / item.duration
const https = (url: string) => url.replace('http:', 'https:') const https = (url: string) => url.replace('http:', 'https:')
const time = new Date(item.view_at * 1000) const time = new Date(item.view_at * 1000)
const cover = (() => { const cover = (() => {

View File

@ -9,10 +9,7 @@
> >
<a :href="data.link" target="_blank"> <a :href="data.link" target="_blank">
<svg aria-hidden="true"> <svg aria-hidden="true">
<use <use :href="'#header-icon-' + data.icon" :xlink:href="'#header-icon-' + data.icon" />
:href="'#header-icon-' + data.icon"
:xlink:href="'#header-icon-' + data.icon"
/>
</svg> </svg>
<div class="name">{{ name }}</div> <div class="name">{{ name }}</div>
<span class="count"> <span class="count">
@ -26,7 +23,8 @@
class="sub-region" class="sub-region"
:href="url" :href="url"
target="_blank" target="_blank"
>{{ regionName }}</a> >{{ regionName }}</a
>
</div> </div>
</div> </div>
</div> </div>
@ -94,8 +92,8 @@ export default Vue.extend({
// 3. https://stackoverflow.com/a/33899301/13860169 // 3. https://stackoverflow.com/a/33899301/13860169
flex-direction: row; flex-direction: row;
writing-mode: vertical-lr; writing-mode: vertical-lr;
&>* { & > * {
writing-mode: horizontal-tb writing-mode: horizontal-tb;
} }
.category-item { .category-item {

View File

@ -1,10 +1,5 @@
<template> <template>
<iframe <iframe :src="item.src" frameborder="0" :width="item.width" :height="item.height"></iframe>
:src="item.src"
frameborder="0"
:width="item.width"
:height="item.height"
></iframe>
</template> </template>
<script lang="ts"> <script lang="ts">

View File

@ -1,4 +1,8 @@
import { defineComponentMetadata, defineOptionsMetadata, OptionsOfMetadata } from '@/components/define' import {
defineComponentMetadata,
defineOptionsMetadata,
OptionsOfMetadata,
} from '@/components/define'
import { LaunchBarActionProvider } from '@/components/launch-bar/launch-bar-action' import { LaunchBarActionProvider } from '@/components/launch-bar/launch-bar-action'
import { urlInclude, urlExclude } from './urls' import { urlInclude, urlExclude } from './urls'
import { entry } from './entry' import { entry } from './entry'
@ -77,10 +81,7 @@ export const component = defineComponentMetadata({
name: 'customNavbar', name: 'customNavbar',
displayName: '自定义顶栏', displayName: '自定义顶栏',
entry, entry,
tags: [ tags: [componentsTags.style, componentsTags.general],
componentsTags.style,
componentsTags.general,
],
options, options,
urlInclude, urlInclude,
urlExclude, urlExclude,
@ -110,15 +111,17 @@ export const component = defineComponentMetadata({
addData('launchBar.actions', (providers: LaunchBarActionProvider[]) => { addData('launchBar.actions', (providers: LaunchBarActionProvider[]) => {
providers.push({ providers.push({
name: 'navbarSettings', name: 'navbarSettings',
getActions: async () => [{ getActions: async () => [
name: '自定义顶栏设置', {
description: 'Custom Navbar Settings', name: '自定义顶栏设置',
icon: 'mdi-sort', description: 'Custom Navbar Settings',
action: async () => { icon: 'mdi-sort',
const { toggleNavbarSettings } = await import('./settings/vm') action: async () => {
toggleNavbarSettings() const { toggleNavbarSettings } = await import('./settings/vm')
toggleNavbarSettings()
},
}, },
}], ],
}) })
}) })
}, },

View File

@ -37,17 +37,12 @@ export default Vue.extend({
this.seasonLogoUrl = '' this.seasonLogoUrl = ''
return return
} }
const json = await getJson( const json = await getJson('https://api.bilibili.com/x/web-show/page/header?resource_id=1')
'https://api.bilibili.com/x/web-show/page/header?resource_id=1',
)
if (json.code !== 0) { if (json.code !== 0) {
this.seasonLogoUrl = '' this.seasonLogoUrl = ''
return return
} }
this.seasonLogoUrl = lodash.get(json, 'data.litpic', '').replace( this.seasonLogoUrl = lodash.get(json, 'data.litpic', '').replace('http:', 'https:')
'http:',
'https:',
)
}, },
true, true,
) )

View File

@ -7,7 +7,8 @@
:href="e.href" :href="e.href"
:data-count="e.count || null" :data-count="e.count || null"
@click="clearCount(e)" @click="clearCount(e)"
>{{ e.name }}</a> >{{ e.name }}</a
>
</div> </div>
</div> </div>
</template> </template>
@ -99,9 +100,7 @@ export default Vue.extend({
return return
} }
const [mainJson, messageJson] = await Promise.all([ const [mainJson, messageJson] = await Promise.all([
getJsonWithCredentials( getJsonWithCredentials('https://api.bilibili.com/x/msgfeed/unread'),
'https://api.bilibili.com/x/msgfeed/unread',
),
getJsonWithCredentials( getJsonWithCredentials(
'https://api.vc.bilibili.com/session_svr/v1/session_svr/single_unread', 'https://api.vc.bilibili.com/session_svr/v1/session_svr/single_unread',
), ),

View File

@ -1,10 +1,7 @@
<template> <template>
<div class="ranking-popup" role="list"> <div class="ranking-popup" role="list">
<div v-for="e of entries" :key="e.name" class="ranking-entry" role="listitem"> <div v-for="e of entries" :key="e.name" class="ranking-entry" role="listitem">
<a <a target="_blank" :href="e.href">{{ e.name }}</a>
target="_blank"
:href="e.href"
>{{ e.name }}</a>
</div> </div>
</div> </div>
</template> </template>

View File

@ -17,7 +17,7 @@ export default Vue.extend({
// transparent mode / no fill // transparent mode / no fill
.launch-bar { .launch-bar {
--color: var(--custom-navbar-foreground); --color: var(--custom-navbar-foreground);
background-color: #000A; background-color: #000a;
opacity: 0.5; opacity: 0.5;
transition: opacity 0.2s ease-out; transition: opacity 0.2s ease-out;
padding: 2px 6px; padding: 2px 6px;

View File

@ -13,11 +13,7 @@
<script lang="ts"> <script lang="ts">
import { getUID } from '@/core/utils' import { getUID } from '@/core/utils'
import { VIcon, VButton } from '@/ui' import { VIcon, VButton } from '@/ui'
import { import { setTriggerElement, loadNavbarSettings, toggleNavbarSettings } from './vm'
setTriggerElement,
loadNavbarSettings,
toggleNavbarSettings,
} from './vm'
export default Vue.extend({ export default Vue.extend({
components: { components: {

View File

@ -9,9 +9,7 @@
> >
<div class="navbar-settings-header"> <div class="navbar-settings-header">
<VIcon class="title-icon" icon="mdi-sort" :size="24"></VIcon> <VIcon class="title-icon" icon="mdi-sort" :size="24"></VIcon>
<div class="title"> <div class="title">顶栏布局设置</div>
顶栏布局设置
</div>
<div class="grow"></div> <div class="grow"></div>
<div class="close" @click="open = false"> <div class="close" @click="open = false">
<VIcon icon="close" :size="18"></VIcon> <VIcon icon="close" :size="18"></VIcon>
@ -19,9 +17,7 @@
</div> </div>
<div class="navbar-settings-content"> <div class="navbar-settings-content">
<div class="navbar-settings-section"> <div class="navbar-settings-section">
<div class="navbar-settings-section-title"> <div class="navbar-settings-section-title">边缘间距</div>
边缘间距
</div>
<div class="navbar-settings-section-description"> <div class="navbar-settings-section-description">
设定两侧边缘处的间距, 单位为百分比, 100%为整个顶栏的宽度. 设定两侧边缘处的间距, 单位为百分比, 100%为整个顶栏的宽度.
<br />空间不足时, 实际呈现的间距会自动缩小. <br />空间不足时, 实际呈现的间距会自动缩小.
@ -32,18 +28,12 @@
@mouseout="peekPadding(false)" @mouseout="peekPadding(false)"
> >
<VSlider v-model="padding" :min="0" :max="40" :step="0.5"></VSlider> <VSlider v-model="padding" :min="0" :max="40" :step="0.5"></VSlider>
<div class="padding-value"> <div class="padding-value">{{ padding.toFixed(1) }}%</div>
{{ padding.toFixed(1) }}%
</div>
</div> </div>
</div> </div>
<div class="navbar-settings-section"> <div class="navbar-settings-section">
<div class="navbar-settings-section-title"> <div class="navbar-settings-section-title">元素呈现</div>
元素呈现 <div class="navbar-settings-section-description">
</div>
<div
class="navbar-settings-section-description"
>
按住并拖动可以调整顺序, 点击眼睛图标可以切换隐藏/显示. 按住并拖动可以调整顺序, 点击眼睛图标可以切换隐藏/显示.
</div> </div>
<VLoading v-if="!loaded" /> <VLoading v-if="!loaded" />
@ -67,11 +57,7 @@
<div class="toggle-visible"> <div class="toggle-visible">
<VIcon <VIcon
:size="18" :size="18"
:icon=" :icon="item.hidden ? 'mdi-eye-off-outline' : 'mdi-eye-outline'"
item.hidden
? 'mdi-eye-off-outline'
: 'mdi-eye-outline'
"
@click="toggleVisible(item)" @click="toggleVisible(item)"
></VIcon> ></VIcon>
</div> </div>
@ -83,26 +69,20 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { SortableEvent } from 'sortablejs' import { SortableEvent } from 'sortablejs'
import { import { VPopup, VIcon, VSlider, VLoading } from '@/ui'
VPopup,
VIcon,
VSlider,
VLoading,
} from '@/ui'
import { addComponentListener } from '@/core/settings' import { addComponentListener } from '@/core/settings'
import { dqa } from '@/core/utils' import { dqa } from '@/core/utils'
import { SortableJSLibrary } from '@/core/runtime-library' import { SortableJSLibrary } from '@/core/runtime-library'
import { getData } from '@/plugins/data' import { getData } from '@/plugins/data'
import { import { CustomNavbarItem, CustomNavbarRenderedItems } from '../custom-navbar-item'
CustomNavbarItem,
CustomNavbarRenderedItems,
} from '../custom-navbar-item'
import { checkSequentialOrder, sortItems } from './orders' import { checkSequentialOrder, sortItems } from './orders'
const { navbarOptions } = CustomNavbarItem const { navbarOptions } = CustomNavbarItem
const [rendered] = getData(CustomNavbarRenderedItems) as [{ const [rendered] = getData(CustomNavbarRenderedItems) as [
items: CustomNavbarItem[] {
}] items: CustomNavbarItem[]
},
]
export default Vue.extend({ export default Vue.extend({
components: { components: {
VPopup, VPopup,
@ -166,9 +146,9 @@ export default Vue.extend({
const container = this.$refs.navbarSortList as HTMLElement const container = this.$refs.navbarSortList as HTMLElement
const element = e.item const element = e.item
console.log(`${element.getAttribute('data-name')} ${e.oldIndex}->${e.newIndex}`) console.log(`${element.getAttribute('data-name')} ${e.oldIndex}->${e.newIndex}`)
const ordersMap = Object.fromEntries([...container.children].map((el, index) => ( const ordersMap = Object.fromEntries(
[el.getAttribute('data-name') as string, index] [...container.children].map((el, index) => [el.getAttribute('data-name') as string, index]),
))) )
this.rendered.items = sortItems(rendered.items, ordersMap) this.rendered.items = sortItems(rendered.items, ordersMap)
}, },
toggleVisible(item: CustomNavbarItem) { toggleVisible(item: CustomNavbarItem) {
@ -186,7 +166,7 @@ export default Vue.extend({
}) })
</script> </script>
<style lang="scss"> <style lang="scss">
@import "common"; @import 'common';
.custom-navbar-settings { .custom-navbar-settings {
@include popup(); @include popup();
width: 400px; width: 400px;

View File

@ -7,7 +7,7 @@ const regenerateOrder = (items: CustomNavbarItem[]) => {
} }
item.order = index item.order = index
}) })
const orderMap = Object.fromEntries(items.map(it => ([it.name, it.order]))) const orderMap = Object.fromEntries(items.map(it => [it.name, it.order]))
CustomNavbarItem.navbarOptions.order = orderMap CustomNavbarItem.navbarOptions.order = orderMap
} }
export const checkSequentialOrder = (items: CustomNavbarItem[]) => { export const checkSequentialOrder = (items: CustomNavbarItem[]) => {

View File

@ -38,7 +38,7 @@ export default Vue.extend({
}) })
</script> </script>
<style lang="scss"> <style lang="scss">
@import "../popup"; @import '../popup';
.navbar-subscriptions { .navbar-subscriptions {
width: 380px; width: 380px;

View File

@ -17,27 +17,18 @@
<div class="card-info"> <div class="card-info">
<h1 class="title" :title="card.title">{{ card.title }}</h1> <h1 class="title" :title="card.title">{{ card.title }}</h1>
<div class="progress-row"> <div class="progress-row">
<div <div v-if="card.status" class="status" :class="'status-' + card.status">
v-if="card.status" {{ card.statusText }}
class="status" </div>
:class="'status-' + card.status"
>{{ card.statusText }}</div>
<div <div
v-if="card.progress" v-if="card.progress"
class="progress" class="progress"
:title="card.progress + ' | ' + card.latest" :title="card.progress + ' | ' + card.latest"
>{{ card.progress }} | {{ card.latest }}</div>
<div
v-else
class="progress"
:title="card.latest"
>{{ card.latest }}</div>
<a
class="info"
:href="card.mediaUrl"
target="_blank"
title="详细信息"
> >
{{ card.progress }} | {{ card.latest }}
</div>
<div v-else class="progress" :title="card.latest">{{ card.latest }}</div>
<a class="info" :href="card.mediaUrl" target="_blank" title="详细信息">
<VIcon icon="mdi-information-outline" :size="16"></VIcon> <VIcon icon="mdi-information-outline" :size="16"></VIcon>
</a> </a>
</div> </div>
@ -52,13 +43,7 @@
<script lang="ts"> <script lang="ts">
import { getUID } from '@/core/utils' import { getUID } from '@/core/utils'
import { logError } from '@/core/utils/log' import { logError } from '@/core/utils/log'
import { import { DpiImage, VLoading, VEmpty, VIcon, ScrollTrigger } from '@/ui'
DpiImage,
VLoading,
VEmpty,
VIcon,
ScrollTrigger,
} from '@/ui'
import { getJsonWithCredentials } from '@/core/ajax' import { getJsonWithCredentials } from '@/core/ajax'
import { SubscriptionTypes } from './subscriptions' import { SubscriptionTypes } from './subscriptions'
@ -129,22 +114,24 @@ export default Vue.extend({
logError(`加载订阅信息失败: ${json.message}`) logError(`加载订阅信息失败: ${json.message}`)
return return
} }
const cards = lodash.uniqBy( const cards = lodash
(this.cards as any[]).concat( .uniqBy(
(lodash.get(json, 'data.list') as any[]).map(item => ({ (this.cards as any[]).concat(
title: item.title, (lodash.get(json, 'data.list') as any[]).map(item => ({
coverUrl: item.square_cover.replace('http:', 'https:'), title: item.title,
latest: item.new_ep.index_show, coverUrl: item.square_cover.replace('http:', 'https:'),
progress: item.progress, latest: item.new_ep.index_show,
id: item.season_id, progress: item.progress,
status: item.follow_status, id: item.season_id,
statusText: getStatusText(item.follow_status), status: item.follow_status,
playUrl: `https://www.bilibili.com/bangumi/play/ss${item.season_id}`, statusText: getStatusText(item.follow_status),
mediaUrl: `https://www.bilibili.com/bangumi/media/md${item.media_id}`, 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, ),
).sort(subscriptionSorter) card => card.id,
)
.sort(subscriptionSorter)
this.page++ this.page++
this.cards = cards this.cards = cards
this.hasMorePage = lodash.get(json, 'data.total', 0) > this.cards.length this.hasMorePage = lodash.get(json, 'data.total', 0) > this.cards.length
@ -157,7 +144,7 @@ export default Vue.extend({
</script> </script>
<style lang="scss"> <style lang="scss">
@import "common"; @import 'common';
.subscription-list { .subscription-list {
width: 100%; width: 100%;

View File

@ -12,7 +12,10 @@ export const checkTransparentFill = async (vm: {
return return
} }
sq( sq(
() => dqa('.animated-banner video, .banner-img img, #banner_link, .international-header .bili-banner, .bili-header__banner'), () =>
dqa(
'.animated-banner video, .banner-img img, #banner_link, .international-header .bili-banner, .bili-header__banner',
),
banners => { banners => {
if (banners.length === 0) { if (banners.length === 0) {
return false return false
@ -35,11 +38,15 @@ export const checkTransparentFill = async (vm: {
if (banner.length === 0) { if (banner.length === 0) {
return return
} }
addComponentListener('customNavbar.transparent', value => { addComponentListener(
if (!getComponentSettings('hideBanner').enabled) { 'customNavbar.transparent',
vm.toggleStyle(value, 'transparent') value => {
} if (!getComponentSettings('hideBanner').enabled) {
}, true) vm.toggleStyle(value, 'transparent')
}
},
true,
)
addComponentListener('hideBanner', value => { addComponentListener('hideBanner', value => {
if (getComponentSettings('customNavbar').options.transparent) { if (getComponentSettings('customNavbar').options.transparent) {
vm.toggleStyle(!value, 'transparent') vm.toggleStyle(!value, 'transparent')

View File

@ -1,9 +1,7 @@
<template> <template>
<div class="navbar-upload"> <div class="navbar-upload">
<VIcon icon="upload" :size="18"></VIcon> <VIcon icon="upload" :size="18"></VIcon>
<div class="navbar-upload-name"> <div class="navbar-upload-name">投稿</div>
投稿
</div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">

View File

@ -1,34 +1,21 @@
<template> <template>
<div role="list" class="upload-popup"> <div role="list" class="upload-popup">
<div role="listitem"> <div role="listitem">
<a <a target="_blank" href="https://member.bilibili.com/platform/upload/text/apply">专栏投稿</a>
target="_blank"
href="https://member.bilibili.com/platform/upload/text/apply"
>专栏投稿</a>
</div> </div>
<div role="listitem"> <div role="listitem">
<a <a target="_blank" href="https://member.bilibili.com/platform/upload/audio/frame">音频投稿</a>
target="_blank"
href="https://member.bilibili.com/platform/upload/audio/frame"
>音频投稿</a>
</div> </div>
<div role="listitem"> <div role="listitem">
<a <a target="_blank" href="https://member.bilibili.com/platform/upload/sticker">贴纸投稿</a>
target="_blank"
href="https://member.bilibili.com/platform/upload/sticker"
>贴纸投稿</a>
</div> </div>
<div role="listitem"> <div role="listitem">
<a <a target="_blank" href="https://member.bilibili.com/platform/upload/video/frame">视频投稿</a>
target="_blank"
href="https://member.bilibili.com/platform/upload/video/frame"
>视频投稿</a>
</div> </div>
<div role="listitem"> <div role="listitem">
<a <a target="_blank" href="https://member.bilibili.com/platform/upload-manager/article"
target="_blank" >投稿管理</a
href="https://member.bilibili.com/platform/upload-manager/article" >
>投稿管理</a>
</div> </div>
<div role="listitem"> <div role="listitem">
<a target="_blank" href="https://member.bilibili.com/platform/home">创作中心</a> <a target="_blank" href="https://member.bilibili.com/platform/home">创作中心</a>
@ -43,7 +30,7 @@ export default Vue.extend({
}) })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "../nav-link"; @import '../nav-link';
.upload-popup { .upload-popup {
width: max-content; width: max-content;
a { a {

View File

@ -1,16 +1,10 @@
<template> <template>
<div class="user-info-panel"> <div class="user-info-panel">
<div v-if="isLogin && userInfo.isLogin === true" class="logged-in"> <div v-if="isLogin && userInfo.isLogin === true" class="logged-in">
<a <a class="name" target="_blank" href="https://space.bilibili.com/">{{ userInfo.uname }}</a>
class="name" <a class="type" target="_blank" href="https://account.bilibili.com/account/big">{{
target="_blank" userType
href="https://space.bilibili.com/" }}</a>
>{{ userInfo.uname }}</a>
<a
class="type"
target="_blank"
href="https://account.bilibili.com/account/big"
>{{ userType }}</a>
<div v-if="userInfo.vipStatus === 1 && userInfo.vipType === 2" class="privileges row"> <div v-if="userInfo.vipStatus === 1 && userInfo.vipType === 2" class="privileges row">
<div <div
class="b-coin" class="b-coin"
@ -43,15 +37,11 @@
:size="30" :size="30"
class="level-icon plus" class="level-icon plus"
/> />
<VIcon <VIcon v-else :icon="'lv' + userInfo.level_info.current_level" class="level-icon" />
v-else
:icon="'lv' + userInfo.level_info.current_level"
class="level-icon"
/>
</a> </a>
<span <span class="level-progress-label"
class="level-progress-label" >{{ userInfo.level_info.current_exp }} / {{ userInfo.level_info.next_exp }}</span
>{{ userInfo.level_info.current_exp }} / {{ userInfo.level_info.next_exp }}</span> >
</div> </div>
<div class="level-progress separator"> <div class="level-progress separator">
<div class="level-progress-thumb" :style="levelProgressStyle"></div> <div class="level-progress-thumb" :style="levelProgressStyle"></div>
@ -77,21 +67,11 @@
<VIcon v-if="userInfo.email_verified" icon="ok" :size="18"></VIcon> <VIcon v-if="userInfo.email_verified" icon="ok" :size="18"></VIcon>
<VIcon v-else icon="cancel" :size="18"></VIcon> <VIcon v-else icon="cancel" :size="18"></VIcon>
</a> </a>
<a <a class="item" target="_blank" href="https://account.bilibili.com/site/coin" title="硬币">
class="item"
target="_blank"
href="https://account.bilibili.com/site/coin"
title="硬币"
>
<VIcon icon="coin-outline" :size="20"></VIcon> <VIcon icon="coin-outline" :size="20"></VIcon>
<span>{{ userInfo.money }}</span> <span>{{ userInfo.money }}</span>
</a> </a>
<a <a class="item" target="_blank" href="https://pay.bilibili.com/bb_balance.html" title="B币">
class="item"
target="_blank"
href="https://pay.bilibili.com/bb_balance.html"
title="B币"
>
<VIcon icon="b-coin-outline" :size="20"></VIcon> <VIcon icon="b-coin-outline" :size="20"></VIcon>
<span>{{ userInfo.wallet.bcoin_balance }}</span> <span>{{ userInfo.wallet.bcoin_balance }}</span>
</a> </a>
@ -103,21 +83,24 @@
:href="'https://space.bilibili.com/' + userInfo.mid + '/fans/follow'" :href="'https://space.bilibili.com/' + userInfo.mid + '/fans/follow'"
target="_blank" target="_blank"
> >
<div class="stats-number">{{ stat.following | count }}</div>关注 <div class="stats-number">{{ stat.following | count }}</div>
关注
</a> </a>
<a <a
class="stats-item" class="stats-item"
:href="'https://space.bilibili.com/' + userInfo.mid + '/fans/fans'" :href="'https://space.bilibili.com/' + userInfo.mid + '/fans/fans'"
target="_blank" target="_blank"
> >
<div class="stats-number">{{ stat.follower | count }}</div>粉丝 <div class="stats-number">{{ stat.follower | count }}</div>
粉丝
</a> </a>
<a <a
class="stats-item" class="stats-item"
:href="'https://space.bilibili.com/' + userInfo.mid + '/dynamic'" :href="'https://space.bilibili.com/' + userInfo.mid + '/dynamic'"
target="_blank" target="_blank"
> >
<div class="stats-number">{{ stat.dynamic_count | count }}</div>动态 <div class="stats-number">{{ stat.dynamic_count | count }}</div>
动态
</a> </a>
</div> </div>
<div class="separator"></div> <div class="separator"></div>
@ -159,25 +142,14 @@
> >
<VIcon icon="course"></VIcon>我的课程 <VIcon icon="course"></VIcon>我的课程
</a> </a>
<div <div class="logout grey-button" @click="logout()">退出登录</div>
class="logout grey-button"
@click="logout()"
>
退出登录
</div>
</div> </div>
<div v-if="!isLogin" class="not-logged-in"> <div v-if="!isLogin" class="not-logged-in">
<h1 class="welcome"> <h1 class="welcome">欢迎来到 bilibili</h1>
欢迎来到 bilibili <a href="https://passport.bilibili.com/register/phone.html" class="signup grey-button"
</h1> >注册</a
<a >
href="https://passport.bilibili.com/register/phone.html" <a href="https://passport.bilibili.com/login" class="login theme-button">登录</a>
class="signup grey-button"
>注册</a>
<a
href="https://passport.bilibili.com/login"
class="login theme-button"
>登录</a>
</div> </div>
</div> </div>
</template> </template>
@ -260,30 +232,18 @@ export default Vue.extend({
async created() { async created() {
const userInfo = await getUserInfo() const userInfo = await getUserInfo()
this.userInfo = userInfo this.userInfo = userInfo
const json = await getJsonWithCredentials( const json = await getJsonWithCredentials('https://api.bilibili.com/x/web-interface/nav/stat')
'https://api.bilibili.com/x/web-interface/nav/stat',
)
this.stat = json.data || {} this.stat = json.data || {}
if (this.isLogin && this.userInfo.vipType === 2) { if (this.isLogin && this.userInfo.vipType === 2) {
// //
const privileges = await getJsonWithCredentials( const privileges = await getJsonWithCredentials('https://api.bilibili.com/x/vip/privilege/my')
'https://api.bilibili.com/x/vip/privilege/my',
)
if (privileges.code === 0) { if (privileges.code === 0) {
const bCoin = privileges.data.list.find( const bCoin = privileges.data.list.find((it: { type: PrivilegeType }) => it.type === 1)
(it: { type: PrivilegeType }) => it.type === 1,
)
this.privileges.bCoin.received = bCoin.state === 1 this.privileges.bCoin.received = bCoin.state === 1
this.privileges.bCoin.expire = new Date( this.privileges.bCoin.expire = new Date(bCoin.expire_time * 1000).toLocaleDateString()
bCoin.expire_time * 1000, const coupons = privileges.data.list.find((it: { type: PrivilegeType }) => it.type === 2)
).toLocaleDateString()
const coupons = privileges.data.list.find(
(it: { type: PrivilegeType }) => it.type === 2,
)
this.privileges.coupons.received = coupons.state === 1 this.privileges.coupons.received = coupons.state === 1
this.privileges.coupons.expire = new Date( this.privileges.coupons.expire = new Date(coupons.expire_time * 1000).toLocaleDateString()
coupons.expire_time * 1000,
).toLocaleDateString()
} }
} }
}, },

View File

@ -4,7 +4,11 @@
<div class="search"> <div class="search">
<TextBox v-model="search" linear placeholder="搜索"></TextBox> <TextBox v-model="search" linear placeholder="搜索"></TextBox>
</div> </div>
<a class="operation" target="_blank" href="https://www.bilibili.com/medialist/play/watchlater"> <a
class="operation"
target="_blank"
href="https://www.bilibili.com/medialist/play/watchlater"
>
<VButton class="round-button" title="播放全部" round> <VButton class="round-button" title="播放全部" round>
<VIcon icon="mdi-play" :size="18"></VIcon> <VIcon icon="mdi-play" :size="18"></VIcon>
</VButton> </VButton>
@ -17,39 +21,21 @@
</div> </div>
<VLoading v-if="loading"></VLoading> <VLoading v-if="loading"></VLoading>
<VEmpty v-else-if="!loading && cards.length === 0"></VEmpty> <VEmpty v-else-if="!loading && cards.length === 0"></VEmpty>
<transition-group <transition-group v-else name="cards" tag="div" class="watchlater-list-content">
v-else <div v-for="(card, index) of filteredCards" :key="card.aid" class="watchlater-card">
name="cards"
tag="div"
class="watchlater-list-content"
>
<div
v-for="(card, index) of filteredCards"
:key="card.aid"
class="watchlater-card"
>
<a class="cover-container" target="_blank" :href="card.href"> <a class="cover-container" target="_blank" :href="card.href">
<DpiImage <DpiImage
class="cover" class="cover"
:src="card.coverUrl" :src="card.coverUrl"
:size="{ width: 130, height: 85 }" :size="{ width: 130, height: 85 }"
></DpiImage> ></DpiImage>
<div <div class="floating remove" title="移除" @click.prevent="remove(card.aid, index)">
class="floating remove"
title="移除"
@click.prevent="remove(card.aid, index)"
>
<VIcon icon="mdi-close" :size="16"></VIcon> <VIcon icon="mdi-close" :size="16"></VIcon>
</div> </div>
<div class="floating duration">{{ card.durationText }}</div> <div class="floating duration">{{ card.durationText }}</div>
<div v-if="card.complete" class="floating viewed">已观看</div> <div v-if="card.complete" class="floating viewed">已观看</div>
</a> </a>
<a <a class="title" target="_blank" :href="card.href" :title="card.title">{{ card.title }}</a>
class="title"
target="_blank"
:href="card.href"
:title="card.title"
>{{ card.title }}</a>
<a <a
class="up" class="up"
target="_blank" target="_blank"
@ -72,14 +58,7 @@ import {
RawWatchlaterItem, RawWatchlaterItem,
toggleWatchlater, toggleWatchlater,
} from '@/components/video/watchlater' } from '@/components/video/watchlater'
import { import { VLoading, VEmpty, TextBox, VButton, VIcon, DpiImage } from '@/ui'
VLoading,
VEmpty,
TextBox,
VButton,
VIcon,
DpiImage,
} from '@/ui'
import { popperMixin } from '../mixins' import { popperMixin } from '../mixins'
interface WatchlaterCard { interface WatchlaterCard {
@ -191,17 +170,17 @@ export default Vue.extend({
const search = this.search.toLowerCase() const search = this.search.toLowerCase()
const cardsList = this.$el.querySelector('.watchlater-list-content') as HTMLElement const cardsList = this.$el.querySelector('.watchlater-list-content') as HTMLElement
cardsList.scrollTo(0, 0) cardsList.scrollTo(0, 0)
this.filteredCards = (this.cards as WatchlaterCard[]).filter(card => ( this.filteredCards = (this.cards as WatchlaterCard[]).filter(
card.title.toLowerCase().includes(search) card =>
|| card.upName.toLowerCase().includes(search) card.title.toLowerCase().includes(search) || card.upName.toLowerCase().includes(search),
)) )
}, 100), }, 100),
}, },
}) })
</script> </script>
<style lang="scss"> <style lang="scss">
@import "common"; @import 'common';
@import "../popup"; @import '../popup';
.custom-navbar .watchlater-list { .custom-navbar .watchlater-list {
@include navbar-popup-height(); @include navbar-popup-height();

View File

@ -22,10 +22,7 @@ export const component: ComponentMetadata = {
}) })
}, },
urlExclude: darkExcludes, urlExclude: darkExcludes,
tags: [ tags: [componentsTags.style, componentsTags.general],
componentsTags.style,
componentsTags.general,
],
description: { description: {
'zh-CN': ` 'zh-CN': `
使 / , \`夜间模式计划时段\` 一同使用. 使 / , \`夜间模式计划时段\` 一同使用.

View File

@ -8,7 +8,10 @@ const add = async () => {
localStorage.setItem('pbp_theme_v4', 'b') localStorage.setItem('pbp_theme_v4', 'b')
const meta = dq('meta[name="theme-color"]') as HTMLMetaElement const meta = dq('meta[name="theme-color"]') as HTMLMetaElement
if (!meta) { if (!meta) {
document.head.insertAdjacentHTML('beforeend', `<meta name="theme-color" content="${darkMetaColor}">`) document.head.insertAdjacentHTML(
'beforeend',
`<meta name="theme-color" content="${darkMetaColor}">`,
)
} else { } else {
meta.dataset.light = meta.content meta.dataset.light = meta.content
meta.content = darkMetaColor meta.content = darkMetaColor
@ -40,10 +43,7 @@ export const component = defineComponentMetadata({
setTimeout(remove, changeDelay) setTimeout(remove, changeDelay)
}, },
description: '启用夜间模式能更好地适应光线暗的环境, 并会大量应用主题颜色.', description: '启用夜间模式能更好地适应光线暗的环境, 并会大量应用主题颜色.',
tags: [ tags: [componentsTags.style, componentsTags.general],
componentsTags.style,
componentsTags.general,
],
instantStyles: [ instantStyles: [
{ {
name: 'dark-mode', name: 'dark-mode',

View File

@ -13,14 +13,14 @@ class ScheduleTime {
this.hour = now.getHours() this.hour = now.getHours()
this.minute = now.getMinutes() this.minute = now.getMinutes()
} else if (args.length === 1) { } else if (args.length === 1) {
const [text] = args; const [text] = args
[this.hour, this.minute] = text ;[this.hour, this.minute] = text
.split(':') .split(':')
.slice(0, 2) .slice(0, 2)
.map(it => ScheduleTime.validatePart(it)) .map(it => ScheduleTime.validatePart(it))
this.normalize() this.normalize()
} else if (args.length === 2) { } else if (args.length === 2) {
[this.hour, this.minute] = args ;[this.hour, this.minute] = args
} }
} }
normalize() { normalize() {
@ -40,17 +40,13 @@ class ScheduleTime {
} }
} }
lessThan(other: ScheduleTime) { lessThan(other: ScheduleTime) {
if (this.hour < other.hour if (this.hour < other.hour || (this.hour === other.hour && this.minute < other.minute)) {
|| (this.hour === other.hour && this.minute < other.minute)
) {
return true return true
} }
return false return false
} }
greaterThan(other: ScheduleTime) { greaterThan(other: ScheduleTime) {
if (this.hour > other.hour if (this.hour > other.hour || (this.hour === other.hour && this.minute > other.minute)) {
|| (this.hour === other.hour && this.minute > other.minute)
) {
return true return true
} }
return false return false
@ -113,11 +109,9 @@ const checkTime = (settings: ComponentSettings) => {
export const component: ComponentMetadata = { export const component: ComponentMetadata = {
name: 'darkModeSchedule', name: 'darkModeSchedule',
displayName: '夜间模式计划时段', displayName: '夜间模式计划时段',
description: '设置一个使用夜间模式的时间段, 进入 / 离开此时间段时, 会自动开启 / 关闭夜间模式. 结束时间小于起始时间时将视为次日, 如 `18:00` 至 `6:00` 表示晚上 18:00 到次日 6:00. 请勿和 \`夜间模式跟随系统\` 一同使用.', description:
tags: [ '设置一个使用夜间模式的时间段, 进入 / 离开此时间段时, 会自动开启 / 关闭夜间模式. 结束时间小于起始时间时将视为次日, 如 `18:00` 至 `6:00` 表示晚上 18:00 到次日 6:00. 请勿和 `夜间模式跟随系统` 一同使用.',
componentsTags.style, tags: [componentsTags.style, componentsTags.general],
componentsTags.general,
],
entry: ({ settings }) => fullyLoaded(() => checkTime(settings)), entry: ({ settings }) => fullyLoaded(() => checkTime(settings)),
urlExclude: darkExcludes, urlExclude: darkExcludes,
options: { options: {

View File

@ -4,9 +4,7 @@ import { bangumiUrls } from '@/core/utils/urls'
export const component = defineComponentMetadata({ export const component = defineComponentMetadata({
displayName: '隐藏番剧点评', displayName: '隐藏番剧点评',
tags: [ tags: [componentsTags.style],
componentsTags.style,
],
...toggleStyle('hideBangumiReviews', () => import('./reviews.scss')), ...toggleStyle('hideBangumiReviews', () => import('./reviews.scss')),
urlInclude: bangumiUrls, urlInclude: bangumiUrls,
description: { description: {

View File

@ -4,9 +4,7 @@ import { bangumiUrls } from '@/core/utils/urls'
export const component = defineComponentMetadata({ export const component = defineComponentMetadata({
displayName: '隐藏番剧承包', displayName: '隐藏番剧承包',
tags: [ tags: [componentsTags.style],
componentsTags.style,
],
...toggleStyle('hideBangumiSponsors', () => import('./sponsors.scss')), ...toggleStyle('hideBangumiSponsors', () => import('./sponsors.scss')),
urlInclude: bangumiUrls, urlInclude: bangumiUrls,
description: { description: {

View File

@ -11,10 +11,7 @@ export const component = defineComponentMetadata({
}, },
], ],
displayName: '隐藏直播推荐', displayName: '隐藏直播推荐',
tags: [ tags: [componentsTags.style, componentsTags.video],
componentsTags.style,
componentsTags.video,
],
description: { description: {
'zh-CN': '隐藏视频页面右侧下方的直播推荐.', 'zh-CN': '隐藏视频页面右侧下方的直播推荐.',
}, },

View File

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

View File

@ -12,9 +12,7 @@ export const component = defineComponentMetadata({
style: () => import('./top-mask.scss'), style: () => import('./top-mask.scss'),
}, },
], ],
tags: [ tags: [componentsTags.style],
componentsTags.style,
],
description: { description: {
'zh-CN': '隐藏视频里鼠标经过时出现在右上角的覆盖层.', 'zh-CN': '隐藏视频里鼠标经过时出现在右上角的覆盖层.',
}, },

View File

@ -4,12 +4,10 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
export default Vue.extend({ export default Vue.extend({})
})
</script> </script>
<style lang="scss"> <style lang="scss">
@import "common"; @import 'common';
html { html {
scroll-behavior: smooth; scroll-behavior: smooth;

View File

@ -2,11 +2,7 @@
<HomeRedesignBase> <HomeRedesignBase>
<div class="fresh-home"> <div class="fresh-home">
<div class="fresh-home-content-layout"> <div class="fresh-home-content-layout">
<FreshLayoutItem <FreshLayoutItem v-for="layout of layouts" :key="layout.name" :item="layout" />
v-for="layout of layouts"
:key="layout.name"
:item="layout"
/>
</div> </div>
</div> </div>
</HomeRedesignBase> </HomeRedesignBase>
@ -29,8 +25,8 @@ export default Vue.extend({
}) })
</script> </script>
<style lang="scss"> <style lang="scss">
@import "common"; @import 'common';
@import "tabs"; @import 'tabs';
.fresh-home { .fresh-home {
padding: 16px 36px; padding: 16px 36px;
@ -65,7 +61,7 @@ export default Vue.extend({
@include h-center(8px); @include h-center(8px);
.be-button { .be-button {
.be-icon { .be-icon {
transition: .3s ease-out; transition: 0.3s ease-out;
} }
.be-iconfont-left-arrow { .be-iconfont-left-arrow {
transform: translateX(-0.5px); transform: translateX(-0.5px);
@ -75,7 +71,7 @@ export default Vue.extend({
} }
.mdi-refresh { .mdi-refresh {
margin: 1px; margin: 1px;
transition-duration: .5s; transition-duration: 0.5s;
} }
&:hover .mdi-refresh { &:hover .mdi-refresh {
transform: rotate(1turn); transform: rotate(1turn);
@ -91,7 +87,7 @@ export default Vue.extend({
} }
.be-icon { .be-icon {
font-weight: normal; font-weight: normal;
transition: .3s ease-out; transition: 0.3s ease-out;
margin-right: 6px; margin-right: 6px;
} }
&.rotate:hover .be-icon { &.rotate:hover .be-icon {

View File

@ -5,7 +5,7 @@
</div> </div>
</template> </template>
<style lang="scss"> <style lang="scss">
@import "common"; @import 'common';
.fresh-home-sub-header { .fresh-home-sub-header {
@include h-center(6px); @include h-center(6px);

View File

@ -24,12 +24,11 @@ export default Vue.extend({
.video-card { .video-card {
border-radius: var(--home-card-radius) !important; border-radius: var(--home-card-radius) !important;
.cover-container { .cover-container {
border-radius: border-radius: calc(var(--home-card-radius) - 1px) calc(var(--home-card-radius) - 1px) 0 0 !important;
calc(var(--home-card-radius) - 1px) calc(var(--home-card-radius) - 1px) 0 0 !important;
} }
&, &,
& * { & * {
transition: .2s ease-out; transition: 0.2s ease-out;
} }
} }
} }

View File

@ -8,13 +8,7 @@
<VLoading v-if="loading" /> <VLoading v-if="loading" />
<VEmpty v-else /> <VEmpty v-else />
</div> </div>
<VideoCardWrapper <VideoCardWrapper v-for="video of videos" v-else ref="cards" :key="video.id" :data="video" />
v-for="video of videos"
v-else
ref="cards"
:key="video.id"
:data="video"
/>
</div> </div>
</div> </div>
</template> </template>
@ -57,13 +51,17 @@ export default Vue.extend({
mounted() { mounted() {
const container = this.$refs.content as HTMLElement const container = this.$refs.content as HTMLElement
let cancel: () => void let cancel: () => void
addComponentListener('freshHome.horizontalWheelScroll', (scroll: boolean) => { addComponentListener(
if (scroll) { 'freshHome.horizontalWheelScroll',
cancel = enableHorizontalScroll(container) (scroll: boolean) => {
} else { if (scroll) {
cancel?.() cancel = enableHorizontalScroll(container)
} } else {
}, true) cancel?.()
}
},
true,
)
}, },
methods: { methods: {
async setupIntersection() { async setupIntersection() {
@ -77,7 +75,9 @@ export default Vue.extend({
const container = this.$refs.content as HTMLElement const container = this.$refs.content as HTMLElement
const style = getComputedStyle(container) const style = getComputedStyle(container)
const containerWidth = container.clientWidth const containerWidth = container.clientWidth
const wrapperWidth = parseFloat(style.getPropertyValue('--card-width')) + parseFloat(style.getPropertyValue('--card-padding')) const wrapperWidth =
parseFloat(style.getPropertyValue('--card-width')) +
parseFloat(style.getPropertyValue('--card-padding'))
const pageWidth = Math.trunc(containerWidth / wrapperWidth) * wrapperWidth const pageWidth = Math.trunc(containerWidth / wrapperWidth) * wrapperWidth
container.scrollBy(offset * pageWidth, 0) container.scrollBy(offset * pageWidth, 0)
}, },
@ -85,8 +85,8 @@ export default Vue.extend({
}) })
</script> </script>
<style lang="scss"> <style lang="scss">
@import "common"; @import 'common';
@import "effects"; @import 'effects';
.fresh-home-video-list { .fresh-home-video-list {
--card-height: var(--home-content-height); --card-height: var(--home-content-height);

View File

@ -8,13 +8,15 @@ export const component = defineComponentMetadata({
name: 'freshHome', name: 'freshHome',
displayName: '清爽首页', displayName: '清爽首页',
urlInclude: homeUrls, urlInclude: homeUrls,
tags: [ tags: [componentsTags.style],
componentsTags.style,
],
entry: () => { entry: () => {
addComponentListener('freshHome.maxWidth', (width: number) => { addComponentListener(
document.documentElement.style.setProperty('--home-max-width-override', `${width}px`) 'freshHome.maxWidth',
}, true) (width: number) => {
document.documentElement.style.setProperty('--home-max-width-override', `${width}px`)
},
true,
)
contentLoaded(async () => { contentLoaded(async () => {
const FreshHome = await import('./FreshHome.vue') const FreshHome = await import('./FreshHome.vue')
const freshHome = mountVueComponent(FreshHome) const freshHome = mountVueComponent(FreshHome)

View File

@ -1,25 +1,17 @@
<template> <template>
<div class="fresh-home-areas"> <div class="fresh-home-areas">
<div class="fresh-home-header"> <div class="fresh-home-header">
<div class="fresh-home-header-title"> <div class="fresh-home-header-title">栏目</div>
栏目
</div>
</div> </div>
<div class="fresh-home-areas-content"> <div class="fresh-home-areas-content">
<a class="fresh-home-areas-content-primary" :href="primary.url" target="_blank"> <a class="fresh-home-areas-content-primary" :href="primary.url" target="_blank">
<div class="fresh-home-areas-content-primary-image"> <div class="fresh-home-areas-content-primary-image"></div>
</div>
<div class="fresh-home-areas-content-primary-title"> <div class="fresh-home-areas-content-primary-title">
{{ primary.title }} {{ primary.title }}
</div> </div>
</a> </a>
<div class="fresh-home-areas-content-other"> <div class="fresh-home-areas-content-other">
<a <a v-for="other of others" :key="other.title" :href="other.url" target="_blank">
v-for="other of others"
:key="other.title"
:href="other.url"
target="_blank"
>
<VButton type="transparent"> <VButton type="transparent">
<VIcon colored :icon="other.icon" :size="22" /> <VIcon colored :icon="other.icon" :size="22" />
{{ other.title }} {{ other.title }}
@ -76,7 +68,7 @@ export default Vue.extend({
}) })
</script> </script>
<style lang="scss"> <style lang="scss">
@import "common"; @import 'common';
.fresh-home-areas { .fresh-home-areas {
@include v-stretch(); @include v-stretch();

View File

@ -1,10 +1,12 @@
<template> <template>
<div class="fresh-home-blackboard" @mouseenter="destroyTimer" @mouseleave="createTimer"> <div class="fresh-home-blackboard" @mouseenter="destroyTimer" @mouseleave="createTimer">
<div class="fresh-home-header"> <div class="fresh-home-header">
<div class="fresh-home-header-title"> <div class="fresh-home-header-title">活动</div>
活动 <a
</div> class="fresh-home-header-icon-button rotate"
<a class="fresh-home-header-icon-button rotate" href="https://www.bilibili.com/blackboard/x/act_list/" target="_blank"> href="https://www.bilibili.com/blackboard/x/act_list/"
target="_blank"
>
<VButton round> <VButton round>
<VIcon icon="mdi-dots-horizontal" :size="20"></VIcon> <VIcon icon="mdi-dots-horizontal" :size="20"></VIcon>
更多 更多
@ -43,22 +45,14 @@
</a> </a>
</div> </div>
<div class="fresh-home-blackboard-jump-dots"> <div class="fresh-home-blackboard-jump-dots">
<label <label v-for="(b, i) of blackboards" :key="i" :for="'blackboard' + i">
v-for="(b, i) of blackboards"
:key="i"
:for="'blackboard' + i"
>
<div class="fresh-home-blackboard-jump-dot"></div> <div class="fresh-home-blackboard-jump-dot"></div>
</label> </label>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { import { VButton, VIcon, DpiImage } from '@/ui'
VButton,
VIcon,
DpiImage,
} from '@/ui'
import { getBlackboards } from './api' import { getBlackboards } from './api'
export default Vue.extend({ export default Vue.extend({
@ -98,18 +92,14 @@ export default Vue.extend({
if (!document.hasFocus() || this.$el.matches(':hover')) { if (!document.hasFocus() || this.$el.matches(':hover')) {
return return
} }
const currentIndex = parseInt( const currentIndex = parseInt(dq(`.${radioClass}:checked`).getAttribute('data-index'))
dq(`.${radioClass}:checked`).getAttribute('data-index'),
)
let targetIndex: number let targetIndex: number
if (currentIndex === this.blackboards.length - 1) { if (currentIndex === this.blackboards.length - 1) {
targetIndex = 0 targetIndex = 0
} else { } else {
targetIndex = currentIndex + 1 targetIndex = currentIndex + 1
} }
(dq( ;(dq(`.${radioClass}[data-index='${targetIndex}']`) as HTMLInputElement).checked = true
`.${radioClass}[data-index='${targetIndex}']`,
) as HTMLInputElement).checked = true
}, 5000) }, 5000)
}, },
destroyTimer() { destroyTimer() {
@ -123,7 +113,7 @@ export default Vue.extend({
}) })
</script> </script>
<style lang="scss"> <style lang="scss">
@import "common"; @import 'common';
$max-card-count: 16; $max-card-count: 16;
.fresh-home { .fresh-home {
@ -131,7 +121,7 @@ $max-card-count: 16;
position: relative; position: relative;
&, &,
& * { & * {
transition: .2s ease-out; transition: 0.2s ease-out;
} }
&-cards { &-cards {
display: flex; display: flex;
@ -194,8 +184,8 @@ $max-card-count: 16;
width: 40px; width: 40px;
} }
&:checked:nth-of-type(#{$i}) ~ .fresh-home-blackboard-cards .fresh-home-blackboard-card { &:checked:nth-of-type(#{$i}) ~ .fresh-home-blackboard-cards .fresh-home-blackboard-card {
transform: transform: translateX(calc(-1 * #{$i - 1} * var(--blackboard-width-without-border)))
translateX(calc(-1 * #{$i - 1} * var(--blackboard-width-without-border))) scale(0.9); scale(0.9);
&:nth-of-type(#{$i}) { &:nth-of-type(#{$i}) {
transform: translateX(calc(-1 * #{$i - 1} * var(--blackboard-width-without-border))); transform: translateX(calc(-1 * #{$i - 1} * var(--blackboard-width-without-border)));
img { img {

View File

@ -14,11 +14,14 @@ export const getBlackboards = async (): Promise<Blackboard[]> => {
throw new Error(`获取活动卡片失败: ${message}`) throw new Error(`获取活动卡片失败: ${message}`)
} }
const list: any[] = data[locId] const list: any[] = data[locId]
return list.map(it => ({ return list.map(
url: it.url, it =>
title: it.name, ({
// isAd: it.is_ad_loc, url: it.url,
isAd: it.res_id !== locId, title: it.name,
imageUrl: it.pic, // isAd: it.is_ad_loc,
} as Blackboard)) isAd: it.res_id !== locId,
imageUrl: it.pic,
} as Blackboard),
)
} }

View File

@ -1,9 +1,7 @@
<template> <template>
<div class="fresh-home-categories"> <div class="fresh-home-categories">
<div class="fresh-home-header"> <div class="fresh-home-header">
<div class="fresh-home-header-title"> <div class="fresh-home-header-title">分区</div>
分区
</div>
<div class="fresh-home-header-center-area"> <div class="fresh-home-header-center-area">
<div class="fresh-home-header-tabs"> <div class="fresh-home-header-tabs">
<div ref="tabs" class="default-tabs"> <div ref="tabs" class="default-tabs">
@ -105,7 +103,7 @@ export default Vue.extend({
}) })
</script> </script>
<style lang="scss"> <style lang="scss">
@import "common"; @import 'common';
.fresh-home-categories { .fresh-home-categories {
@include v-stretch(); @include v-stretch();

View File

@ -2,13 +2,9 @@
<div class="fresh-home-categories-bangumi"> <div class="fresh-home-categories-bangumi">
<div class="fresh-home-categories-bangumi-timeline"> <div class="fresh-home-categories-bangumi-timeline">
<div class="fresh-home-categories-bangumi-timeline-header"> <div class="fresh-home-categories-bangumi-timeline-header">
<SubHeader> <SubHeader> 时间表 </SubHeader>
时间表
</SubHeader>
</div> </div>
<BangumiTimeline <BangumiTimeline :api="timelineApi" />
:api="timelineApi"
/>
</div> </div>
<div class="fresh-home-categories-bangumi-rank-list"> <div class="fresh-home-categories-bangumi-rank-list">
<a <a
@ -16,15 +12,9 @@
:href="rankingsLink" :href="rankingsLink"
target="_blank" target="_blank"
> >
<SubHeader> <SubHeader> 排行榜 </SubHeader>
排行榜
</SubHeader>
</a> </a>
<RankList <RankList bangumi-mode :parse-json="parseJson" :api="rankingsApi" />
bangumi-mode
:parse-json="parseJson"
:api="rankingsApi"
/>
</div> </div>
</div> </div>
</template> </template>
@ -73,22 +63,20 @@ export default Vue.extend({
parseJson(json: any) { parseJson(json: any) {
const items = (json.data?.list ?? []) as any[] const items = (json.data?.list ?? []) as any[]
const cards = items const cards = items
.map( .map((item): RankListCard => {
(item): RankListCard => { const upName = item.new_ep?.index_show ?? item.title
const upName = item.new_ep?.index_show ?? item.title return {
return { id: item.season_id,
id: item.season_id, title: item.title,
title: item.title, playCount: item.stat.view,
playCount: item.stat.view, points: item.stat.follow,
points: item.stat.follow, upHref: item.url,
upHref: item.url, upName,
upName, dynamic: upName,
dynamic: upName, coverUrl: item.new_ep?.cover ?? item.ss_horizontal_cover,
coverUrl: item.new_ep?.cover ?? item.ss_horizontal_cover, videoHref: item.url,
videoHref: item.url, }
} })
},
)
.slice(0, 10) .slice(0, 10)
return applyContentFilter(cards) return applyContentFilter(cards)
}, },
@ -96,8 +84,8 @@ export default Vue.extend({
}) })
</script> </script>
<style lang="scss"> <style lang="scss">
@import "common"; @import 'common';
@import "effects"; @import 'effects';
.fresh-home-categories-bangumi { .fresh-home-categories-bangumi {
@include h-stretch(var(--fresh-home-categories-column-gap)); @include h-stretch(var(--fresh-home-categories-column-gap));

View File

@ -25,19 +25,13 @@
<div class="fresh-home-categories-bangumi-timeline-date-text"> <div class="fresh-home-categories-bangumi-timeline-date-text">
{{ dayOfWeekText(item) }} {{ dayOfWeekText(item) }}
</div> </div>
<div <div v-if="index === todayIndex" class="fresh-home-categories-bangumi-timeline-date-today">
v-if="index === todayIndex"
class="fresh-home-categories-bangumi-timeline-date-today"
>
TODAY TODAY
</div> </div>
</div> </div>
<div <div
ref="seasonsList" ref="seasonsList"
class=" class="fresh-home-categories-bangumi-timeline-seasons-container scroll-top scroll-bottom"
fresh-home-categories-bangumi-timeline-seasons-container
scroll-top scroll-bottom
"
:class="{ 'not-empty': item.episodes.length > 0 }" :class="{ 'not-empty': item.episodes.length > 0 }"
> >
<div <div
@ -88,19 +82,13 @@
follow: season.follow, follow: season.follow,
}" }"
> >
<div <div class="fresh-home-categories-bangumi-timeline-season-time-icon">
class="fresh-home-categories-bangumi-timeline-season-time-icon"
>
<VIcon <VIcon
:icon=" :icon="season.follow ? 'mdi-heart-outline' : 'mdi-progress-clock'"
season.follow ? 'mdi-heart-outline' : 'mdi-progress-clock'
"
:size="14" :size="14"
/> />
</div> </div>
<div <div class="fresh-home-categories-bangumi-timeline-season-time-text">
class="fresh-home-categories-bangumi-timeline-season-time-text"
>
{{ season.pub_time }} {{ season.pub_time }}
</div> </div>
</div> </div>
@ -111,12 +99,7 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { import { DpiImage, VIcon, VEmpty, VLoading } from '@/ui'
DpiImage,
VIcon,
VEmpty,
VLoading,
} from '@/ui'
import { addComponentListener } from '@/core/settings' import { addComponentListener } from '@/core/settings'
import { enableHorizontalScroll } from '@/core/horizontal-scroll' import { enableHorizontalScroll } from '@/core/horizontal-scroll'
import { cssVariableMixin, requestMixin } from '../../../../mixin' import { cssVariableMixin, requestMixin } from '../../../../mixin'
@ -157,15 +140,9 @@ const timelineCssVars = (() => {
const seasonTodayWidth = 250 const seasonTodayWidth = 250
const timelineItemHeight = 66 const timelineItemHeight = 66
const timelineTodayHeight = 96 const timelineTodayHeight = 96
const timelineViewportItemsHeight = ( const timelineViewportItemsHeight = 6 * timelineItemHeight + timelineTodayHeight
6 * timelineItemHeight + timelineTodayHeight const timelineItemGap = (rankListHeight - timelineViewportItemsHeight) / 6
) const timelineViewportHeight = 6 * timelineItemGap + timelineViewportItemsHeight
const timelineItemGap = (
rankListHeight - timelineViewportItemsHeight
) / 6
const timelineViewportHeight = (
6 * timelineItemGap + timelineViewportItemsHeight
)
return { return {
seasonItemWidth, seasonItemWidth,
seasonTodayWidth, seasonTodayWidth,
@ -243,21 +220,22 @@ export default Vue.extend({
await this.$nextTick() await this.$nextTick()
const list: HTMLElement[] = this.$refs.seasonsList const list: HTMLElement[] = this.$refs.seasonsList
let cancelAll: () => void let cancelAll: () => void
addComponentListener('freshHome.horizontalWheelScroll', (scroll: boolean) => { addComponentListener(
if (scroll) { 'freshHome.horizontalWheelScroll',
const cancel = list (scroll: boolean) => {
.flatMap(it => [...it.children]) if (scroll) {
.map(it => enableHorizontalScroll(it as HTMLElement)) const cancel = list
cancelAll = () => cancel.forEach(fn => fn()) .flatMap(it => [...it.children])
} else { .map(it => enableHorizontalScroll(it as HTMLElement))
cancelAll?.() cancelAll = () => cancel.forEach(fn => fn())
} } else {
}, true) cancelAll?.()
const root: HTMLElement = this.$el }
root.scrollTop = ( },
5 * timelineCssVars.timelineItemHeight true,
+ 5 * timelineCssVars.timelineItemGap
) )
const root: HTMLElement = this.$el
root.scrollTop = 5 * timelineCssVars.timelineItemHeight + 5 * timelineCssVars.timelineItemGap
const classPrefix = '.fresh-home-categories-bangumi-timeline' const classPrefix = '.fresh-home-categories-bangumi-timeline'
list.forEach(seasons => { list.forEach(seasons => {
@ -279,7 +257,10 @@ export default Vue.extend({
this.scrolled = true this.scrolled = true
return return
} }
const lastPublishedElement = dq(todaySeasons, `[data-season="${lastPublishedItem.season_id}"]`) as HTMLElement const lastPublishedElement = dq(
todaySeasons,
`[data-season="${lastPublishedItem.season_id}"]`,
) as HTMLElement
if (!lastPublishedElement) { if (!lastPublishedElement) {
return return
} }
@ -299,23 +280,14 @@ export default Vue.extend({
return season.pub_ts * 1000 <= this.now return season.pub_ts * 1000 <= this.now
}, },
dayOfWeekText(item: TimelineDay) { dayOfWeekText(item: TimelineDay) {
return `${[ return `${['日', '一', '二', '三', '四', '五', '六', '日'][item.day_of_week]}`
'日',
'一',
'二',
'三',
'四',
'五',
'六',
'日',
][item.day_of_week]}`
}, },
}, },
}) })
</script> </script>
<style lang="scss"> <style lang="scss">
@import "common"; @import 'common';
@import "effects"; @import 'effects';
.fresh-home-categories-bangumi { .fresh-home-categories-bangumi {
&-timeline { &-timeline {
@ -377,16 +349,13 @@ export default Vue.extend({
$icon-height: 48; $icon-height: 48;
--scale-factor-x: calc(#{$icon-width} / 38); --scale-factor-x: calc(#{$icon-width} / 38);
--scale-factor-y: calc(#{$icon-height} / 36); --scale-factor-y: calc(#{$icon-height} / 36);
background-size: calc(247px * var(--scale-factor-x)) background-size: calc(247px * var(--scale-factor-x)) calc(663px * var(--scale-factor-y));
calc(663px * var(--scale-factor-y));
width: #{$icon-width}px; width: #{$icon-width}px;
height: #{$icon-height}px; height: #{$icon-height}px;
background-position-x: calc(-146px * var(--scale-factor-x)); background-position-x: calc(-146px * var(--scale-factor-x));
@for $day from 1 through 7 { @for $day from 1 through 7 {
&.day-of-week-#{$day} { &.day-of-week-#{$day} {
background-position-y: calc( background-position-y: calc(#{-36 - 72 * ($day - 1)}px * var(--scale-factor-y));
#{-36 - 72 * ($day - 1)}px * var(--scale-factor-y)
);
} }
} }
body.dark & { body.dark & {
@ -485,8 +454,7 @@ export default Vue.extend({
&.today { &.today {
border-radius: 12px; border-radius: 12px;
&.follow.published { &.follow.published {
box-shadow: 0 0 0 2px var(--theme-color), box-shadow: 0 0 0 2px var(--theme-color), 0 0 0 5px var(--theme-color-20);
0 0 0 5px var(--theme-color-20);
} }
} }
} }

View File

@ -2,28 +2,19 @@
<div class="fresh-home-categories-default"> <div class="fresh-home-categories-default">
<div class="fresh-home-categories-default-video-column"> <div class="fresh-home-categories-default-video-column">
<div class="fresh-home-categories-default-video-column-item"> <div class="fresh-home-categories-default-video-column-item">
<SubHeader> <SubHeader> 有新动态 </SubHeader>
有新动态
</SubHeader>
<VideoSlides :api="activeVideosApi" /> <VideoSlides :api="activeVideosApi" />
</div> </div>
<div class="fresh-home-categories-default-video-column-item"> <div class="fresh-home-categories-default-video-column-item">
<SubHeader> <SubHeader> 最新发布 </SubHeader>
最新发布
</SubHeader>
<VideoSlides :api="newVideosApi" /> <VideoSlides :api="newVideosApi" />
</div> </div>
</div> </div>
<div class="fresh-home-categories-default-rank-list"> <div class="fresh-home-categories-default-rank-list">
<a :href="rankingsLink" target="_blank"> <a :href="rankingsLink" target="_blank">
<SubHeader> <SubHeader> 排行榜 </SubHeader>
排行榜
</SubHeader>
</a> </a>
<RankList <RankList :parse-json="parseJson" :api="rankingsApi" />
:parse-json="parseJson"
:api="rankingsApi"
/>
</div> </div>
</div> </div>
</template> </template>
@ -87,7 +78,7 @@ export default Vue.extend({
}) })
</script> </script>
<style lang="scss"> <style lang="scss">
@import "common"; @import 'common';
.fresh-home-categories-default { .fresh-home-categories-default {
@include h-stretch(var(--fresh-home-categories-column-gap)); @include h-stretch(var(--fresh-home-categories-column-gap));

View File

@ -4,11 +4,7 @@
<VLoading v-if="loading" /> <VLoading v-if="loading" />
<div v-if="(error || items.length === 0) && !loading" class="fresh-home-rank-list-empty"> <div v-if="(error || items.length === 0) && !loading" class="fresh-home-rank-list-empty">
<VEmpty /> <VEmpty />
<VButton <VButton class="fresh-home-rank-list-refresh-button" round @click="reload">
class="fresh-home-rank-list-refresh-button"
round
@click="reload"
>
<VIcon icon="mdi-refresh" /> <VIcon icon="mdi-refresh" />
刷新 刷新
</VButton> </VButton>
@ -25,11 +21,7 @@
> >
{{ firstItem.title }} {{ firstItem.title }}
</a> </a>
<a <a class="fresh-home-rank-list-cover" target="_blank" :href="firstItem.videoHref">
class="fresh-home-rank-list-cover"
target="_blank"
:href="firstItem.videoHref"
>
<DpiImage <DpiImage
:src="firstItem.coverUrl" :src="firstItem.coverUrl"
:size="{ width: ui.firstCoverWidth, height: ui.firstCoverHeight }" :size="{ width: ui.firstCoverWidth, height: ui.firstCoverHeight }"
@ -51,15 +43,8 @@
<div class="fresh-home-rank-list-laser" data-number="1"></div> <div class="fresh-home-rank-list-laser" data-number="1"></div>
</div> </div>
<div v-if="secondItem" class="fresh-home-rank-list-second-item animation"> <div v-if="secondItem" class="fresh-home-rank-list-second-item animation">
<a <a class="fresh-home-rank-list-rank-item" target="_blank" :href="secondItem.videoHref">
class="fresh-home-rank-list-rank-item" <div class="fresh-home-rank-list-rank-item-title" :title="secondItem.title">
target="_blank"
:href="secondItem.videoHref"
>
<div
class="fresh-home-rank-list-rank-item-title"
:title="secondItem.title"
>
{{ secondItem.title }} {{ secondItem.title }}
</div> </div>
<UpInfo <UpInfo
@ -78,11 +63,7 @@
{{ secondItem.playCount | formatCount }} {{ secondItem.playCount | formatCount }}
</div> </div>
</a> </a>
<a <a class="fresh-home-rank-list-cover" target="_blank" :href="secondItem.videoHref">
class="fresh-home-rank-list-cover"
target="_blank"
:href="secondItem.videoHref"
>
<DpiImage <DpiImage
:src="secondItem.coverUrl" :src="secondItem.coverUrl"
:size="{ width: ui.secondCoverWidth, height: ui.secondCoverHeight }" :size="{ width: ui.secondCoverWidth, height: ui.secondCoverHeight }"
@ -91,15 +72,8 @@
<div class="fresh-home-rank-list-laser" data-number="2"></div> <div class="fresh-home-rank-list-laser" data-number="2"></div>
</div> </div>
<div v-if="thirdItem" class="fresh-home-rank-list-third-item animation"> <div v-if="thirdItem" class="fresh-home-rank-list-third-item animation">
<a <a class="fresh-home-rank-list-rank-item" target="_blank" :href="thirdItem.videoHref">
class="fresh-home-rank-list-rank-item" <div class="fresh-home-rank-list-rank-item-title" :title="thirdItem.title">
target="_blank"
:href="thirdItem.videoHref"
>
<div
class="fresh-home-rank-list-rank-item-title"
:title="thirdItem.title"
>
{{ thirdItem.title }} {{ thirdItem.title }}
</div> </div>
<UpInfo <UpInfo
@ -118,11 +92,7 @@
{{ secondItem.playCount | formatCount }} {{ secondItem.playCount | formatCount }}
</div> </div>
</a> </a>
<a <a class="fresh-home-rank-list-cover" target="_blank" :href="thirdItem.videoHref">
class="fresh-home-rank-list-cover"
target="_blank"
:href="thirdItem.videoHref"
>
<DpiImage <DpiImage
:src="thirdItem.coverUrl" :src="thirdItem.coverUrl"
:size="{ width: ui.thirdCoverWidth, height: ui.thirdCoverHeight }" :size="{ width: ui.thirdCoverWidth, height: ui.thirdCoverHeight }"
@ -136,13 +106,7 @@
<script lang="ts"> <script lang="ts">
import UpInfo from '@/components/feeds/UpInfo.vue' import UpInfo from '@/components/feeds/UpInfo.vue'
import { formatCount } from '@/core/utils/formatters' import { formatCount } from '@/core/utils/formatters'
import { import { DpiImage, VIcon, VLoading, VEmpty, VButton } from '@/ui'
DpiImage,
VIcon,
VLoading,
VEmpty,
VButton,
} from '@/ui'
import { requestMixin, cssVariableMixin } from '../../../../mixin' import { requestMixin, cssVariableMixin } from '../../../../mixin'
import { rankListCssVars } from './rank-list' import { rankListCssVars } from './rank-list'
@ -158,10 +122,7 @@ export default Vue.extend({
filters: { filters: {
formatCount, formatCount,
}, },
mixins: [ mixins: [requestMixin(), cssVariableMixin(rankListCssVars)],
requestMixin(),
cssVariableMixin(rankListCssVars),
],
props: { props: {
parseJson: { parseJson: {
type: Function, type: Function,
@ -201,7 +162,7 @@ export default Vue.extend({
}) })
</script> </script>
<style lang="scss"> <style lang="scss">
@import "common"; @import 'common';
.fresh-home-rank-list { .fresh-home-rank-list {
position: relative; position: relative;
@ -225,7 +186,7 @@ export default Vue.extend({
& &-stats { & &-stats {
@include h-center(12px); @include h-center(12px);
font-size: 12px; font-size: 12px;
opacity: .5; opacity: 0.5;
margin: 0 10px; margin: 0 10px;
.be-icon { .be-icon {
margin-right: -8px; margin-right: -8px;
@ -243,7 +204,7 @@ export default Vue.extend({
&-title { &-title {
@include semi-bold(); @include semi-bold();
transition: color .2s ease-out; transition: color 0.2s ease-out;
line-height: var(--rank-item-title-height); line-height: var(--rank-item-title-height);
box-sizing: content-box; box-sizing: content-box;
&:hover { &:hover {
@ -266,7 +227,7 @@ export default Vue.extend({
} }
} }
@include v-stretch(); @include v-stretch();
animation: .4s var(--animation-timing) first-animation paused both; animation: 0.4s var(--animation-timing) first-animation paused both;
position: absolute; position: absolute;
top: var(--padding); top: var(--padding);
left: var(--padding); left: var(--padding);
@ -317,7 +278,7 @@ export default Vue.extend({
} }
} }
@include v-stretch(); @include v-stretch();
animation: .4s var(--animation-timing) second-animation paused both; animation: 0.4s var(--animation-timing) second-animation paused both;
position: absolute; position: absolute;
top: var(--offset-second); top: var(--offset-second);
bottom: var(--padding); bottom: var(--padding);
@ -350,7 +311,7 @@ export default Vue.extend({
} }
} }
@include v-stretch(); @include v-stretch();
animation: .4s var(--animation-timing) third-animation paused both; animation: 0.4s var(--animation-timing) third-animation paused both;
position: absolute; position: absolute;
top: var(--offset-third); top: var(--offset-third);
right: var(--padding); right: var(--padding);
@ -379,7 +340,7 @@ export default Vue.extend({
box-shadow: none; box-shadow: none;
overflow: hidden; overflow: hidden;
transform-origin: bottom; transform-origin: bottom;
transition: .2s ease-out; transition: 0.2s ease-out;
position: relative; position: relative;
img { img {
@ -398,11 +359,7 @@ export default Vue.extend({
flex: 1; flex: 1;
width: 4px; width: 4px;
border-radius: 2px; border-radius: 2px;
background-image: linear-gradient( background-image: linear-gradient(to bottom, var(--theme-color) 0%, var(--theme-color-10) 100%);
to bottom,
var(--theme-color) 0%,
var(--theme-color-10) 100%
);
&::after { &::after {
content: attr(data-number); content: attr(data-number);
@include absolute-center(); @include absolute-center();
@ -436,7 +393,7 @@ export default Vue.extend({
} }
.be-icon { .be-icon {
margin-right: 6px; margin-right: 6px;
transition: .5s ease-out; transition: 0.5s ease-out;
} }
} }

View File

@ -18,19 +18,12 @@
</div> </div>
<div class="cover-placeholder-vertical"></div> <div class="cover-placeholder-vertical"></div>
<div v-if="!loaded" class="fresh-home-video-slides-empty"> <div v-if="!loaded" class="fresh-home-video-slides-empty">
<div <div class="empty-placeholder fresh-home-video-slides-main-title" v-text="' '"></div>
class="empty-placeholder fresh-home-video-slides-main-title"
v-text="' '"
></div>
<div class="empty-indicator"> <div class="empty-indicator">
<VLoading v-if="loading" /> <VLoading v-if="loading" />
<div v-if="error" class="empty-indicator-error"> <div v-if="error" class="empty-indicator-error">
<VEmpty /> <VEmpty />
<VButton <VButton class="fresh-home-video-slides-refresh-button" round @click="reload">
class="fresh-home-video-slides-refresh-button"
round
@click="reload"
>
<VIcon icon="mdi-refresh" /> <VIcon icon="mdi-refresh" />
刷新 刷新
</VButton> </VButton>
@ -42,11 +35,7 @@
<div class="fresh-home-video-slides-row"> <div class="fresh-home-video-slides-row">
<div class="cover-placeholder-horizontal"></div> <div class="cover-placeholder-horizontal"></div>
<div class="fresh-home-video-slides-main-actions"> <div class="fresh-home-video-slides-main-actions">
<a <a class="fresh-home-video-slides-play-button" :href="currentUrl" target="_blank">
class="fresh-home-video-slides-play-button"
:href="currentUrl"
target="_blank"
>
<VButton type="primary" round> <VButton type="primary" round>
<VIcon icon="mdi-play" /> <VIcon icon="mdi-play" />
播放 播放
@ -96,12 +85,7 @@
<div class="description-text" v-text="currentItem.description"></div> <div class="description-text" v-text="currentItem.description"></div>
</div> </div>
<div class="fresh-home-video-slides-actions"> <div class="fresh-home-video-slides-actions">
<VButton <VButton class="fresh-home-video-slides-refresh-button" title="刷新" icon @click="reload">
class="fresh-home-video-slides-refresh-button"
title="刷新"
icon
@click="reload"
>
<VIcon icon="mdi-refresh" /> <VIcon icon="mdi-refresh" />
</VButton> </VButton>
<VButton <VButton
@ -112,12 +96,7 @@
> >
<VIcon icon="mdi-arrow-left" /> <VIcon icon="mdi-arrow-left" />
</VButton> </VButton>
<VButton <VButton class="fresh-home-video-slides-next-button" title="下一个" icon @click="nextCard">
class="fresh-home-video-slides-next-button"
title="下一个"
icon
@click="nextCard"
>
<VIcon icon="mdi-arrow-right" :size="36" /> <VIcon icon="mdi-arrow-right" :size="36" />
</VButton> </VButton>
</div> </div>
@ -140,15 +119,18 @@ export default Vue.extend({
VLoading, VLoading,
VEmpty, VEmpty,
}, },
mixins: [requestMixin(), cssVariableMixin({ mixins: [
mainCoverHeight: 185, requestMixin(),
mainCoverWidth: 287, cssVariableMixin({
otherCoverHeight: 100, mainCoverHeight: 185,
otherCoverWidth: 154, mainCoverWidth: 287,
mainPaddingX: 18, otherCoverHeight: 100,
mainPaddingY: 20, otherCoverWidth: 154,
coverPadding: 16, mainPaddingX: 18,
})], mainPaddingY: 20,
coverPadding: 16,
}),
],
data() { data() {
return { return {
watchlaterList, watchlaterList,
@ -224,8 +206,8 @@ export default Vue.extend({
}) })
</script> </script>
<style lang="scss"> <style lang="scss">
@import "common"; @import 'common';
@import "effects"; @import 'effects';
.fresh-home-video-slides { .fresh-home-video-slides {
@include card(12px); @include card(12px);
@ -339,9 +321,7 @@ export default Vue.extend({
justify-content: space-between; justify-content: space-between;
position: relative; position: relative;
padding-top: var(--main-info-padding); padding-top: var(--main-info-padding);
width: calc( width: calc(var(--main-cover-width) + var(--cover-padding) + var(--other-cover-width));
var(--main-cover-width) + var(--cover-padding) + var(--other-cover-width)
);
} }
& &-main-title { & &-main-title {
font-size: 16px; font-size: 16px;
@ -403,9 +383,7 @@ export default Vue.extend({
} }
&:nth-child(1) { &:nth-child(1) {
opacity: 0; opacity: 0;
transform: translateX( transform: translateX(calc(0px - var(--other-cover-width) - var(--cover-padding)));
calc(0px - var(--other-cover-width) - var(--cover-padding))
);
} }
&:nth-child(2) { &:nth-child(2) {
width: var(--main-cover-width); width: var(--main-cover-width);

View File

@ -1,7 +1,4 @@
const bangumiNames = [ const bangumiNames = ['番剧', '国创']
'番剧',
'国创',
]
export const getContent = (tabName: string) => { export const getContent = (tabName: string) => {
console.log('getContent', tabName) console.log('getContent', tabName)
if (bangumiNames.includes(tabName)) { if (bangumiNames.includes(tabName)) {

View File

@ -1,9 +1,7 @@
<template> <template>
<div class="fresh-home-feeds"> <div class="fresh-home-feeds">
<div class="fresh-home-header"> <div class="fresh-home-header">
<div class="fresh-home-header-title"> <div class="fresh-home-header-title">动态</div>
动态
</div>
<div class="fresh-home-header-center-area"> <div class="fresh-home-header-center-area">
<div class="fresh-home-header-tabs"> <div class="fresh-home-header-tabs">
<div class="default-tabs"> <div class="default-tabs">
@ -22,11 +20,7 @@
</div> </div>
</div> </div>
<div class="fresh-home-header-pagination"> <div class="fresh-home-header-pagination">
<a <a href="https://www.bilibili.com/video/online.html" target="_blank" title="在线列表">
href="https://www.bilibili.com/video/online.html"
target="_blank"
title="在线列表"
>
<VButton icon> <VButton icon>
<VIcon icon="mdi-account-group-outline" :size="19" /> <VIcon icon="mdi-account-group-outline" :size="19" />
</VButton> </VButton>
@ -53,11 +47,7 @@
</div> </div>
</div> </div>
<div class="fresh-home-feeds-content"> <div class="fresh-home-feeds-content">
<VideoList <VideoList ref="videoList" :videos="videos" :loading="loading" />
ref="videoList"
:videos="videos"
:loading="loading"
/>
</div> </div>
</div> </div>
</template> </template>
@ -113,13 +103,15 @@ export default Vue.extend({
async reload() { async reload() {
this.loading = true this.loading = true
this.videos = [] this.videos = []
this.videos = await this.selectedTab.api().finally(() => { this.loading = false }) this.videos = await this.selectedTab.api().finally(() => {
this.loading = false
})
}, },
}, },
}) })
</script> </script>
<style lang="scss"> <style lang="scss">
@import "common"; @import 'common';
.fresh-home-feeds { .fresh-home-feeds {
@include v-stretch(); @include v-stretch();

View File

@ -5,11 +5,5 @@ import { categories } from './categories/categories'
import { feeds } from './feeds/feeds' import { feeds } from './feeds/feeds'
import { trending } from './trending/trending' import { trending } from './trending/trending'
const builtInLayouts = [ const builtInLayouts = [blackboard, trending, feeds, areas, categories]
blackboard,
trending,
feeds,
areas,
categories,
]
export const [layouts] = registerAndGetData('homeRedesign.fresh.layouts', [...builtInLayouts]) export const [layouts] = registerAndGetData('homeRedesign.fresh.layouts', [...builtInLayouts])

View File

@ -17,19 +17,12 @@
</div> </div>
</div> </div>
<div class="fresh-home-trending-content"> <div class="fresh-home-trending-content">
<VideoList <VideoList ref="videoList" :videos="videos" :loading="loading" />
ref="videoList"
:videos="videos"
:loading="loading"
/>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { import { VButton, VIcon } from '@/ui'
VButton,
VIcon,
} from '@/ui'
import VideoList from '../../VideoList.vue' import VideoList from '../../VideoList.vue'
import { freshHomeOptions } from '../../types' import { freshHomeOptions } from '../../types'
import { getTrendingVideos } from '../../../trending' import { getTrendingVideos } from '../../../trending'
@ -61,14 +54,15 @@ export default Vue.extend({
async reload() { async reload() {
this.loading = true this.loading = true
this.videos = [] this.videos = []
this.videos = await getTrendingVideos(freshHomeOptions.personalized) this.videos = await getTrendingVideos(freshHomeOptions.personalized).finally(() => {
.finally(() => { this.loading = false }) this.loading = false
})
}, },
}, },
}) })
</script> </script>
<style lang="scss"> <style lang="scss">
@import "common"; @import 'common';
.fresh-home-trending { .fresh-home-trending {
@include v-stretch(); @include v-stretch();

View File

@ -17,10 +17,8 @@ export const setupScrollMask = (config: ScrollMaskConfig) => {
} }
const observerConfig: IntersectionObserverInit = { threshold: [1], root: container } const observerConfig: IntersectionObserverInit = { threshold: [1], root: container }
const [firstItem] = items const [firstItem] = items
const [firstObserver] = intersectionObserve( const [firstObserver] = intersectionObserve([firstItem], observerConfig, records =>
[firstItem], records.forEach(r => {
observerConfig,
records => records.forEach(r => {
const isScrollTop = r.isIntersecting && r.intersectionRatio === 1 const isScrollTop = r.isIntersecting && r.intersectionRatio === 1
container.classList.toggle('scroll-top', isScrollTop) container.classList.toggle('scroll-top', isScrollTop)
}), }),
@ -30,10 +28,8 @@ export const setupScrollMask = (config: ScrollMaskConfig) => {
newObservers.push(firstObserver) newObservers.push(firstObserver)
if (items.length > 1) { if (items.length > 1) {
const lastItem = items[items.length - 1] const lastItem = items[items.length - 1]
const [lastObserver] = intersectionObserve( const [lastObserver] = intersectionObserve([lastItem], observerConfig, records =>
[lastItem], records.forEach(r => {
observerConfig,
records => records.forEach(r => {
const isScrollBottom = r.isIntersecting && r.intersectionRatio === 1 const isScrollBottom = r.isIntersecting && r.intersectionRatio === 1
container.classList.toggle('scroll-bottom', isScrollBottom) container.classList.toggle('scroll-bottom', isScrollBottom)
}), }),

View File

@ -39,13 +39,17 @@ export default Vue.extend({
}, },
mounted() { mounted() {
const columnCountKey = '--minimal-home-column-count-override' const columnCountKey = '--minimal-home-column-count-override'
addComponentListener('minimalHome.columnCount', (count: number) => { addComponentListener(
if (count > 0) { 'minimalHome.columnCount',
(this.$el as HTMLElement).style.setProperty(columnCountKey, count.toString()) (count: number) => {
} else { if (count > 0) {
(this.$el as HTMLElement).style.removeProperty(columnCountKey) ;(this.$el as HTMLElement).style.setProperty(columnCountKey, count.toString())
} } else {
}, true) ;(this.$el as HTMLElement).style.removeProperty(columnCountKey)
}
},
true,
)
}, },
}) })
</script> </script>
@ -76,17 +80,13 @@ export default Vue.extend({
&-tabs { &-tabs {
flex-grow: 1; flex-grow: 1;
min-width: calc( min-width: calc(
var(--card-width) * var(--minimal-home-card-column) + var(--card-width) * var(--minimal-home-card-column) + var(--minimal-home-grid-gap) *
var(--minimal-home-grid-gap) * (var(--minimal-home-card-column) - 1) + 2 * (var(--minimal-home-card-column) - 1) + 2 * var(--minimal-home-grid-padding)
var(--minimal-home-grid-padding)
); );
.minimal-home-tab { .minimal-home-tab {
&-cards { &-cards {
display: grid; display: grid;
grid-template-columns: repeat( grid-template-columns: repeat(var(--minimal-home-card-column), var(--card-width));
var(--minimal-home-card-column),
var(--card-width)
);
gap: var(--minimal-home-grid-gap); gap: var(--minimal-home-grid-gap);
padding: 0 var(--minimal-home-grid-padding); padding: 0 var(--minimal-home-grid-padding);
margin-bottom: 16px; margin-bottom: 16px;

View File

@ -5,17 +5,14 @@
icon icon
class="minimal-home-operations-refresh" class="minimal-home-operations-refresh"
title="刷新" title="刷新"
@click="backToTop(); $emit('refresh')" @click="
backToTop()
$emit('refresh')
"
> >
<VIcon icon="mdi-refresh" :size="size" /> <VIcon icon="mdi-refresh" :size="size" />
</VButton> </VButton>
<VButton <VButton round icon class="minimal-home-operations-top" title="返回顶部" @click="backToTop">
round
icon
class="minimal-home-operations-top"
title="返回顶部"
@click="backToTop"
>
<VIcon icon="mdi-arrow-up" :size="size" /> <VIcon icon="mdi-arrow-up" :size="size" />
</VButton> </VButton>
</div> </div>

View File

@ -25,13 +25,15 @@ export const component = defineComponentMetadata({
name: 'minimalHome', name: 'minimalHome',
displayName: '极简首页', displayName: '极简首页',
urlInclude: homeUrls, urlInclude: homeUrls,
tags: [ tags: [componentsTags.style],
componentsTags.style,
],
entry: () => { entry: () => {
addComponentListener('minimalHome.columnCount', (count: number) => { addComponentListener(
document.documentElement.style.setProperty('--home-column-count-override', count.toString()) 'minimalHome.columnCount',
}, true) (count: number) => {
document.documentElement.style.setProperty('--home-column-count-override', count.toString())
},
true,
)
contentLoaded(async () => { contentLoaded(async () => {
const MinimalHome = await import('./MinimalHome.vue') const MinimalHome = await import('./MinimalHome.vue')
const minimalHome = mountVueComponent(MinimalHome) const minimalHome = mountVueComponent(MinimalHome)

View File

@ -2,4 +2,5 @@ import { OptionsOfMetadata } from '@/components/define'
import { getComponentSettings } from '@/core/settings' import { getComponentSettings } from '@/core/settings'
import type { minimalHomeOptionsMetadata } from '.' import type { minimalHomeOptionsMetadata } from '.'
export const minimalHomeOptions = getComponentSettings<OptionsOfMetadata<typeof minimalHomeOptionsMetadata>>('minimalHome').options export const minimalHomeOptions =
getComponentSettings<OptionsOfMetadata<typeof minimalHomeOptionsMetadata>>('minimalHome').options

View File

@ -14,10 +14,7 @@ import { VideoCard } from '@/components/feeds/video-card'
import VideoCardComponent from '@/components/feeds/VideoCard.vue' import VideoCardComponent from '@/components/feeds/VideoCard.vue'
import { logError } from '@/core/utils/log' import { logError } from '@/core/utils/log'
import { ascendingStringSort } from '@/core/utils/sort' import { ascendingStringSort } from '@/core/utils/sort'
import { import { VEmpty, ScrollTrigger } from '@/ui'
VEmpty,
ScrollTrigger,
} from '@/ui'
import MinimalHomeOperations from '../MinimalHomeOperations.vue' import MinimalHomeOperations from '../MinimalHomeOperations.vue'
export default Vue.extend({ export default Vue.extend({
@ -46,7 +43,10 @@ export default Vue.extend({
try { try {
this.error = false this.error = false
this.loading = true this.loading = true
this.cards = lodash.uniqBy([...this.cards, ...await getVideoFeeds('video', this.lastID)], it => it.id) this.cards = lodash.uniqBy(
[...this.cards, ...(await getVideoFeeds('video', this.lastID))],
it => it.id,
)
} catch (error) { } catch (error) {
logError(error) logError(error)
this.error = true this.error = true

View File

@ -13,10 +13,7 @@ import { VideoCard } from '@/components/feeds/video-card'
import VideoCardComponent from '@/components/feeds/VideoCard.vue' import VideoCardComponent from '@/components/feeds/VideoCard.vue'
import { logError } from '@/core/utils/log' import { logError } from '@/core/utils/log'
import { ascendingStringSort } from '@/core/utils/sort' import { ascendingStringSort } from '@/core/utils/sort'
import { import { VEmpty, VLoading } from '@/ui'
VEmpty,
VLoading,
} from '@/ui'
import { getTrendingVideos } from '../../trending' import { getTrendingVideos } from '../../trending'
import MinimalHomeOperations from '../MinimalHomeOperations.vue' import MinimalHomeOperations from '../MinimalHomeOperations.vue'
import { minimalHomeOptions } from '../options' import { minimalHomeOptions } from '../options'

View File

@ -3,9 +3,11 @@ import { getJson } from '@/core/ajax'
/** /**
* API , 使 parseJson JSON , this.items * API , 使 parseJson JSON , this.items
*/ */
export const requestMixin = (config: { export const requestMixin = (
requestMethod?: (url: string) => Promise<any> config: {
} = {}) => { requestMethod?: (url: string) => Promise<any>
} = {},
) => {
const { requestMethod = getJson } = config const { requestMethod = getJson } = config
return Vue.extend({ return Vue.extend({
props: { props: {
@ -34,9 +36,10 @@ export const requestMixin = (config: {
try { try {
this.error = false this.error = false
this.loading = true this.loading = true
this.items = this.parseJson( this.items = this.parseJson(await requestMethod(this.api)).slice(
await requestMethod(this.api), 0,
).slice(0, this.itemLimit ?? Infinity) this.itemLimit ?? Infinity,
)
} catch (error) { } catch (error) {
console.error(error) console.error(error)
this.error = true this.error = true
@ -52,19 +55,18 @@ export const requestMixin = (config: {
* UI this.ui , CSS var * UI this.ui , CSS var
* @param variables UI * @param variables UI
*/ */
export const cssVariableMixin = (variables: Record<string, string | number>) => Vue.extend({ export const cssVariableMixin = (variables: Record<string, string | number>) =>
data() { Vue.extend({
return { data() {
ui: variables, return {
} ui: variables,
}, }
mounted() { },
const element = this.$el as HTMLElement mounted() {
Object.entries(variables).forEach( const element = this.$el as HTMLElement
([name, value]) => { Object.entries(variables).forEach(([name, value]) => {
const stringValue = typeof value === 'number' ? `${value}px` : value const stringValue = typeof value === 'number' ? `${value}px` : value
element.style.setProperty(`--${lodash.kebabCase(name)}`, stringValue) element.style.setProperty(`--${lodash.kebabCase(name)}`, stringValue)
}, })
) },
}, })
})

View File

@ -11,10 +11,7 @@ export const component = defineComponentMetadata({
style: () => import('./player-shadow.scss'), style: () => import('./player-shadow.scss'),
}, },
], ],
tags: [ tags: [componentsTags.style, componentsTags.video],
componentsTags.style,
componentsTags.video,
],
description: { description: {
'zh-CN': '为播放器添加主题色投影.', 'zh-CN': '为播放器添加主题色投影.',
}, },

View File

@ -4,11 +4,9 @@ export const component = defineComponentMetadata({
name: 'elegantScrollbar', name: 'elegantScrollbar',
entry: none, entry: none,
displayName: '使用细滚动条', displayName: '使用细滚动条',
description: '使用浏览器的滚动条风格替代系统的滚动条, 不过 macOS 系统滚动条比浏览器做得好一些, 因此不建议 macOS 使用此功能.', description:
tags: [ '使用浏览器的滚动条风格替代系统的滚动条, 不过 macOS 系统滚动条比浏览器做得好一些, 因此不建议 macOS 使用此功能.',
componentsTags.style, tags: [componentsTags.style, componentsTags.general],
componentsTags.general,
],
instantStyles: [ instantStyles: [
{ {
name: 'elegant-scrollbar', name: 'elegant-scrollbar',

View File

@ -16,9 +16,13 @@ export const component = defineComponentMetadata({
'zh-CN': '给脚本的侧栏设置垂直偏移量, 范围为 -35% ~ 40%', 'zh-CN': '给脚本的侧栏设置垂直偏移量, 范围为 -35% ~ 40%',
}, },
entry: ({ metadata }) => { entry: ({ metadata }) => {
addComponentListener(`${metadata.name}.offset`, (value: number) => { addComponentListener(
document.body.style.setProperty('--be-sidebar-offset', `${value}%`) `${metadata.name}.offset`,
}, true) (value: number) => {
document.body.style.setProperty('--be-sidebar-offset', `${value}%`)
},
true,
)
}, },
options: { options: {
offset: { offset: {

View File

@ -5,9 +5,13 @@ export const component = defineComponentMetadata({
name, name,
entry: async ({ metadata }) => { entry: async ({ metadata }) => {
const { addComponentListener } = await import('@/core/settings') const { addComponentListener } = await import('@/core/settings')
addComponentListener(metadata.name, (value: boolean) => { addComponentListener(
document.body.classList.toggle('simplify-comment', value) metadata.name,
}, true) (value: boolean) => {
document.body.classList.toggle('simplify-comment', value)
},
true,
)
}, },
instantStyles: [ instantStyles: [
{ {
@ -29,7 +33,5 @@ export const component = defineComponentMetadata({
> : 关注和等级可以通过鼠标停留在头像上, .`.trim(), > : 关注和等级可以通过鼠标停留在头像上, .`.trim(),
}, },
tags: [ tags: [componentsTags.style],
componentsTags.style,
],
}) })

View File

@ -75,13 +75,15 @@ const metadata: ComponentMetadata = {
() => dqa('.proxy-box > div'), () => dqa('.proxy-box > div'),
elements => elements.length > 0 || isNotHome, elements => elements.length > 0 || isNotHome,
) )
return Object.fromEntries(categoryElements.map(it => ([ return Object.fromEntries(
it.id.replace(/^bili_/, ''), categoryElements.map(it => [
{ it.id.replace(/^bili_/, ''),
displayName: it.querySelector('header .name')?.textContent?.trim() ?? '未知分区', {
defaultValue: false, displayName: it.querySelector('header .name')?.textContent?.trim() ?? '未知分区',
}, defaultValue: false,
]))) },
]),
)
} }
const skipIds = ['推广'] const skipIds = ['推广']
@ -100,24 +102,25 @@ const metadata: ComponentMetadata = {
} }
return null return null
} }
const entries = headers const entries =
?.filter(element => !skipIds.includes(element.id)) headers
.map(element => { ?.filter(element => !skipIds.includes(element.id))
const container = getContainer(element) as HTMLElement .map(element => {
const name = element.id const container = getContainer(element) as HTMLElement
if (container) { const name = element.id
container.dataset.area = name if (container) {
return [ container.dataset.area = name
name, return [
{ name,
displayName: name, {
defaultValue: false, displayName: name,
}, defaultValue: false,
] },
} ]
return null }
}) return null
.filter((it): it is [string, SimplifyHomeOption] => it !== null) ?? [] })
.filter((it): it is [string, SimplifyHomeOption] => it !== null) ?? []
return Object.fromEntries(entries) return Object.fromEntries(entries)
})() })()
const generatedSwitches: Record<string, unknown> = {} const generatedSwitches: Record<string, unknown> = {}
@ -142,12 +145,16 @@ const metadata: ComponentMetadata = {
generatedSwitches[key] = option generatedSwitches[key] = option
}) })
options.simplifyOptions.switches = generatedSwitches options.simplifyOptions.switches = generatedSwitches
const generatedStyles = Object.keys(generatedOptions).map(name => ` const generatedStyles = Object.keys(generatedOptions)
.map(name =>
`
body.simplifyHome-switch-${name} .bili-layout .bili-grid[data-area="${name}"], body.simplifyHome-switch-${name} .bili-layout .bili-grid[data-area="${name}"],
body.simplifyHome-switch-${name} .storey-box .proxy-box #bili_${name} { body.simplifyHome-switch-${name} .storey-box .proxy-box #bili_${name} {
display: none !important; display: none !important;
} }
`.trim()).join('\n') `.trim(),
)
.join('\n')
addStyle(generatedStyles, 'simplify-home-generated') addStyle(generatedStyles, 'simplify-home-generated')
}, },
} }

View File

@ -91,21 +91,19 @@ export const component = createSwitchOptions({
displayName: '房间皮肤', displayName: '房间皮肤',
}, },
}, },
})( })({
{ name: 'simplifyLiveroom',
name: 'simplifyLiveroom', displayName: '简化直播间',
displayName: '简化直播间', entry: styledComponentEntry(
entry: styledComponentEntry(() => import('./live.scss'), async () => { () => import('./live.scss'),
async () => {
const { setupSkinSimplify } = await import('./skin') const { setupSkinSimplify } = await import('./skin')
setupSkinSimplify() setupSkinSimplify()
}),
description: {
'zh-CN': '隐藏直播间中各种不需要的内容.',
}, },
tags: [ ),
componentsTags.live, description: {
componentsTags.style, 'zh-CN': '隐藏直播间中各种不需要的内容.',
],
urlInclude: liveUrls,
}, },
) tags: [componentsTags.live, componentsTags.style],
urlInclude: liveUrls,
})

View File

@ -2,11 +2,15 @@ import { addComponentListener } from '@/core/settings'
import { select } from '@/core/spin-query' import { select } from '@/core/spin-query'
export const setupSkinSimplify = async () => { export const setupSkinSimplify = async () => {
addComponentListener('simplifyLiveroom.switch-skin', async (disable: boolean) => { addComponentListener(
const skinCss = await select('#skin-css') as HTMLStyleElement 'simplifyLiveroom.switch-skin',
if (!skinCss) { async (disable: boolean) => {
return const skinCss = (await select('#skin-css')) as HTMLStyleElement
} if (!skinCss) {
skinCss.media = disable ? 'none' : 'all' return
}, true) }
skinCss.media = disable ? 'none' : 'all'
},
true,
)
} }

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