From 6d591719e24ca02d8e4d9e957ae668eb647c5c11 Mon Sep 17 00:00:00 2001 From: timongh <46739861+timongh@users.noreply.github.com> Date: Tue, 20 Sep 2022 22:19:15 +0800 Subject: [PATCH 01/50] Change feeds to define api --- registry/lib/components/feeds/copy-link/index.ts | 6 +++--- registry/lib/components/feeds/disable-details/index.ts | 6 +++--- registry/lib/components/feeds/extend-live/index.ts | 6 +++--- registry/lib/components/feeds/filter/index.ts | 7 ++++--- registry/lib/components/feeds/fixed-sidebars/index.ts | 6 +++--- registry/lib/components/feeds/fold-comments/index.ts | 6 +++--- registry/lib/components/feeds/full-content/index.ts | 6 +++--- registry/lib/components/feeds/full-title/index.ts | 6 +++--- registry/lib/components/feeds/unfold/index.ts | 6 +++--- 9 files changed, 28 insertions(+), 27 deletions(-) diff --git a/registry/lib/components/feeds/copy-link/index.ts b/registry/lib/components/feeds/copy-link/index.ts index 1fc8a2d8f..767d1db89 100644 --- a/registry/lib/components/feeds/copy-link/index.ts +++ b/registry/lib/components/feeds/copy-link/index.ts @@ -1,4 +1,4 @@ -import { ComponentMetadata } from '@/components/types' +import { defineComponentMetadata } from '@/components/define' import { FeedsCard } from '@/components/feeds/api' import { feedsUrls } from '@/core/utils/urls' @@ -17,7 +17,7 @@ const entry = async () => { added: addCopyLinkButton, }) } -export const component: ComponentMetadata = { +export const component = defineComponentMetadata({ name: 'copyFeedsLink', displayName: '复制动态链接', description: { @@ -29,4 +29,4 @@ export const component: ComponentMetadata = { componentsTags.feeds, componentsTags.utils, ], -} +}) diff --git a/registry/lib/components/feeds/disable-details/index.ts b/registry/lib/components/feeds/disable-details/index.ts index 4fa0e215b..598bf5fa8 100644 --- a/registry/lib/components/feeds/disable-details/index.ts +++ b/registry/lib/components/feeds/disable-details/index.ts @@ -1,4 +1,4 @@ -import { ComponentMetadata } from '@/components/types' +import { defineComponentMetadata } from '@/components/define' import { FeedsCard, feedsCardTypes } from '@/components/feeds/api' import { feedsUrls } from '@/core/utils/urls' @@ -73,7 +73,7 @@ const entry = async () => { added: disableDetails, }) } -export const component: ComponentMetadata = { +export const component = defineComponentMetadata({ name: 'disableFeedsDetails', displayName: '禁止跳转动态详情', tags: [ @@ -92,4 +92,4 @@ export const component: ComponentMetadata = { addStyle() enabled = true }, -} +}) diff --git a/registry/lib/components/feeds/extend-live/index.ts b/registry/lib/components/feeds/extend-live/index.ts index 0988bb399..abbec8a6d 100644 --- a/registry/lib/components/feeds/extend-live/index.ts +++ b/registry/lib/components/feeds/extend-live/index.ts @@ -1,4 +1,4 @@ -import { ComponentMetadata } from '@/components/types' +import { defineComponentMetadata } from '@/components/define' import { styledComponentEntry } from '@/components/styled-component' interface LiveInfo { @@ -63,7 +63,7 @@ const entry = async () => { console.log(presented, extend) } -export const component: ComponentMetadata = { +export const component = defineComponentMetadata({ name: 'extendFeedsLive', displayName: '直播信息扩充', description: { @@ -77,4 +77,4 @@ export const component: ComponentMetadata = { urlInclude: [ /^https:\/\/t\.bilibili\.com\/$/, ], -} +}) diff --git a/registry/lib/components/feeds/filter/index.ts b/registry/lib/components/feeds/filter/index.ts index 4dea62775..c4895ce46 100644 --- a/registry/lib/components/feeds/filter/index.ts +++ b/registry/lib/components/feeds/filter/index.ts @@ -1,5 +1,6 @@ +import { defineComponentMetadata } from '@/components/define' import { feedsCardsManager } from '@/components/feeds/api' -import { ComponentMetadata, ComponentOptions } from '@/components/types' +import { ComponentOptions } from '@/components/types' import { feedsFilterPlugin } from './plugin' const entry = async () => { @@ -50,7 +51,7 @@ export type FeedsFilterOptions = { sideCards: number[] specialTypes: number[] } -export const component: ComponentMetadata = { +export const component = defineComponentMetadata({ name: 'feedsFilter', displayName: '动态过滤器', description: { @@ -68,4 +69,4 @@ export const component: ComponentMetadata = { /^https:\/\/t\.bilibili\.com\/$/, ], plugin: feedsFilterPlugin, -} +}) diff --git a/registry/lib/components/feeds/fixed-sidebars/index.ts b/registry/lib/components/feeds/fixed-sidebars/index.ts index ee10bf626..3c188b9c1 100644 --- a/registry/lib/components/feeds/fixed-sidebars/index.ts +++ b/registry/lib/components/feeds/fixed-sidebars/index.ts @@ -1,4 +1,4 @@ -import { ComponentMetadata } from '@/components/types' +import { defineComponentMetadata } from '@/components/define' const id = 'fixed-sidebars-style' const entry = async () => { @@ -6,7 +6,7 @@ const entry = async () => { disableProfilePopup() } -export const component: ComponentMetadata = { +export const component = defineComponentMetadata({ name: 'fixedFeedsSidebars', instantStyles: [ { @@ -26,4 +26,4 @@ export const component: ComponentMetadata = { urlInclude: [ /^https:\/\/t\.bilibili\.com\/$/, ], -} +}) diff --git a/registry/lib/components/feeds/fold-comments/index.ts b/registry/lib/components/feeds/fold-comments/index.ts index f016ec09f..ee268faf8 100644 --- a/registry/lib/components/feeds/fold-comments/index.ts +++ b/registry/lib/components/feeds/fold-comments/index.ts @@ -1,4 +1,4 @@ -import { ComponentMetadata } from '@/components/types' +import { defineComponentMetadata } from '@/components/define' import { styledComponentEntry } from '@/components/styled-component' import { feedsUrlsWithoutDetail } from '@/core/utils/urls' import { feedsCardsManager } from '@/components/feeds/api' @@ -76,7 +76,7 @@ const entry = async () => { }) } -export const component: ComponentMetadata = { +export const component = defineComponentMetadata({ name: 'foldComments', displayName: '快速收起评论', description: { @@ -87,4 +87,4 @@ export const component: ComponentMetadata = { componentsTags.feeds, ], entry: styledComponentEntry(() => import('./fold-comment.scss'), entry), -} +}) diff --git a/registry/lib/components/feeds/full-content/index.ts b/registry/lib/components/feeds/full-content/index.ts index f76b6e6a3..27062a026 100644 --- a/registry/lib/components/feeds/full-content/index.ts +++ b/registry/lib/components/feeds/full-content/index.ts @@ -1,8 +1,8 @@ -import { ComponentMetadata } from '@/components/types' +import { defineComponentMetadata } from '@/components/define' import { toggleStyle } from '@/components/styled-component' import { feedsUrlsWithoutDetail } from '@/core/utils/urls' -export const component: ComponentMetadata = { +export const component = defineComponentMetadata({ ...toggleStyle('fullFeedsContent', () => import('./full-content.scss')), displayName: '展开动态内容', description: { @@ -13,4 +13,4 @@ export const component: ComponentMetadata = { componentsTags.feeds, ], urlInclude: feedsUrlsWithoutDetail, -} +}) diff --git a/registry/lib/components/feeds/full-title/index.ts b/registry/lib/components/feeds/full-title/index.ts index 144890292..eec89dbf7 100644 --- a/registry/lib/components/feeds/full-title/index.ts +++ b/registry/lib/components/feeds/full-title/index.ts @@ -1,11 +1,11 @@ +import { defineComponentMetadata } from '@/components/define' import { toggleStyle } from '@/components/styled-component' -import { ComponentMetadata } from '@/components/types' -export const component: ComponentMetadata = { +export const component = defineComponentMetadata({ ...toggleStyle('fullFeedsTitle', () => import('./full-feeds-title.scss')), displayName: '展开动态标题', description: { 'zh-CN': '在顶栏的视频动态中, 无论标题多长总是完全展开.', }, tags: [componentsTags.feeds, componentsTags.style], -} +}) diff --git a/registry/lib/components/feeds/unfold/index.ts b/registry/lib/components/feeds/unfold/index.ts index 4e9132771..f30face4b 100644 --- a/registry/lib/components/feeds/unfold/index.ts +++ b/registry/lib/components/feeds/unfold/index.ts @@ -1,7 +1,7 @@ -import { ComponentMetadata } from '@/components/types' +import { defineComponentMetadata } from '@/components/define' import { feedsUrlsWithoutDetail } from '@/core/utils/urls' -export const component: ComponentMetadata = { +export const component = defineComponentMetadata({ name: 'unfoldFeeds', displayName: '动态反折叠', tags: [componentsTags.feeds], @@ -25,4 +25,4 @@ export const component: ComponentMetadata = { }, }) }, -} +}) From 9b44fe09f5804652126c56a6f6f7d8163f143782 Mon Sep 17 00:00:00 2001 From: timongh <46739861+timongh@users.noreply.github.com> Date: Tue, 20 Sep 2022 23:30:09 +0800 Subject: [PATCH 02/50] Change options definition of `feedsFilter` to define api --- registry/lib/components/feeds/filter/index.ts | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/registry/lib/components/feeds/filter/index.ts b/registry/lib/components/feeds/filter/index.ts index c4895ce46..0eae5fc24 100644 --- a/registry/lib/components/feeds/filter/index.ts +++ b/registry/lib/components/feeds/filter/index.ts @@ -1,6 +1,5 @@ -import { defineComponentMetadata } from '@/components/define' +import { defineOptionsMetadata, defineComponentMetadata, OptionsOfMetadata } from '@/components/define' import { feedsCardsManager } from '@/components/feeds/api' -import { ComponentOptions } from '@/components/types' import { feedsFilterPlugin } from './plugin' const entry = async () => { @@ -22,35 +21,33 @@ const entry = async () => { const { mountVueComponent } = await import('@/core/utils') leftPanel.insertAdjacentElement('afterbegin', mountVueComponent(FeedsFilterCard).$el) } -const options: ComponentOptions = { + +const options = defineOptionsMetadata({ types: { - defaultValue: [], + defaultValue: [] as number[], displayName: '过滤动态类型', hidden: true, }, patterns: { - defaultValue: [], + defaultValue: [] as string[], displayName: '过滤关键词', hidden: true, }, sideCards: { - defaultValue: [], + /** FIXME: 虽然代码里按照`number[]`, 但其实存进去的是`string[]`? */ + defaultValue: [] as number[], displayName: '过滤侧边栏', hidden: true, }, specialTypes: { - defaultValue: [], + defaultValue: [] as number[], displayName: '过滤特殊动态类型', hidden: true, }, -} -export type FeedsFilterOptions = { - types: number[] - patterns: string[] - /** FIXME: 虽然代码里按照`number[]`, 但其实存进去的是`string[]`? */ - sideCards: number[] - specialTypes: number[] -} +}) + +export type FeedsFilterOptions = OptionsOfMetadata + export const component = defineComponentMetadata({ name: 'feedsFilter', displayName: '动态过滤器', From e60039747557b6af7ebf5a7b4747eab7fe7510df Mon Sep 17 00:00:00 2001 From: timongh <46739861+timongh@users.noreply.github.com> Date: Sun, 2 Oct 2022 00:06:22 +0800 Subject: [PATCH 03/50] Change some components to define api --- .../style/dark-mode/schedule/index.ts | 58 ++++++++++--------- .../components/video/player/common/speed.ts | 7 ++- 2 files changed, 36 insertions(+), 29 deletions(-) diff --git a/registry/lib/components/style/dark-mode/schedule/index.ts b/registry/lib/components/style/dark-mode/schedule/index.ts index 4da930dd7..d0dd924b6 100644 --- a/registry/lib/components/style/dark-mode/schedule/index.ts +++ b/registry/lib/components/style/dark-mode/schedule/index.ts @@ -1,4 +1,4 @@ -import { ComponentMetadata } from '@/components/types' +import { defineComponentMetadata, defineOptionsMetadata, OptionsOfMetadata } from '@/components/define' import { fullyLoaded } from '@/core/life-cycle' import { ComponentSettings, getComponentSettings } from '@/core/settings' import { Range } from '@/ui/range' @@ -91,7 +91,33 @@ class ScheduleTime { return result } } -const checkTime = (settings: ComponentSettings) => { + +const options = defineOptionsMetadata({ + range: { + defaultValue: { + start: '18:00', + end: '6:00', + }, + displayName: '时间段', + validator: (range: Range) => { + const { start, end } = range + const regex = /^(\d{1,2}):(\d{1,2})$/ + if (!regex.test(start) || !regex.test(end)) { + return null + } + const startTime = new ScheduleTime(range.start) + const endTime = new ScheduleTime(range.end) + return { + start: startTime.toString(), + end: endTime.toString(), + } + }, + }, +}) + +type Options = OptionsOfMetadata + +const checkTime = (settings: ComponentSettings) => { const start = new ScheduleTime(settings.options.range.start) const end = new ScheduleTime(settings.options.range.end) const now = new ScheduleTime() @@ -110,7 +136,8 @@ const checkTime = (settings: ComponentSettings) => { setTimeout(() => checkTime(settings), timeout) } } -export const component: ComponentMetadata = { + +export const component = defineComponentMetadata({ name: 'darkModeSchedule', displayName: '夜间模式计划时段', description: '设置一个使用夜间模式的时间段, 进入 / 离开此时间段时, 会自动开启 / 关闭夜间模式. 结束时间小于起始时间时将视为次日, 如 `18:00` 至 `6:00` 表示晚上 18:00 到次日 6:00. 请勿和 \`夜间模式跟随系统\` 一同使用.', @@ -120,26 +147,5 @@ export const component: ComponentMetadata = { ], entry: ({ settings }) => fullyLoaded(() => checkTime(settings)), urlExclude: darkExcludes, - options: { - range: { - defaultValue: { - start: '18:00', - end: '6:00', - }, - displayName: '时间段', - validator: (range: Range) => { - const { start, end } = range - const regex = /^(\d{1,2}):(\d{1,2})$/ - if (!regex.test(start) || !regex.test(end)) { - return null - } - const startTime = new ScheduleTime(range.start) - const endTime = new ScheduleTime(range.end) - return { - start: startTime.toString(), - end: endTime.toString(), - } - }, - }, - }, -} + options, +}) diff --git a/registry/lib/components/video/player/common/speed.ts b/registry/lib/components/video/player/common/speed.ts index abd370937..f053e1573 100644 --- a/registry/lib/components/video/player/common/speed.ts +++ b/registry/lib/components/video/player/common/speed.ts @@ -1,7 +1,8 @@ import { ComponentEntry, ComponentMetadata, - ComponentOption, + OptionMetadata, + UnknownOptions, } from '@/components/types' import { CoreApis } from '@/core/core-apis' import { addComponentListener, ComponentSettings } from '@/core/settings' @@ -31,7 +32,7 @@ export type EntryContext = Parameters[0] export type OptionSubjects = O & { [K in keyof O as `${Exclude}$`]: Subject } -export class EntrySpeedComponent> +export class EntrySpeedComponent implements EntryContext { static create: < OO extends Record = unknown @@ -39,7 +40,7 @@ implements EntryContext { ComponentMetadata, 'entry' | 'reload' | 'unload' | 'options' > & { - options?: { [K in keyof OO]: ComponentOption } + options?: { [K in keyof OO]: OptionMetadata } }) => ComponentMetadata; static contextMap: Partial> = { From 691ed3318805df4a0367fc82231bb75189b166ca Mon Sep 17 00:00:00 2001 From: timongh <46739861+timongh@users.noreply.github.com> Date: Sun, 2 Oct 2022 01:05:15 +0800 Subject: [PATCH 04/50] Change the type of UnknownOptions from any to unknown --- .../touch/mini-player/touch-move.ts | 2 +- .../touch/player-gestures/swiper.ts | 2 +- .../lib/components/utils/keymap/actions.ts | 5 +- registry/lib/components/utils/keymap/index.ts | 127 ++++++++++-------- .../video/player/extend-speed/component.ts | 3 +- .../video/player/remember-speed/component.ts | 2 +- .../style/custom-navbar-channel/index.ts | 8 +- src/components/auto-update/checker.ts | 3 +- src/components/auto-update/index.ts | 2 +- src/components/types.ts | 3 +- src/core/settings/helpers.ts | 11 +- 11 files changed, 92 insertions(+), 76 deletions(-) diff --git a/registry/lib/components/touch/mini-player/touch-move.ts b/registry/lib/components/touch/mini-player/touch-move.ts index bca3768b1..65c1497a8 100644 --- a/registry/lib/components/touch/mini-player/touch-move.ts +++ b/registry/lib/components/touch/mini-player/touch-move.ts @@ -67,7 +67,7 @@ export const enableTouchMove = (element: HTMLElement, options?: TouchMoveOptions // let startTouch: Touch let lastTouch: Touch let move: boolean - const minMoveDistance = lodash.get(options, 'minMoveDistance', getComponentSettings('touchMiniPlayer').options.touchMoveDistance) + const minMoveDistance = lodash.get(options, 'minMoveDistance', getComponentSettings('touchMiniPlayer').options.touchMoveDistance) as number // const scroll = lodash.get(options, 'scroll', false) const touchstart = (e: TouchEvent) => { if (e.touches.length < 1) { diff --git a/registry/lib/components/touch/player-gestures/swiper.ts b/registry/lib/components/touch/player-gestures/swiper.ts index c6d8f7bdf..231b50d91 100644 --- a/registry/lib/components/touch/player-gestures/swiper.ts +++ b/registry/lib/components/touch/player-gestures/swiper.ts @@ -39,7 +39,7 @@ const createPosition = (e: TouchEvent, element: HTMLElement) => { * - `cancel`: 请求取消调整 */ export class SwipeAction extends EventTarget { - minSwipeDistance = getComponentSettings('touchPlayerGestures').options.swiperDistance + minSwipeDistance = getComponentSettings('touchPlayerGestures').options.swiperDistance as number startPosition: Position = null lastAction: { type: 'brightness' | 'volume' | 'progress' diff --git a/registry/lib/components/utils/keymap/actions.ts b/registry/lib/components/utils/keymap/actions.ts index 6cdc53ef3..ace0242cb 100644 --- a/registry/lib/components/utils/keymap/actions.ts +++ b/registry/lib/components/utils/keymap/actions.ts @@ -1,6 +1,7 @@ import { playerAgent } from '@/components/video/player-agent' import { getComponentSettings } from '@/core/settings' import { registerAndGetData } from '@/plugins/data' +import { Options } from '.' import { KeyBindingAction, KeyBindingActionContext } from './bindings' export const clickElement = ( @@ -172,11 +173,11 @@ export const builtInActions: Record = { }, longJumpBackward: { displayName: '长倒退', - run: () => playerAgent.changeTime(-(getComponentSettings('keymap').options.longJumpSeconds)), + run: () => playerAgent.changeTime(-(getComponentSettings('keymap').options.longJumpSeconds)), }, longJumpForward: { displayName: '长前进', - run: () => playerAgent.changeTime(getComponentSettings('keymap').options.longJumpSeconds), + run: () => playerAgent.changeTime(getComponentSettings('keymap').options.longJumpSeconds), }, jumpBackward: { displayName: '倒退', diff --git a/registry/lib/components/utils/keymap/index.ts b/registry/lib/components/utils/keymap/index.ts index e9c71cab4..5f57b9896 100644 --- a/registry/lib/components/utils/keymap/index.ts +++ b/registry/lib/components/utils/keymap/index.ts @@ -1,89 +1,100 @@ import { LaunchBarActionProvider } from '@/components/launch-bar/launch-bar-action' import { styledComponentEntry } from '@/components/styled-component' -import { ComponentEntry } from '@/components/types' -import { defineComponentMetadata } from '@/components/define' +import { + defineComponentMetadata, + defineOptionsMetadata, + OptionsOfMetadata, +} from '@/components/define' import { addComponentListener } from '@/core/settings' import { actions } from './actions' import { KeyBinding, KeyBindingConfig, loadKeyBindings } from './bindings' import { presetBase, presets } from './presets' -let config: KeyBindingConfig = null -const parseBindings = (bindings: Record) => ( - Object.entries(bindings).map(([actionName, keyString]) => { - const keys = keyString.split(' ').filter(it => it !== '') - return { - keys, - action: actions[actionName] || none, - } as KeyBinding - }) -) -const entry: ComponentEntry = styledComponentEntry(() => import('./playback-tip.scss'), async ({ settings }) => { - const update = () => { - const presetName = settings.options.preset - const preset = presets[presetName] || {} - const bindings = parseBindings( - { ...presetBase, ...preset, ...settings.options.customKeyBindings }, - ) - if (config) { - config.bindings = bindings - } else { - config = loadKeyBindings(bindings) - } - } - - addComponentListener('keymap.preset', update, true) - addComponentListener('keymap.customKeyBindings', update) +const options = defineOptionsMetadata({ + longJumpSeconds: { + defaultValue: 85, + displayName: '长跳跃秒数', + }, + customKeyBindings: { + defaultValue: {} as Record, + displayName: '自定义键位', + hidden: true, + }, + preset: { + defaultValue: 'Default', + displayName: '预设', + hidden: true, + }, }) +export type Options = OptionsOfMetadata +let config: KeyBindingConfig = null +const parseBindings = (bindings: Record): KeyBinding[] => { + const parseBinding = (actionName: string, keyString: string) => { + const keys = keyString.split(' ').filter(it => it !== '') + return { keys, action: actions[actionName] } + } + return Object.entries(bindings).map(([n, k]) => parseBinding(n, k)) +} +const entry = styledComponentEntry( + () => import('./playback-tip.scss'), + async ({ settings }) => { + const update = () => { + const presetName = settings.options.preset + const preset = presets[presetName] || {} + const bindings = parseBindings({ + ...presetBase, + ...preset, + ...settings.options.customKeyBindings, + }) + if (config) { + config.bindings = bindings + } else { + config = loadKeyBindings(bindings) + } + } + + addComponentListener('keymap.preset', update, true) + addComponentListener('keymap.customKeyBindings', update) + }, +) export const component = defineComponentMetadata({ name: 'keymap', displayName: '快捷键扩展', - tags: [ - componentsTags.video, - componentsTags.utils, - ], + tags: [componentsTags.video, componentsTags.utils], // urlInclude: [ // ...videoAndBangumiUrls, // ...cheeseUrls, // ...mediaListUrls, // ], entry, - unload: () => { config && (config.enable = false) }, - reload: () => { config && (config.enable = true) }, + unload: () => { + config && (config.enable = false) + }, + reload: () => { + config && (config.enable = true) + }, description: { 'zh-CN': '为脚本的功能和 b 站的功能启用键盘快捷键支持, 快捷键列表可在`快捷键设置`中查看和配置.', }, extraOptions: () => import('./settings/ExtraOptions.vue').then(m => m.default), - options: { - longJumpSeconds: { - defaultValue: 85, - displayName: '长跳跃秒数', - }, - customKeyBindings: { - defaultValue: {}, - displayName: '自定义键位', - hidden: true, - }, - preset: { - defaultValue: 'Default', - displayName: '预设', - hidden: true, - }, - }, + options, plugin: { displayName: '快捷键扩展 - 搜索支持', setup: ({ addData }) => { addData('launchBar.actions', (providers: LaunchBarActionProvider[]) => { providers.push({ name: 'keymapSettings', - getActions: async () => [{ - name: '快捷键扩展设置', - description: 'Keymap Settings', - icon: 'mdi-keyboard-settings-outline', - action: async () => { - const { toggleKeymapSettings } = await import('./settings/vm') - toggleKeymapSettings() + getActions: async () => [ + { + name: '快捷键扩展设置', + description: 'Keymap Settings', + icon: 'mdi-keyboard-settings-outline', + action: async () => { + const { toggleKeymapSettings } = await import('./settings/vm') + toggleKeymapSettings() + }, }, - }], + ], }) }) }, diff --git a/registry/lib/components/video/player/extend-speed/component.ts b/registry/lib/components/video/player/extend-speed/component.ts index c8004f525..af8c8e2f6 100644 --- a/registry/lib/components/video/player/extend-speed/component.ts +++ b/registry/lib/components/video/player/extend-speed/component.ts @@ -1,3 +1,4 @@ +import { UnknownOptions } from '@/components/types' import { ComponentSettings, getComponentSettings } from '@/core/settings' import { addStyle } from '@/core/style' import { Toast } from '@/core/toast' @@ -28,7 +29,7 @@ export const ERROR_MESSAGE_DURATION = 5000 /** 计算菜单项 order */ export const calcOrder = (value: number) => ((MAX_BROWSER_SPEED_VALUE - value) * 10000).toString() -export interface Options { +export type Options = { /** 最大菜单高度 */ maxMenuHeight: boolean /** 隐藏进度条 */ diff --git a/registry/lib/components/video/player/remember-speed/component.ts b/registry/lib/components/video/player/remember-speed/component.ts index 0ff501f26..6c75b58b0 100644 --- a/registry/lib/components/video/player/remember-speed/component.ts +++ b/registry/lib/components/video/player/remember-speed/component.ts @@ -3,7 +3,7 @@ import { EntrySpeedComponent, VideoIdObject } from '../common/speed' import { NoSuchSpeedMenuItemElementError, SpeedContext } from '../common/speed/context' import { formatSpeedText } from '../common/speed/utils' -export interface Options { +export type Options = { /** 全局倍速 */ globalSpeed: number /** 固定全局倍速 */ diff --git a/registry/lib/plugins/style/custom-navbar-channel/index.ts b/registry/lib/plugins/style/custom-navbar-channel/index.ts index 7e5cfe955..edd343f71 100644 --- a/registry/lib/plugins/style/custom-navbar-channel/index.ts +++ b/registry/lib/plugins/style/custom-navbar-channel/index.ts @@ -1,3 +1,4 @@ +import { CustomNavbarOptions } from 'registry/lib/components/style/custom-navbar' import { PluginMetadata } from '@/plugins/plugin' import { getComponentSettings } from '@/core/settings' import type { CustomNavbarItemInit } from '../../../components/style/custom-navbar/custom-navbar-item' @@ -11,7 +12,7 @@ export const plugin: PluginMetadata = { const defaultLink = 'https://www.bilibili.com/v/channel/' const name = 'channel' const isOpenInNewTab = () => { - const { options } = getComponentSettings('customNavbar') + const { options } = getComponentSettings('customNavbar') as { options: CustomNavbarOptions } if (name in options.openInNewTabOverrides) { return options.openInNewTabOverrides[name] } @@ -23,7 +24,10 @@ export const plugin: PluginMetadata = { content: () => import('./NavbarChannel.vue'), clickAction: () => { const channelId = dq('.navbar-channel[data-channel-id]').getAttribute('data-channel-id') - window.open(channelId ? `${defaultLink}${channelId}` : defaultLink, isOpenInNewTab() ? '_blank' : '_self') + window.open( + channelId ? `${defaultLink}${channelId}` : defaultLink, + isOpenInNewTab() ? '_blank' : '_self', + ) }, }) }) diff --git a/src/components/auto-update/checker.ts b/src/components/auto-update/checker.ts index a87ee11bc..10e968186 100644 --- a/src/components/auto-update/checker.ts +++ b/src/components/auto-update/checker.ts @@ -13,6 +13,7 @@ import { UpdateCheckItem, CheckSingleTypeUpdateConfig, } from './utils' +import { Options } from '.' export const checkUpdate = async (config: CheckUpdateConfig) => { const { @@ -140,7 +141,7 @@ export const checkAllUpdate = async (config: CheckSingleTypeUpdateConfig) => { console.groupEnd() } export const silentCheckUpdate = () => checkAllUpdate({ - maxCount: getComponentSettings(name).options.maxUpdateCount, + maxCount: getComponentSettings(name).options.maxUpdateCount, }) export const silentCheckUpdateAndReload = reload(silentCheckUpdate) diff --git a/src/components/auto-update/index.ts b/src/components/auto-update/index.ts index e120f749f..fcde49450 100644 --- a/src/components/auto-update/index.ts +++ b/src/components/auto-update/index.ts @@ -62,7 +62,7 @@ const optionsMetadata = defineOptionsMetadata({ }, }) -type Options = OptionsOfMetadata +export type Options = OptionsOfMetadata const entry: ComponentEntry = async ({ settings: { options: opt }, diff --git a/src/components/types.ts b/src/components/types.ts index 86bf1be91..af5c9758c 100644 --- a/src/components/types.ts +++ b/src/components/types.ts @@ -48,8 +48,7 @@ export interface ComponentTag { type ComponentOptionValidator = (value: T, oldValue: T) => T | undefined | null -// TODO: 参考 discussion #3041。当不兼容代码替换完成后将 any 改为 unknown -export type UnknownOptions = Record +export type UnknownOptions = Record export type EmptyOptions = Record diff --git a/src/core/settings/helpers.ts b/src/core/settings/helpers.ts index ff8bd48ba..7691c92ef 100644 --- a/src/core/settings/helpers.ts +++ b/src/core/settings/helpers.ts @@ -58,12 +58,11 @@ const emptySettings: ComponentSettings = { }), } -// TODO: 参考 discussion #3041。 -// 当不兼容代码替换完成后将 R 的默认类型替换为 UnknownOptions /** * 获取已加载组件的设置 * - * 若组件未安装,则返回一个默认的 ComponentSettings 对象: + * 使用此函数,应当确保该组件已被加载。 + * 否则返回值是一个默认的 ComponentSettings 对象: * ```js * { * enabled: false, @@ -73,9 +72,9 @@ const emptySettings: ComponentSettings = { * * @param component 组件或组件名称 */ -export const getComponentSettings = ( - component: ComponentMetadata | string, -): ComponentSettings => { +export const getComponentSettings = ( + component: ComponentMetadata | string, +): ComponentSettings => { let componentMetadata: ComponentMetadata if (typeof component === 'string') { if (componentsMap[component] === undefined) { From 1dbbc36d22abe14e1f02a0ecd49e97dc4becd51b Mon Sep 17 00:00:00 2001 From: timongh <46739861+timongh@users.noreply.github.com> Date: Sun, 2 Oct 2022 01:07:17 +0800 Subject: [PATCH 05/50] Perfect the type of styled-components --- .../utils/image-resolution/index.ts | 20 +++++++++++-------- .../utils/image-resolution/resolution.ts | 3 ++- src/components/styled-component.ts | 20 ++++++++----------- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/registry/lib/components/utils/image-resolution/index.ts b/registry/lib/components/utils/image-resolution/index.ts index ffc4867d6..436fe23bd 100644 --- a/registry/lib/components/utils/image-resolution/index.ts +++ b/registry/lib/components/utils/image-resolution/index.ts @@ -1,6 +1,16 @@ -import { defineComponentMetadata } from '@/components/define' +import { defineComponentMetadata, defineOptionsMetadata, OptionsOfMetadata } from '@/components/define' import { startResolution } from './resolution' +const options = defineOptionsMetadata({ + scale: { + displayName: '缩放级别', + defaultValue: 'auto', + hidden: true, + }, +}) + +export type Options = OptionsOfMetadata + export const component = defineComponentMetadata({ name: 'imageResolution', displayName: '高分辨率图片', @@ -12,11 +22,5 @@ export const component = defineComponentMetadata({ description: { 'zh-CN': '根据屏幕 DPI 请求更高分辨率的图片, 例如 DPI 缩放 200% 则请求 2 倍的分辨率, 加载时间也会相应变长一些. (也会导致某些浏览器里出现图片闪动, 因为本质上是更换了图片源)', }, - options: { - scale: { - displayName: '缩放级别', - defaultValue: 'auto', - hidden: true, - }, - }, + options, }) diff --git a/registry/lib/components/utils/image-resolution/resolution.ts b/registry/lib/components/utils/image-resolution/resolution.ts index 3d8b5a7d4..2ab9e1317 100644 --- a/registry/lib/components/utils/image-resolution/resolution.ts +++ b/registry/lib/components/utils/image-resolution/resolution.ts @@ -1,4 +1,5 @@ import { styledComponentEntry } from '@/components/styled-component' +import { Options } from '.' const resizeRegex = /@(\d+)[Ww]_(\d+)[Hh]/ const excludeSelectors = [ @@ -59,7 +60,7 @@ export const imageResolution = async (dpi: number, element: HTMLElement) => { replaceSource(e => e.style.backgroundImage, (e, v) => (e.style.backgroundImage = v)) }) } -export const startResolution = styledComponentEntry(() => import('./fix.scss'), +export const startResolution = styledComponentEntry(() => import('./fix.scss'), async ({ settings }) => { const { allMutations } = await import('@/core/observer') const dpi = settings.options.scale === 'auto' diff --git a/src/components/styled-component.ts b/src/components/styled-component.ts index 36947ea8e..fec9c7b94 100644 --- a/src/components/styled-component.ts +++ b/src/components/styled-component.ts @@ -1,33 +1,31 @@ import { none } from '@/core/utils' -import { ComponentEntry, ComponentMetadata } from './component' +import { ComponentEntry, ComponentMetadata, UnknownOptions } from './component' /** * 创建一个自动添加指定样式的组件入口函数 * @param styleImport 动态导入样式的函数 * @param entry 组件入口函数 */ -export const styledComponentEntry = ( +export const styledComponentEntry = ( styleImport: () => Promise<{ default: string }>, - entry: ComponentEntry, -): ComponentEntry => ( - async context => { + entry: ComponentEntry, +): ComponentEntry => async context => { const { default: style } = await styleImport() const { addStyle } = await import('@/core/style') addStyle(style, context.metadata.name) return entry(context) } -) /** * 创建仅切换样式的组件`entry`, `reload`和`unload`, 展开至组件定义中即可, 也可以提供可选的组件入口函数 * @param styleImport 动态导入样式的函数 * @param entry 组件入口函数 */ -export const toggleStyle = ( +export const toggleStyle = ( name: string, styleImport: () => Promise<{ default: string }>, - entry: ComponentEntry = none, -): Pick => { + entry: ComponentEntry = none, +): Pick, 'name' | 'entry' | 'reload' | 'unload'> => { let styleElement: HTMLStyleElement = null const styleEntry = async () => { if (styleElement) { @@ -39,9 +37,7 @@ export const toggleStyle = ( } return { name, - entry: context => ( - styleEntry().then(() => entry(context)) - ), + entry: context => styleEntry().then(() => entry(context)), reload: styleEntry, unload: () => { styleElement?.remove() From 982491557d802ce411c6abe3eb334e6696e6b3cc Mon Sep 17 00:00:00 2001 From: timongh <46739861+timongh@users.noreply.github.com> Date: Sun, 2 Oct 2022 01:07:50 +0800 Subject: [PATCH 06/50] Remove ComponentOption(s) --- src/components/types.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/components/types.ts b/src/components/types.ts index af5c9758c..0ea865504 100644 --- a/src/components/types.ts +++ b/src/components/types.ts @@ -80,10 +80,6 @@ export type OptionsMetadata = { [OptionName in keyof O]: OptionMetadata } -// TODO: 参考 discussion #3041。当不兼容代码替换完成后删除 -export type ComponentOptions = OptionsMetadata -export type ComponentOption = OptionMetadata - /** 组件标签 */ export const componentsTags = { /** 视频 */ From 3f9cf020a0d620e46299d24defe0851b353b7b18 Mon Sep 17 00:00:00 2001 From: timongh <46739861+timongh@users.noreply.github.com> Date: Sun, 2 Oct 2022 03:07:43 +0800 Subject: [PATCH 07/50] Perfect the type of switch options --- .../components/style/simplify/home/index.ts | 93 +++-- .../components/style/simplify/live/index.ts | 4 +- src/components/switch-options.ts | 361 +++++++++++++++--- 3 files changed, 364 insertions(+), 94 deletions(-) diff --git a/registry/lib/components/style/simplify/home/index.ts b/registry/lib/components/style/simplify/home/index.ts index 1b8ce162d..90957df50 100644 --- a/registry/lib/components/style/simplify/home/index.ts +++ b/registry/lib/components/style/simplify/home/index.ts @@ -1,12 +1,16 @@ -import { createSwitchOptions, SwitchOptions } from '@/components/switch-options' -import { ComponentMetadata } from '@/components/types' +import { + newSwitchComponentBuilder, + defineSwitchMetadata, + defineIncompleteSwitchComponentMetadata, +} from '@/components/switch-options' + import { addComponentListener, getComponentSettings } from '@/core/settings' import { sq } from '@/core/spin-query' import { addStyle } from '@/core/style' import { getCookieValue } from '@/core/utils' import { mainSiteUrls } from '@/core/utils/urls' -const switchOptions: SwitchOptions = { +const switchMetadata = defineSwitchMetadata({ name: 'simplifyOptions', dimAt: 'checked', switchProps: { @@ -43,8 +47,9 @@ const switchOptions: SwitchOptions = { displayName: '右侧分区导航(旧)', }, }, -} -const metadata: ComponentMetadata = { +}) + +const metadata = defineIncompleteSwitchComponentMetadata({ name: 'simplifyHome', displayName: '简化首页', description: { @@ -81,13 +86,17 @@ const metadata: ComponentMetadata = { () => dqa('.proxy-box > div'), elements => elements.length > 0 || isNotHome, ) - return Object.fromEntries(categoryElements.map(it => ([ - it.id.replace(/^bili_/, ''), - { - displayName: it.querySelector('header .name')?.textContent?.trim() ?? '未知分区', - defaultValue: false, - }, - ]))) + return Object.fromEntries( + categoryElements.map(it => [ + it.id.replace(/^bili_/, ''), + { + displayName: + it.querySelector('header .name')?.textContent?.trim() + ?? '未知分区', + defaultValue: false, + }, + ]), + ) } const skipIds = ['推广'] @@ -123,39 +132,47 @@ const metadata: ComponentMetadata = { } return null }) - .filter((it): it is [string, SimplifyHomeOption] => it !== null) ?? [] + .filter((it): it is [string, SimplifyHomeOption] => it !== null) + ?? [] return Object.fromEntries(entries) })() const generatedSwitches: Record = {} - Object.entries(generatedOptions).forEach(([key, { displayName, defaultValue }]) => { - const option = { - defaultValue, - displayName, - } - const optionKey = `switch-${key}` - if (options[optionKey] === undefined) { - options[optionKey] = defaultValue - } - const switchKey = `switch-${key}` - addComponentListener( - `${metadata.name}.${switchKey}`, - (value: boolean) => { - document.body.classList.toggle(`${metadata.name}-${switchKey}`, value) - }, - true, - ) - switchOptions.switches[key] = option - generatedSwitches[key] = option - }) - options.simplifyOptions.switches = generatedSwitches - const generatedStyles = Object.keys(generatedOptions).map(name => ` + Object.entries(generatedOptions).forEach( + ([key, { displayName, defaultValue }]) => { + const option = { + defaultValue, + displayName, + } + const optionKey = `switch-${key}` + if (options[optionKey] === undefined) { + options[optionKey] = defaultValue + } + const switchKey = `switch-${key}` + addComponentListener( + `${metadata.name}.${switchKey}`, + (value: boolean) => { + document.body.classList.toggle( + `${metadata.name}-${switchKey}`, + value, + ) + }, + true, + ) + switchMetadata.switches[key] = option + generatedSwitches[key] = option + }, + ); + (options.simplifyOptions as any).switches = generatedSwitches + const generatedStyles = Object.keys(generatedOptions) + .map(name => ` body.simplifyHome-switch-${name} .bili-layout .bili-grid[data-area="${name}"], body.simplifyHome-switch-${name} .storey-box .proxy-box #bili_${name} { display: none !important; } - `.trim()).join('\n') + `.trim()) + .join('\n') addStyle(generatedStyles, 'simplify-home-generated') }, -} +}) -export const component = createSwitchOptions(switchOptions)(metadata) +export const component = newSwitchComponentBuilder(switchMetadata)(metadata) diff --git a/registry/lib/components/style/simplify/live/index.ts b/registry/lib/components/style/simplify/live/index.ts index 684f4cea5..b0d869534 100644 --- a/registry/lib/components/style/simplify/live/index.ts +++ b/registry/lib/components/style/simplify/live/index.ts @@ -1,8 +1,8 @@ -import { createSwitchOptions } from '@/components/switch-options' +import { newSwitchComponentBuilder } from '@/components/switch-options' import { styledComponentEntry } from '@/components/styled-component' import { liveUrls } from '@/core/utils/urls' -export const component = createSwitchOptions({ +export const component = newSwitchComponentBuilder({ name: 'simplifyOptions', dimAt: 'checked', switchProps: { diff --git a/src/components/switch-options.ts b/src/components/switch-options.ts index 26d251aa2..1006987d5 100644 --- a/src/components/switch-options.ts +++ b/src/components/switch-options.ts @@ -1,71 +1,324 @@ -import { getComponentSettings, addComponentListener } from '@/core/settings' -import { ComponentMetadata, ComponentOptions } from './component' +/** + * Switch Options + * @module components/switch-options + * + * 通过包装原始的 ComponentMetadata 为组件提供一系列开关选项。 + * 如果组件未定义 Widget,还会提供一个默认的有相同效果的 Widget。 + * + * 主要函数是 {@link newSwitchComponentBuilder}。 + */ -type Switches = { - [key: string]: { - displayName: string - defaultValue: boolean - } +import { getComponentSettings, addComponentListener } from '@/core/settings' +import { + ComponentEntry, + ComponentMetadata, + OptionsMetadata, + OptionsOfMetadata, + UnknownOptions, +} from './component' +import { Widget } from './widget' + +/** + * 单个开关的设置 + */ +export interface SwitchItemMetadata { + displayName: string + defaultValue: boolean } -export interface SwitchOptions { - name: string - switches: Switches - radio?: boolean - dimAt?: 'checked' | 'notChecked' + +/** + * 一个对象,键是开关的名称,用于生成 options;值是关于单个开关的设置 + */ +export type SwitchItemsMetadata = { + [key in S]: SwitchItemMetadata +} + +/** + * 从 SwitchItemsMetadata 中提取所有 SwitchItem 的名称 + */ +export type SwitchItemNamesOfItemsMetadata> = keyof C + +/** + * 可用于单独定义 SwitchItemsMetadata + */ +export const defineSwitchItemsMetadata = ( + c: SwitchItemsMetadata, +): SwitchItemsMetadata => c + +/** + * 用于配置 API 的行为 + * + * {@link SwitchItemsMetadata} 可用 {@link defineSwitchItemsMetadata} 单独定义 + */ +export interface SwitchMetadata { + // 注入 options 时会使用 + name: N + // 每个开关的单独配置,其键名会被用于生成注入 options 的名称,见 {@link SwitchItemNames} + switches: SwitchItemsMetadata + // 是否单选 default: false + radio?: undefined | boolean + // default: 'checked' + dimAt?: undefined | 'checked' | 'notChecked' + // 配置每个开关的图标 switchProps?: { checkedIcon?: string notCheckedIcon?: string iconPosition?: 'left' | 'right' } } -export const createSwitchOptions = (options: SwitchOptions) => { - if (options.radio === undefined) { - options.radio = false + +/** + * 从 SwitchMetadata 中提取 name 的字面量类型(如果可能) + */ +export type SwitchNameOfMetadata> = C['name'] + +/** + * 从 SwitchMetadata 中提取所有 SwitchItem 的名称 + */ +export type SwitchItemNamesOfMetadata> = + keyof C['switches'] + +/** + * 定义一个 {@link SwitchMetadata} + */ +export const defineSwitchMetadata = ( + c: SwitchMetadata, +): SwitchMetadata => c + +/** + * 传递给 Widget 的选项。同时也是注入组件的 options 的一部分 + */ +export interface SwitchWidgetOptions { + name: N + switches: SwitchItemsMetadata + radio: boolean + dimAt: 'checked' | 'notChecked' + switchProps?: { + checkedIcon?: string + notCheckedIcon?: string + iconPosition?: 'left' | 'right' } - const { name: optionName, switches } = options - const extendComponentOptions: ComponentOptions = {} - Object.entries(switches).forEach(([key, { displayName, defaultValue }]) => { - extendComponentOptions[`switch-${key}`] = { + componentName: string + optionDisplayName: string +} + +/** + * 创建一个 SwitchWidgetOptions + */ +const newSwitchWidgetOptions = ( + metadata: SwitchMetadata, + componentName: string, + optionDisplayName, +) => ({ + ...metadata, + radio: metadata.radio === undefined ? false : metadata.radio, + dimAt: metadata.dimAt === undefined ? 'checked' : metadata.dimAt, + componentName, + optionDisplayName, + }) + +/** + * 注入的 options 的名称集合。规则是 SwitchItemsMetadata 的所有键加上 'switch-' 前缀。 + */ +export type SwitchItemNames = `switch-${S}` + +/** + * API 注入的 options 中的一部分 + */ +export type SwitchItemOptions = Record, string> + +/** + * 在原始的 Options 类型中添加了该 API 所需的定义后形成的类型 + */ +export type SwitchOptions = O & { + N: SwitchWidgetOptions +} & SwitchItemOptions + +/** + * 提取 SwitchOptions 类型 + */ +export type SwitchOptionsOfSwitchMetadata< + O extends UnknownOptions, + C extends SwitchMetadata, +> = C extends SwitchMetadata ? SwitchOptions : never + +/** + * 提取 SwitchOptions 类型 + */ +export type SwitchOptionsOfMetadata< + M extends OptionsMetadata, + C extends SwitchMetadata, +> = SwitchOptionsOfSwitchMetadata, C> + +/** + * 在原始的 OptionsMetadata 中添加了该 API 所需的定义后形成的类型 + */ +export type SwitchOptionsMetadata< + O extends UnknownOptions, + N extends string, + S extends string, +> = OptionsMetadata> + +/** + * 创建一个 extender。 + * + * extender 是一个函数,其作用是为传入的 OptionsMetadata 添加新字段。 + * + * 该函数会直接修改传入的 OptionsMetadata,并将其返回。 + */ +const newSwitchOptionsMetadataExtender = ( + itemsMetadata: SwitchItemsMetadata, +): (( + options: OptionsMetadata, + widgetOptions: SwitchWidgetOptions, +) => SwitchOptionsMetadata) => { + const optionsToExtend = {} + const entries = Object.entries(itemsMetadata) + for (const [key, { displayName, defaultValue }] of entries) { + optionsToExtend[`switch-${key}`] = { defaultValue, displayName, hidden: true, } - }) - return (component: ComponentMetadata) => { - const optionDisplayName = `${component.displayName}选项` - const selfOption = { - componentName: component.name, - optionDisplayName, - } - Object.assign(options, selfOption) - extendComponentOptions[optionName] = { + } + return ( + options: OptionsMetadata, + widgetOptions: SwitchWidgetOptions, + ) => { + optionsToExtend[widgetOptions.name as string] = { defaultValue: options, - displayName: optionDisplayName, + displayName: widgetOptions.optionDisplayName, } - component.options = { ...component.options, ...extendComponentOptions } - if (!component.widget) { - component.widget = { - component: () => import('./SwitchOptions.vue').then(m => m.default), - options, - } - } - const originalEntry = component.entry - component.entry = async (...args) => { - originalEntry?.(...args) - const { name } = component - const componentOptions = getComponentSettings(name).options - Object.keys(componentOptions).forEach(key => { - if (key.startsWith('switch-')) { - addComponentListener( - `${name}.${key}`, - (value: boolean) => { - document.body.classList.toggle(`${name}-${key}`, value) - }, - true, - ) - } - }) - } - return component + Object.assign(options, optionsToExtend) + return options as SwitchOptionsMetadata + } +} + +/** + * 创建默认 Widget + */ +const newWidget = ( + options: SwitchWidgetOptions, +): Omit => ({ + component: () => import('./SwitchOptions.vue').then(m => m.default), + options, + }) + +/** + * 包装后的 entry 函数,函数参数类型中包含 API 注入的 `options` + */ +export type SwitchEntry< + O extends UnknownOptions, + N extends string, + S extends string, +> = ComponentEntry> + +/** + * 类似于 `ComponentMetadata`。 + * + * 定义该类型时,请使用 {@link defineIncompleteSwitchComponentMetadata} + * + * 不同之处在于,该类型在定义时,可在此 API 的相关 `options` 注入前, + * 让 `entry` 函数获取到完整类型提示。 + */ +export type IncompleteSwitchComponentMetadata< + O extends UnknownOptions, + N extends string, + S extends string, +> = { + [K in keyof ComponentMetadata]: K extends 'entry' + ? SwitchEntry + : ComponentMetadata[K] +} + +/** + * 辅助定义一个 {@link IncompleteSwitchComponentMetadata}, + * 类似 {@link ./define.ts defineComponentMetadata}。 + */ +export const defineIncompleteSwitchComponentMetadata = < + O extends UnknownOptions, + N extends string, + S extends string, +>( + m: IncompleteSwitchComponentMetadata, + ): IncompleteSwitchComponentMetadata => m + +/** + * 包装原始 entry 函数并返回 + */ +const newSwitchEntry = ( + component: ComponentMetadata | IncompleteSwitchComponentMetadata, +): SwitchEntry => (...args) => { + const result = component.entry(...args) + const componentOptions = getComponentSettings(component.name).options + Object.keys(componentOptions).forEach(key => { + if (key.startsWith('switch-')) { + addComponentListener( + `${component.name}.${key}`, + (value: boolean) => { + document.body.classList.toggle(`${component.name}-${key}`, value) + }, + true, + ) + } + }) + return result + } + +/** + * 被包装后的 `ComponentMetadata` + */ +export type SwitchComponentMetadata< + O extends UnknownOptions, + N extends string, + S extends string, +> = ComponentMetadata> + +/** + * 创建一个 builder + * + * builder 是一个函数,该函数接收一个 `ComponentMetadata`,为其添加 `options`。 + * 如果组件为未定义 Widget,还会提供一个默认的有相同效果的 Widget。 + * + * 定义 {@link SwitchMetadata} 请使用 {@link defineSwitchMetadata} + * + * 所注入的 options 参考 {@link SwitchOptions}。 + * Widget 的选项见 {@link SwitchWidgetOptions}。 + * + * 如果想在定义原始 `ComponentMetadata` 时提前获得被注入的 `options` 的类型提示, + * 可以使用 {@link defineIncompleteSwitchComponentMetadata} 函数辅助定义 + * + * 为了实现效果,builder 还会对 `entry` 进行包装, + * 因此包装后的 `entry` 与之前的不再是同一个函数。 + * + * 该函数会直接修改传入的 `ComponentMetadata` 并将其返回。 + * + */ +export const newSwitchComponentBuilder = ( + metadata: SwitchMetadata, +): (( + component: ComponentMetadata | IncompleteSwitchComponentMetadata, +) => SwitchComponentMetadata) => { + const extendOptions = newSwitchOptionsMetadataExtender(metadata.switches) + // 返回的 builder + return ( + component: ComponentMetadata | IncompleteSwitchComponentMetadata, + ) => { + const widgetOptions = newSwitchWidgetOptions( + metadata, + component.name, + `${component.displayName}选项`, + ) + + // 若没有 Widget,则注入一个 + if (!component.widget) { + component.widget = newWidget(widgetOptions) + } + // 扩展组件 options + extendOptions(component.options, widgetOptions) + // 包装 entry 函数 + component.entry = newSwitchEntry(component) + + return component as SwitchComponentMetadata } } From 295b9169dd45bbdba55743e3e44c63dd63e58657 Mon Sep 17 00:00:00 2001 From: timongh <46739861+timongh@users.noreply.github.com> Date: Sun, 2 Oct 2022 03:08:27 +0800 Subject: [PATCH 08/50] Add type annotation to i18n --- src/components/i18n/helpers.ts | 4 ++-- src/components/i18n/machine-translator/translators.ts | 5 +++-- src/components/i18n/types.ts | 4 ++++ 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/components/i18n/helpers.ts b/src/components/i18n/helpers.ts index 3748dae60..7bbd3f6b9 100644 --- a/src/components/i18n/helpers.ts +++ b/src/components/i18n/helpers.ts @@ -1,7 +1,7 @@ import { addData } from '@/plugins/data' import { getComponentSettings } from '@/core/settings' import { languageNameToCode } from '@/core/utils/i18n' -import { Translation, GeneralTranslation, RegexTranslation } from './types' +import { Translation, GeneralTranslation, RegexTranslation, Options as I18nOptions } from './types' /** * 在`plugin.setup`中可使用此帮助函数快速注入翻译数据 @@ -22,6 +22,6 @@ export const addI18nData = ( }) } export const getSelectedLanguage = () => { - const settings = getComponentSettings('i18n') + const settings = getComponentSettings('i18n') return languageNameToCode(settings.options.language) } diff --git a/src/components/i18n/machine-translator/translators.ts b/src/components/i18n/machine-translator/translators.ts index dafaf3e1b..e876096b8 100644 --- a/src/components/i18n/machine-translator/translators.ts +++ b/src/components/i18n/machine-translator/translators.ts @@ -4,6 +4,7 @@ import { getComponentSettings } from '@/core/settings' import { languageNameToCode } from '@/core/utils/i18n' import { registerAndGetData } from '@/plugins/data' import { formData } from '@/core/utils' +import { Options as I18nOptions } from '../types' export abstract class MachineTranslateProvider { abstract translate(text: string): Promise @@ -11,7 +12,7 @@ export abstract class MachineTranslateProvider { abstract link: string abstract defaultLanguage: string protected getTargetLanguage() { - const i18n = getComponentSettings('i18n') + const i18n = getComponentSettings('i18n') if (i18n.enabled) { return languageNameToCode(i18n.options.language) } @@ -109,7 +110,7 @@ export const [translateProviders] = registerAndGetData('i18n.machineTranslators' } as Record) export const translateProviderNames = Object.keys(translateProviders) export const getTranslator = (): MachineTranslateProvider => { - const { options: { translator } } = getComponentSettings('i18n') + const { options: { translator } } = getComponentSettings('i18n') const provider = translateProviders[translator] || translateProviders.GoogleCN return provider } diff --git a/src/components/i18n/types.ts b/src/components/i18n/types.ts index eb44b5e7e..4b99b0255 100644 --- a/src/components/i18n/types.ts +++ b/src/components/i18n/types.ts @@ -10,3 +10,7 @@ export type LanguagePack = { map?: [string, Translation][] regex?: RegexTranslation } +export type Options = { + language: string, + translator: string, +} From 1d653143ec37d6a216be00e51cc6e3b463f31cca Mon Sep 17 00:00:00 2001 From: timongh <46739861+timongh@users.noreply.github.com> Date: Sun, 2 Oct 2022 03:08:58 +0800 Subject: [PATCH 09/50] Add type annotation to imageExporter --- .../components/utils/image-exporter/feed.ts | 38 ++++++++++++------- .../components/utils/image-exporter/index.ts | 26 +++++++------ 2 files changed, 39 insertions(+), 25 deletions(-) diff --git a/registry/lib/components/utils/image-exporter/feed.ts b/registry/lib/components/utils/image-exporter/feed.ts index 954e56b78..e859f2ac5 100644 --- a/registry/lib/components/utils/image-exporter/feed.ts +++ b/registry/lib/components/utils/image-exporter/feed.ts @@ -6,8 +6,11 @@ import { Toast } from '@/core/toast' import { matchUrlPattern, retrieveImageUrl } from '@/core/utils' import { formatTitle } from '@/core/utils/title' import { feedsUrls } from '@/core/utils/urls' +import { Options } from '.' -export const setupFeedImageExporter: ComponentEntry = async ({ settings: { options } }) => { +export const setupFeedImageExporter: ComponentEntry = async ({ + settings: { options }, +}) => { if (!feedsUrls.some(url => matchUrlPattern(url))) { return } @@ -18,24 +21,28 @@ export const setupFeedImageExporter: ComponentEntry = async ({ settings: { optio text: '导出图片', action: async () => { const imageUrls: { url: string; extension: string }[] = [] - dqa(card.element, '.main-content .img-content, .bili-album__preview__picture__img').forEach((img: HTMLImageElement | HTMLDivElement) => { - const urlData = retrieveImageUrl(img) - if (urlData && !imageUrls.some(({ url }) => url === urlData.url)) { - imageUrls.push(urlData) - } - }) + dqa(card.element, '.main-content .img-content, .bili-album__preview__picture__img').forEach( + (img: HTMLImageElement | HTMLDivElement) => { + const urlData = retrieveImageUrl(img) + if (urlData && !imageUrls.some(({ url }) => url === urlData.url)) { + imageUrls.push(urlData) + } + }, + ) if (imageUrls.length === 0) { Toast.info('此条动态没有检测到任何图片.', '导出图片') return } const toast = Toast.info('下载中...', '导出图片') let downloadedCount = 0 - const imageBlobs = await Promise.all(imageUrls.map(async ({ url }) => { - const blob = await getBlob(url) - downloadedCount++ - toast.message = `下载中... (${downloadedCount}/${imageUrls.length})` - return blob - })) + const imageBlobs = await Promise.all( + imageUrls.map(async ({ url }) => { + const blob = await getBlob(url) + downloadedCount++ + toast.message = `下载中... (${downloadedCount}/${imageUrls.length})` + return blob + }), + ) const pack = new DownloadPackage() const { feedFormat } = options imageBlobs.forEach((blob, index) => { @@ -45,7 +52,10 @@ export const setupFeedImageExporter: ComponentEntry = async ({ settings: { optio originalUser: (card as RepostFeedsCard).repostUsername ?? card.username, n: (index + 1).toString(), } - pack.add(`${formatTitle(feedFormat, false, titleData)}${imageUrls[index].extension}`, blob) + pack.add( + `${formatTitle(feedFormat, false, titleData)}${imageUrls[index].extension}`, + blob, + ) }) toast.close() const packTitleData = { diff --git a/registry/lib/components/utils/image-exporter/index.ts b/registry/lib/components/utils/image-exporter/index.ts index 2f633822f..37900ff30 100644 --- a/registry/lib/components/utils/image-exporter/index.ts +++ b/registry/lib/components/utils/image-exporter/index.ts @@ -1,8 +1,21 @@ -import { defineComponentMetadata } from '@/components/define' +import { defineComponentMetadata, defineOptionsMetadata, OptionsOfMetadata } from '@/components/define' import { matchUrlPattern } from '@/core/utils' import { columnUrls, feedsUrls } from '@/core/utils/urls' import { setupFeedImageExporter } from './feed' +const options = defineOptionsMetadata({ + columnFormat: { + defaultValue: '[title][ - n]', + displayName: '专栏图片命名格式', + }, + feedFormat: { + defaultValue: '[user][ - id][ - n]', + displayName: '动态图片命名格式', + }, +}) + +export type Options = OptionsOfMetadata + export const component = defineComponentMetadata({ name: 'imageExporter', displayName: '图片批量导出', @@ -21,14 +34,5 @@ export const component = defineComponentMetadata({ ...feedsUrls, ...columnUrls, ], - options: { - columnFormat: { - defaultValue: '[title][ - n]', - displayName: '专栏图片命名格式', - }, - feedFormat: { - defaultValue: '[user][ - id][ - n]', - displayName: '动态图片命名格式', - }, - }, + options, }) From b1402a1853f94e03065363ea12441ecd19645301 Mon Sep 17 00:00:00 2001 From: timongh <46739861+timongh@users.noreply.github.com> Date: Sun, 2 Oct 2022 03:09:54 +0800 Subject: [PATCH 10/50] Remove unused import in extendVideoSpeed --- registry/lib/components/video/player/extend-speed/component.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/registry/lib/components/video/player/extend-speed/component.ts b/registry/lib/components/video/player/extend-speed/component.ts index af8c8e2f6..4f6c79e4d 100644 --- a/registry/lib/components/video/player/extend-speed/component.ts +++ b/registry/lib/components/video/player/extend-speed/component.ts @@ -1,4 +1,3 @@ -import { UnknownOptions } from '@/components/types' import { ComponentSettings, getComponentSettings } from '@/core/settings' import { addStyle } from '@/core/style' import { Toast } from '@/core/toast' From fae2455b4df898fd5a7814c702635804418ca439 Mon Sep 17 00:00:00 2001 From: timongh <46739861+timongh@users.noreply.github.com> Date: Sun, 2 Oct 2022 03:36:55 +0800 Subject: [PATCH 11/50] Change some components to define api --- .../lib/components/style/dark-mode/follow-system/index.ts | 6 +++--- registry/lib/docs/index.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/registry/lib/components/style/dark-mode/follow-system/index.ts b/registry/lib/components/style/dark-mode/follow-system/index.ts index c4b3634a1..7f6785d25 100644 --- a/registry/lib/components/style/dark-mode/follow-system/index.ts +++ b/registry/lib/components/style/dark-mode/follow-system/index.ts @@ -1,8 +1,8 @@ -import { ComponentMetadata } from '@/components/types' +import { defineComponentMetadata } from '@/components/define' import { LifeCycleEventTypes } from '@/core/life-cycle' import { darkExcludes } from '../dark-urls' -export const component: ComponentMetadata = { +export const component = defineComponentMetadata({ name: 'darkModeFollowSystem', displayName: '夜间模式跟随系统', entry: () => { @@ -33,4 +33,4 @@ export const component: ComponentMetadata = { > 注:在某些浏览器 (如 \`Microsoft Edge\`) 中,夜间模式仅会同步浏览器的亮 / 暗主题. `.trim(), }, -} +}) diff --git a/registry/lib/docs/index.ts b/registry/lib/docs/index.ts index 7f5ad0dbe..9eead2c47 100644 --- a/registry/lib/docs/index.ts +++ b/registry/lib/docs/index.ts @@ -1,4 +1,4 @@ -import { ComponentMetadata } from '@/components/types' +import { defineComponentMetadata } from '@/components/define' import { cdnRoots } from '@/core/cdn-types' import { branches } from '@/core/meta' import { getComponentsDoc } from './components-doc' @@ -90,7 +90,7 @@ ${getDocText(pluginsDoc.title, pluginsDoc.items)} } } } -export const doc: ComponentMetadata = { +export const doc = defineComponentMetadata({ name: 'featureDocsGenerator', displayName: '功能文档生成器', entry, @@ -99,4 +99,4 @@ export const doc: ComponentMetadata = { delete unsafeWindow.generateDocs }, tags: [componentsTags.utils], -} +}) From ab04bdb88d00e85ec2f3180ed2989566814db7b3 Mon Sep 17 00:00:00 2001 From: timongh <46739861+timongh@users.noreply.github.com> Date: Fri, 7 Oct 2022 11:04:03 +0800 Subject: [PATCH 12/50] Change the doc and some definitions of switch-options --- .../components/style/simplify/home/index.ts | 4 +- .../components/style/simplify/live/index.ts | 4 +- src/components/switch-options.ts | 196 ++++++++++-------- 3 files changed, 116 insertions(+), 88 deletions(-) diff --git a/registry/lib/components/style/simplify/home/index.ts b/registry/lib/components/style/simplify/home/index.ts index 90957df50..bb03bd9cc 100644 --- a/registry/lib/components/style/simplify/home/index.ts +++ b/registry/lib/components/style/simplify/home/index.ts @@ -1,5 +1,5 @@ import { - newSwitchComponentBuilder, + newSwitchComponentWrapper, defineSwitchMetadata, defineIncompleteSwitchComponentMetadata, } from '@/components/switch-options' @@ -175,4 +175,4 @@ const metadata = defineIncompleteSwitchComponentMetadata({ }, }) -export const component = newSwitchComponentBuilder(switchMetadata)(metadata) +export const component = newSwitchComponentWrapper(switchMetadata)(metadata) diff --git a/registry/lib/components/style/simplify/live/index.ts b/registry/lib/components/style/simplify/live/index.ts index b0d869534..e85e40216 100644 --- a/registry/lib/components/style/simplify/live/index.ts +++ b/registry/lib/components/style/simplify/live/index.ts @@ -1,8 +1,8 @@ -import { newSwitchComponentBuilder } from '@/components/switch-options' +import { newSwitchComponentWrapper } from '@/components/switch-options' import { styledComponentEntry } from '@/components/styled-component' import { liveUrls } from '@/core/utils/urls' -export const component = newSwitchComponentBuilder({ +export const component = newSwitchComponentWrapper({ name: 'simplifyOptions', dimAt: 'checked', switchProps: { diff --git a/src/components/switch-options.ts b/src/components/switch-options.ts index 1006987d5..ab03716cc 100644 --- a/src/components/switch-options.ts +++ b/src/components/switch-options.ts @@ -1,11 +1,11 @@ /** - * Switch Options - * @module components/switch-options + * Switch Options API + * @module src/components/switch-options * * 通过包装原始的 ComponentMetadata 为组件提供一系列开关选项。 * 如果组件未定义 Widget,还会提供一个默认的有相同效果的 Widget。 * - * 主要函数是 {@link newSwitchComponentBuilder}。 + * API 主要函数是 {@link newSwitchComponentWrapper}。 */ import { getComponentSettings, addComponentListener } from '@/core/settings' @@ -21,23 +21,23 @@ import { Widget } from './widget' /** * 单个开关的设置 */ -export interface SwitchItemMetadata { +export type SwitchItemMetadata = { + /** 开关的显示名称 */ displayName: string + /** 开关的默认开启状态 */ defaultValue: boolean } /** - * 一个对象,键是开关的名称,用于生成 options;值是关于单个开关的设置 + * 用于配置所有开关。 + * + * 该定义中的每个属性都会被用于生成一个组件 option。 + * 其中 option 的键为属性加上前缀 'switch-',值为一个 `boolean`,表示开关状态。 */ export type SwitchItemsMetadata = { [key in S]: SwitchItemMetadata } -/** - * 从 SwitchItemsMetadata 中提取所有 SwitchItem 的名称 - */ -export type SwitchItemNamesOfItemsMetadata> = keyof C - /** * 可用于单独定义 SwitchItemsMetadata */ @@ -46,20 +46,34 @@ export const defineSwitchItemsMetadata = ( ): SwitchItemsMetadata => c /** - * 用于配置 API 的行为 - * - * {@link SwitchItemsMetadata} 可用 {@link defineSwitchItemsMetadata} 单独定义 + * 用于配置 API 的行为。使用 {@link defineSwitchMetadata} 定义。 */ export interface SwitchMetadata { - // 注入 options 时会使用 + /** + * 作为键名注入到组件 `options` 中 + * + * @see {@link SwitchOptions} + */ name: N - // 每个开关的单独配置,其键名会被用于生成注入 options 的名称,见 {@link SwitchItemNames} + /** 每个开关的单独配置。可用 {@link defineSwitchItemsMetadata} 单独定义 */ switches: SwitchItemsMetadata - // 是否单选 default: false + /** + * 是否单选。值为 `undefined` 时取默认值。 + * + * @default false + */ radio?: undefined | boolean - // default: 'checked' + /** + * 控制开关变暗的时机 + * + * `undefined`: 始终不变暗 + * `'checked'`: 当开关关闭时变暗 + * `'notChecked'`: 始终为变暗状态 + * + * @default undefined + */ dimAt?: undefined | 'checked' | 'notChecked' - // 配置每个开关的图标 + /** 配置每个开关的图标 */ switchProps?: { checkedIcon?: string notCheckedIcon?: string @@ -67,17 +81,6 @@ export interface SwitchMetadata { } } -/** - * 从 SwitchMetadata 中提取 name 的字面量类型(如果可能) - */ -export type SwitchNameOfMetadata> = C['name'] - -/** - * 从 SwitchMetadata 中提取所有 SwitchItem 的名称 - */ -export type SwitchItemNamesOfMetadata> = - keyof C['switches'] - /** * 定义一个 {@link SwitchMetadata} */ @@ -86,52 +89,65 @@ export const defineSwitchMetadata = ( ): SwitchMetadata => c /** - * 传递给 Widget 的选项。同时也是注入组件的 options 的一部分 + * 注入组件的 options 的一部分。 + * + * 接口中的属性值大部分来自于 {@link SwitchMetadata} 中的定义。 + * 用户在 `SwitchMetadata` 中未定义的,为其默认值。 + * + * @see {@link SwitchOptions} */ -export interface SwitchWidgetOptions { +export interface SwitchMetadataOption { name: N switches: SwitchItemsMetadata radio: boolean - dimAt: 'checked' | 'notChecked' + dimAt: undefined | 'checked' | 'notChecked' switchProps?: { checkedIcon?: string notCheckedIcon?: string iconPosition?: 'left' | 'right' } + /** 组件名称 */ componentName: string + /** 组件的 Widget 显示名称 */ optionDisplayName: string } /** - * 创建一个 SwitchWidgetOptions + * 创建一个 SwitchMetadataOption */ -const newSwitchWidgetOptions = ( +const newSwitchMetadataOption = ( metadata: SwitchMetadata, componentName: string, optionDisplayName, ) => ({ ...metadata, radio: metadata.radio === undefined ? false : metadata.radio, - dimAt: metadata.dimAt === undefined ? 'checked' : metadata.dimAt, + dimAt: metadata.dimAt, componentName, optionDisplayName, }) /** - * 注入的 options 的名称集合。规则是 SwitchItemsMetadata 的所有键加上 'switch-' 前缀。 + * 为类型参数 `S` 加上 'switch-' 前缀。 */ export type SwitchItemNames = `switch-${S}` /** * API 注入的 options 中的一部分 */ -export type SwitchItemOptions = Record, string> +export type SwitchItemOptions = Record, boolean> /** - * 在原始的 Options 类型中添加了该 API 所需的定义后形成的类型 + * 包装完成后的组件元数据中的 options 类型 + * + * 由三部分组成: + * 1. 组件元数据被包装前的所有 options + * 2. 一个键值对:键为 {@link SwitchMetadata} 定义时的 `name` 属性,值为 {@link SwitchMetadataOption} 类型 + * 3. 对应 {@link SwitchItemsMetadata} 的定义内容 + * (来自于 `SwitchMetadata` 的 `switches`)。详情见该类型描述 */ export type SwitchOptions = O & { - N: SwitchWidgetOptions + N: SwitchMetadataOption } & SwitchItemOptions /** @@ -151,7 +167,7 @@ export type SwitchOptionsOfMetadata< > = SwitchOptionsOfSwitchMetadata, C> /** - * 在原始的 OptionsMetadata 中添加了该 API 所需的定义后形成的类型 + * 在原始的 OptionsMetadata 中添加了该 API 注入的 options 后形成的类型 */ export type SwitchOptionsMetadata< O extends UnknownOptions, @@ -160,18 +176,21 @@ export type SwitchOptionsMetadata< > = OptionsMetadata> /** - * 创建一个 extender。 + * 向传入的 `options` 注入 API 所需的内容 * - * extender 是一个函数,其作用是为传入的 OptionsMetadata 添加新字段。 - * - * 该函数会直接修改传入的 OptionsMetadata,并将其返回。 + * 该函数会直接修改传入的 `options` 自身,并将其返回。 + */ +type SwitchOptionsMetadataExtender = ( + options: OptionsMetadata, + switchMetadataOption: SwitchMetadataOption, +) => SwitchOptionsMetadata + +/** + * 创建一个 {@link SwitchOptionsMetadataExtender} */ const newSwitchOptionsMetadataExtender = ( itemsMetadata: SwitchItemsMetadata, -): (( - options: OptionsMetadata, - widgetOptions: SwitchWidgetOptions, -) => SwitchOptionsMetadata) => { +): SwitchOptionsMetadataExtender => { const optionsToExtend = {} const entries = Object.entries(itemsMetadata) for (const [key, { displayName, defaultValue }] of entries) { @@ -183,11 +202,11 @@ const newSwitchOptionsMetadataExtender = ( } return ( options: OptionsMetadata, - widgetOptions: SwitchWidgetOptions, + switchMetadataOption: SwitchMetadataOption, ) => { - optionsToExtend[widgetOptions.name as string] = { + optionsToExtend[switchMetadataOption.name as string] = { defaultValue: options, - displayName: widgetOptions.optionDisplayName, + displayName: switchMetadataOption.optionDisplayName, } Object.assign(options, optionsToExtend) return options as SwitchOptionsMetadata @@ -198,28 +217,28 @@ const newSwitchOptionsMetadataExtender = ( * 创建默认 Widget */ const newWidget = ( - options: SwitchWidgetOptions, + options: SwitchMetadataOption, ): Omit => ({ component: () => import('./SwitchOptions.vue').then(m => m.default), options, }) /** - * 包装后的 entry 函数,函数参数类型中包含 API 注入的 `options` + * 携带 API 注入的 `options` 的 `entry` 函数类型 */ export type SwitchEntry< O extends UnknownOptions, N extends string, S extends string, -> = ComponentEntry> + T = unknown, +> = ComponentEntry, T> /** - * 类似于 `ComponentMetadata`。 + * 用于被 API 包装的组件原数据,类似于 `ComponentMetadata`。 * - * 定义该类型时,请使用 {@link defineIncompleteSwitchComponentMetadata} + * 定义该类型时,请使用 {@link defineIncompleteSwitchComponentMetadata}。 * - * 不同之处在于,该类型在定义时,可在此 API 的相关 `options` 注入前, - * 让 `entry` 函数获取到完整类型提示。 + * 在通过该函数定义组件的 `entry` 时,可以获得被 API 注入的 options 的类型提示。 */ export type IncompleteSwitchComponentMetadata< O extends UnknownOptions, @@ -232,8 +251,10 @@ export type IncompleteSwitchComponentMetadata< } /** - * 辅助定义一个 {@link IncompleteSwitchComponentMetadata}, - * 类似 {@link ./define.ts defineComponentMetadata}。 + * 定义一个用于被该 API 包装的组件元数据, + * 类似于 {@link import("./define").defineComponentMetadata}。 + * + * 与直接定义的不同之处在于:定义 `entry` 时,可以获得被 API 注入的 options 的类型提示。 */ export const defineIncompleteSwitchComponentMetadata = < O extends UnknownOptions, @@ -244,7 +265,7 @@ export const defineIncompleteSwitchComponentMetadata = < ): IncompleteSwitchComponentMetadata => m /** - * 包装原始 entry 函数并返回 + * 包装原始 `entry` 函数并返回 */ const newSwitchEntry = ( component: ComponentMetadata | IncompleteSwitchComponentMetadata, @@ -266,7 +287,7 @@ const newSwitchEntry = = ComponentMetadata> /** - * 创建一个 builder + * 用于包装组件元数据,生成开关选项。 * - * builder 是一个函数,该函数接收一个 `ComponentMetadata`,为其添加 `options`。 - * 如果组件为未定义 Widget,还会提供一个默认的有相同效果的 Widget。 + * 传入参数可以是普通的组件元数据,也可以是 {@link IncompleteSwitchComponentMetadata}。 + * 后者应使用 {@link defineIncompleteSwitchComponentMetadata} 定义; + * 在通过该函数定义组件的 `entry` 时,可以获得被 API 注入的 options 的类型提示。 * - * 定义 {@link SwitchMetadata} 请使用 {@link defineSwitchMetadata} + * 为保障组件功能的正常运行,请勿使用以 'switch-' 开头的 option。 * - * 所注入的 options 参考 {@link SwitchOptions}。 - * Widget 的选项见 {@link SwitchWidgetOptions}。 + * 若传入组件未定义 Widget,则会注入一个用于控制各开关的 Widget。 + * 此 Widget 的功能与注入组件的 options 功能相同。 * - * 如果想在定义原始 `ComponentMetadata` 时提前获得被注入的 `options` 的类型提示, - * 可以使用 {@link defineIncompleteSwitchComponentMetadata} 函数辅助定义 + * 被包装后的元数据,其 `entry` 函数是原函数的包装,用于实现相关功能。 * - * 为了实现效果,builder 还会对 `entry` 进行包装, - * 因此包装后的 `entry` 与之前的不再是同一个函数。 - * - * 该函数会直接修改传入的 `ComponentMetadata` 并将其返回。 + * 该函数会直接修改传入参数自身,并将其返回。 * + * @param component - 被包装的组件元数据 + * @returns 包装完成的组件元数据 */ -export const newSwitchComponentBuilder = ( - metadata: SwitchMetadata, -): (( +export type SwitchComponentWrapper = ( component: ComponentMetadata | IncompleteSwitchComponentMetadata, -) => SwitchComponentMetadata) => { +) => SwitchComponentMetadata + +/** + * 创建一个 {@link SwitchComponentWrapper} + * + * 传入参数请使用 {@link defineSwitchMetadata} 定义。 + * + * @param metadata - 相关配置元数据 + * @returns 组件包装器 + */ +export const newSwitchComponentWrapper = ( + metadata: SwitchMetadata, +): SwitchComponentWrapper => { const extendOptions = newSwitchOptionsMetadataExtender(metadata.switches) - // 返回的 builder + // 返回的 wrapper return ( component: ComponentMetadata | IncompleteSwitchComponentMetadata, ) => { - const widgetOptions = newSwitchWidgetOptions( + const switchMetadataOption = newSwitchMetadataOption( metadata, component.name, `${component.displayName}选项`, ) - // 若没有 Widget,则注入一个 if (!component.widget) { - component.widget = newWidget(widgetOptions) + component.widget = newWidget(switchMetadataOption) } // 扩展组件 options - extendOptions(component.options, widgetOptions) + extendOptions(component.options, switchMetadataOption) // 包装 entry 函数 component.entry = newSwitchEntry(component) - return component as SwitchComponentMetadata } } From 9bf584f435d3d1a67fa0e3f60b5bd83270767b35 Mon Sep 17 00:00:00 2001 From: Mike Lei Date: Mon, 5 Dec 2022 23:17:39 +0000 Subject: [PATCH 13/50] fix(fold-comments): use smooth scrolling when folding comments --- registry/lib/components/feeds/fold-comments/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/registry/lib/components/feeds/fold-comments/index.ts b/registry/lib/components/feeds/fold-comments/index.ts index e75971bd5..2b845e510 100644 --- a/registry/lib/components/feeds/fold-comments/index.ts +++ b/registry/lib/components/feeds/fold-comments/index.ts @@ -24,7 +24,7 @@ const entry = async () => { button.innerHTML = '收起评论' button.addEventListener('click', () => { clickHandler() - card.scrollIntoView() + card.scrollIntoView({ behavior: 'smooth' }) }) commentBox.insertAdjacentElement('beforeend', button) } From c658cd67e59fe6b7c454a6e69b3e10eb33465d44 Mon Sep 17 00:00:00 2001 From: the1812 Date: Sat, 10 Dec 2022 21:04:31 +0800 Subject: [PATCH 14/50] Add rollback doc --- README.md | 2 ++ doc/rollback.md | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 doc/rollback.md diff --git a/README.md b/README.md index b0234f047..eb8344af8 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,8 @@ | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | | [安装](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master-cdn/dist/bilibili-evolved.user.js) | [安装](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/dist/bilibili-evolved.user.js) | [安装](https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/dist/bilibili-evolved.preview.user.js) | +> 如果处于某种原因只能停在旧版, 可以参考[旧版本回退指南](./doc/rollback.md). + # 设置 脚本启用后, 在网页左侧中央会有功能面板和设置面板的入口. 功能面板中包含适用于当前页面的一些功能入口, 设置面板中可以管理组件的开启/关闭, 修改组件选项, 以及安装/卸载组件和插件等. diff --git a/doc/rollback.md b/doc/rollback.md new file mode 100644 index 000000000..6f15fabb8 --- /dev/null +++ b/doc/rollback.md @@ -0,0 +1,21 @@ +# 旧版本回退指南 + +> ⚠警告: 使用旧版本产生的不兼容 / 功能失效等问题需要自行承担, 本仓库只受理最新版脚本的反馈. + +在 [Release](https://github.com/the1812/Bilibili-Evolved/releases) 中找到要回退的版本, 并记住发布的时间. + +前往 https://github.com/the1812/Bilibili-Evolved/commits/master/dist/bilibili-evolved.user.js, 找到相同日期中的第一个 `CI Build`, 并点击查看详情. + +![image](https://user-images.githubusercontent.com/26504152/206855808-27c0d501-0c98-4f9c-be30-53ad0db715ab.png) + +在详情页中右键 `Raw`, 选择 `Save link as...` (链接另存为), 会下载脚本的内容. + +![image](https://user-images.githubusercontent.com/26504152/206855884-4a704c4c-4020-47fc-aa04-0f855d73b19b.png) + +将脚本内容粘贴覆盖到油猴中, 并删掉这两行检查更新用的配置. + +![image](https://user-images.githubusercontent.com/26504152/206856196-fbde3ef9-eddf-4b3f-a12f-12547f2f0494.png) + +保存后, 进入 b 站, 从脚本设置中关闭 `自动更新器`. + +![image](https://user-images.githubusercontent.com/26504152/206856445-ce5ebeb1-7d58-4602-8c3b-c506efdd5f32.png) From 9dc2ad58f47d804578f343cad813204c5dfc6b6b Mon Sep 17 00:00:00 2001 From: the1812 Date: Sat, 10 Dec 2022 22:10:56 +0800 Subject: [PATCH 15/50] Refactor navbar history filters (fix #3676, #2729) --- .../style/custom-navbar/CustomNavbarItem.vue | 4 +- .../favorites/NavbarFavorites.vue | 2 +- .../custom-navbar/history/NavbarHistory.vue | 81 ++++++++++++------- .../style/custom-navbar/history/history.ts | 2 +- .../style/custom-navbar/history/types.ts | 34 ++++++-- .../custom-navbar/settings/NavbarSettings.vue | 2 +- .../subscriptions/SubscriptionsList.vue | 2 +- .../custom-navbar/user-info/UserInfoPopup.vue | 6 +- .../watchlater/NavbarWatchlater.vue | 2 +- src/ui/CheckBox.vue | 2 +- 10 files changed, 90 insertions(+), 47 deletions(-) diff --git a/registry/lib/components/style/custom-navbar/CustomNavbarItem.vue b/registry/lib/components/style/custom-navbar/CustomNavbarItem.vue index d61166464..6bb5f51c0 100644 --- a/registry/lib/components/style/custom-navbar/CustomNavbarItem.vue +++ b/registry/lib/components/style/custom-navbar/CustomNavbarItem.vue @@ -155,6 +155,8 @@ export default Vue.extend({ From 172efb861b51c29e5702d21d77ce759b6e6b6c01 Mon Sep 17 00:00:00 2001 From: aiden Date: Mon, 19 Dec 2022 00:56:22 +0800 Subject: [PATCH 25/50] fix --- .../components/utils/import-series/index.ts | 1 - .../components/utils/import-series/logic.ts | 75 +++++++------------ 2 files changed, 29 insertions(+), 47 deletions(-) diff --git a/registry/lib/components/utils/import-series/index.ts b/registry/lib/components/utils/import-series/index.ts index 9c08291f3..bb0307668 100644 --- a/registry/lib/components/utils/import-series/index.ts +++ b/registry/lib/components/utils/import-series/index.ts @@ -10,7 +10,6 @@ export const component = defineComponentMetadata({ displayName: '批量导入合集', tags: [componentsTags.utils], entry: realEntry, - enabledByDefault: true, description: { 'zh-CN': '在合集页面制作一个批量导入按钮,可以新增所有合集内视频至同名收藏夹', }, diff --git a/registry/lib/components/utils/import-series/logic.ts b/registry/lib/components/utils/import-series/logic.ts index 94fb4b846..cbf9c6456 100644 --- a/registry/lib/components/utils/import-series/logic.ts +++ b/registry/lib/components/utils/import-series/logic.ts @@ -1,46 +1,29 @@ -import progress from './progress.vue' -import { mountVueComponent } from '@/core/utils' +import Progress from './Progress.vue' +import { mountVueComponent, getCsrf, delay } from '@/core/utils' +import { sq } from '@/core/spin-query' export const realEntry = async () => { - // 模仿python中的time.sleep - const sleep = timeout => - new Promise(resolve => { - setTimeout(resolve, timeout) - }) - const url = new URL(window.location.href) // 合集id const sid = url.searchParams.get('sid') // 合集作者 const uid = url.pathname.split('/')[1] // 合集名 - let series_name = '' - while (true) { - series_name = document.getElementsByClassName('item cur')[0].innerHTML - if (series_name !== '') { - break - } else { - await sleep(500) - } - } + let seriesName = '' + seriesName = await sq( + () => document.getElementsByClassName('item cur')[0].innerHTML, + t => t !== '', + { + queryInterval: 500, + }, + ) - const get_cookie = name => { - const cookies = document.cookie.split(';') - for (let i = 0; i < cookies.length; i++) { - const cookie = cookies[i].split('=') - cookie[0] = cookie[0].trim() - if (cookie[0] === name) { - return cookie[1] - } - } - return '' - } // 当前登录用户 我 的验证信息 - const csrf = get_cookie('bili_jct') + const csrf = getCsrf() // add a button - const page_head = document.getElementsByClassName('page-head')[0] - const rel = page_head.children[1] + const pageHead = document.getElementsByClassName('page-head')[0] + const rel = pageHead.children[1] const button = document.createElement('a') // 什么离谱class名,play打错了可还行 button.className = 'paly-all-btn' @@ -51,17 +34,17 @@ export const realEntry = async () => { text-align: center; width: 65px; ` - page_head.insertBefore(button, rel) + pageHead.insertBefore(button, rel) button.onclick = async () => { // 获取合集所有视频 - const series_videos = ( + const seriesVideos = ( await fetch( `https://api.bilibili.com/x/series/archives?mid=${uid}&series_id=${sid}&only_normal=true&sort=desc&pn=1&ps=99999`, ).then(r => r.json()) ).data.archives - let fav_id = 0 + let favId = 0 // 创建收藏夹,获取新收藏夹id while (true) { const response = await fetch('https://api.bilibili.com/x/v3/fav/folder/add', { @@ -70,40 +53,40 @@ export const realEntry = async () => { headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, - body: `title=${series_name}&csrf=${csrf}&privacy=0`, + body: `title=${seriesName}&csrf=${csrf}&privacy=0`, }).then(r => r.json()) if (response.code === 0) { - fav_id = response.data.id + favId = response.data.id break } else { - await sleep(2000) + await delay(2000) } } // 挂载弹出框 - const mount_point = document.createElement('div') - document.getElementsByTagName('body')[0].appendChild(mount_point) - type progressSetting = Vue & { + const mountPoint = document.createElement('div') + document.getElementsByTagName('body')[0].appendChild(mountPoint) + type ProgressSetting = Vue & { isDisabled: boolean all: number handled: number } - const p: progressSetting = mountVueComponent(progress, mount_point) - p.all = series_videos.length + const p: ProgressSetting = mountVueComponent(Progress, mountPoint) + p.all = seriesVideos.length - for (let i = 0; i < series_videos.length; i++) { + for (let i = 0; i < seriesVideos.length; i++) { // 添加每一个视频至收藏夹 while (true) { // 做个延迟,防止太快而遭服务器拒绝 - await sleep(500) + await delay(500) const response = await fetch('https://api.bilibili.com/x/v3/fav/resource/deal', { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, - body: `rid=${series_videos[i].aid}&add_media_ids=${fav_id}&type=2&csrf=${csrf}`, + body: `rid=${seriesVideos[i].aid}&add_media_ids=${favId}&type=2&csrf=${csrf}`, }).then(r => r.json()) if (response.code === 0) { @@ -112,7 +95,7 @@ export const realEntry = async () => { break } else { // 如果请求失败,等待2s后重试 - await sleep(2000) + await delay(2000) } } } From 9c75c40a52f9144d21d543e215c9a5829445c727 Mon Sep 17 00:00:00 2001 From: aiden Date: Mon, 19 Dec 2022 14:48:36 +0800 Subject: [PATCH 26/50] add support for collection, add transition, fix bugs --- .../components/utils/import-series/index.ts | 2 +- .../components/utils/import-series/logic.ts | 206 +++++++++++------- .../utils/import-series/progress.vue | 37 +++- 3 files changed, 158 insertions(+), 87 deletions(-) diff --git a/registry/lib/components/utils/import-series/index.ts b/registry/lib/components/utils/import-series/index.ts index bb0307668..2b106a043 100644 --- a/registry/lib/components/utils/import-series/index.ts +++ b/registry/lib/components/utils/import-series/index.ts @@ -13,5 +13,5 @@ export const component = defineComponentMetadata({ description: { 'zh-CN': '在合集页面制作一个批量导入按钮,可以新增所有合集内视频至同名收藏夹', }, - urlInclude: [/^https:\/\/space\.bilibili\.com\/\d+\/channel\/seriesdetail/], + urlInclude: [/^https:\/\/space\.bilibili\.com\//], }) diff --git a/registry/lib/components/utils/import-series/logic.ts b/registry/lib/components/utils/import-series/logic.ts index cbf9c6456..694242946 100644 --- a/registry/lib/components/utils/import-series/logic.ts +++ b/registry/lib/components/utils/import-series/logic.ts @@ -1,23 +1,132 @@ import Progress from './Progress.vue' import { mountVueComponent, getCsrf, delay } from '@/core/utils' import { sq } from '@/core/spin-query' +import { urlChange } from '@/core/observer' +import { Toast } from '@/core/toast' + +const addSeries = async (sid, uid, csrf) => { + // 获取合集所有视频 + const seriesVideos = ( + await fetch( + `https://api.bilibili.com/x/series/archives?mid=${uid}&series_id=${sid}&only_normal=true&sort=desc&pn=1&ps=99999`, + ).then(r => r.json()) + ).data.archives -export const realEntry = async () => { - const url = new URL(window.location.href) - // 合集id - const sid = url.searchParams.get('sid') - // 合集作者 - const uid = url.pathname.split('/')[1] // 合集名 - let seriesName = '' - seriesName = await sq( + const seriesName = await sq( () => document.getElementsByClassName('item cur')[0].innerHTML, t => t !== '', - { - queryInterval: 500, - }, ) + // 挂载弹出框 + const mountPoint = document.createElement('div') + document.getElementsByTagName('body')[0].appendChild(mountPoint) + type ProgressSetting = Vue & { + isDisabled: boolean + all: number + handled: number + } + const p: ProgressSetting = mountVueComponent(Progress, mountPoint) + p.all = seriesVideos.length + + // 创建收藏夹,获取新收藏夹id + let favId = 0 + while (true) { + const response = await fetch('https://api.bilibili.com/x/v3/fav/folder/add', { + method: 'POST', + credentials: 'include', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: `title=${seriesName}&csrf=${csrf}&privacy=0`, + }).then(r => r.json()) + + if (response.code === 0) { + favId = response.data.id + break + } else { + await delay(2000) + } + } + + // 添加每一个视频至收藏夹 + for (let i = 0; i < seriesVideos.length; i++) { + // 做个延迟,防止太快而遭服务器拒绝 + await delay(500) + while (true) { + const response = await fetch('https://api.bilibili.com/x/v3/fav/resource/deal', { + method: 'POST', + credentials: 'include', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: `rid=${seriesVideos[i].aid}&add_media_ids=${favId}&type=2&csrf=${csrf}`, + }).then(r => r.json()) + + if (response.code === 0) { + // 如果请求成功,更新progress组件 + p.handled = i + 1 + break + } else { + // 如果请求失败,等待2s后重试 + await delay(2000) + } + } + } + + // 添加收藏夹任务完成,更改按钮为可点击 + p.isDisabled = false +} + +const addCollection = async (sid, csrf) => { + while (true) { + const response = await fetch('https://api.bilibili.com/x/v3/fav/season/fav', { + method: 'POST', + credentials: 'include', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: `platform=web&season_id=${sid}&csrf=${csrf}`, + }).then(r => r.json()) + + if (response.code === 0) { + Toast.success('成功', '导入合集', 2000) + break + } else { + await delay(2000) + } + } +} + +const addButton = () => { + enum PageType { + Collection, + Series, + } + + const url = new URL(window.location.href) + let pageType: PageType + + // series 或 collection 的 id + let sid + if (url.pathname.includes('seriesdetail')) { + // series id + console.log('aiden: series') + sid = url.searchParams.get('sid') + pageType = PageType.Series + } else if (url.pathname.includes('collectiondetail')) { + // collection id + console.log('aiden: collection') + sid = url.searchParams.get('sid') + pageType = PageType.Collection + } else { + console.log('aiden: none') + return + } + + // 合集作者 + const uid = url.pathname.split('/')[1] + // 当前登录用户 我 的验证信息 const csrf = getCsrf() @@ -33,73 +142,20 @@ export const realEntry = async () => { display: block; text-align: center; width: 65px; - ` + ` pageHead.insertBefore(button, rel) button.onclick = async () => { - // 获取合集所有视频 - const seriesVideos = ( - await fetch( - `https://api.bilibili.com/x/series/archives?mid=${uid}&series_id=${sid}&only_normal=true&sort=desc&pn=1&ps=99999`, - ).then(r => r.json()) - ).data.archives - - let favId = 0 - // 创建收藏夹,获取新收藏夹id - while (true) { - const response = await fetch('https://api.bilibili.com/x/v3/fav/folder/add', { - method: 'POST', - credentials: 'include', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - body: `title=${seriesName}&csrf=${csrf}&privacy=0`, - }).then(r => r.json()) - - if (response.code === 0) { - favId = response.data.id - break - } else { - await delay(2000) - } + if (pageType === PageType.Series) { + await addSeries(sid, uid, csrf) + } else { + await addCollection(sid, csrf) } - - // 挂载弹出框 - const mountPoint = document.createElement('div') - document.getElementsByTagName('body')[0].appendChild(mountPoint) - type ProgressSetting = Vue & { - isDisabled: boolean - all: number - handled: number - } - const p: ProgressSetting = mountVueComponent(Progress, mountPoint) - p.all = seriesVideos.length - - for (let i = 0; i < seriesVideos.length; i++) { - // 添加每一个视频至收藏夹 - while (true) { - // 做个延迟,防止太快而遭服务器拒绝 - await delay(500) - const response = await fetch('https://api.bilibili.com/x/v3/fav/resource/deal', { - method: 'POST', - credentials: 'include', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - body: `rid=${seriesVideos[i].aid}&add_media_ids=${favId}&type=2&csrf=${csrf}`, - }).then(r => r.json()) - - if (response.code === 0) { - // 如果请求成功,更新progress组件 - p.handled = i + 1 - break - } else { - // 如果请求失败,等待2s后重试 - await delay(2000) - } - } - } - // 添加收藏夹任务完成,更改按钮为可点击 - p.isDisabled = false } } + +export const realEntry = async () => { + urlChange(() => { + addButton() + }) +} diff --git a/registry/lib/components/utils/import-series/progress.vue b/registry/lib/components/utils/import-series/progress.vue index eda806d6c..01c216f23 100644 --- a/registry/lib/components/utils/import-series/progress.vue +++ b/registry/lib/components/utils/import-series/progress.vue @@ -1,11 +1,9 @@