mirror of
https://github.com/the1812/Bilibili-Evolved.git
synced 2025-11-04 21:22:45 +08:00
Merge pull request #3715 from timongh/tools-to-define-api
define API 收尾工作完成
This commit is contained in:
commit
df95d9dfe7
@ -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(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@ -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<string>) => {
|
||||
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<typeof options>
|
||||
|
||||
const checkTime = (settings: ComponentSettings<Options>) => {
|
||||
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<string>) => {
|
||||
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,
|
||||
})
|
||||
|
||||
@ -1,12 +1,16 @@
|
||||
import { createSwitchOptions, SwitchOptions } from '@/components/switch-options'
|
||||
import { ComponentMetadata } from '@/components/types'
|
||||
import {
|
||||
newSwitchComponentWrapper,
|
||||
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<string, unknown> = {}
|
||||
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 = newSwitchComponentWrapper(switchMetadata)(metadata)
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { createSwitchOptions } 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 = createSwitchOptions({
|
||||
export const component = newSwitchComponentWrapper({
|
||||
name: 'simplifyOptions',
|
||||
dimAt: 'checked',
|
||||
switchProps: {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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<Options> = 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 = {
|
||||
|
||||
@ -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<typeof options>
|
||||
|
||||
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,
|
||||
})
|
||||
|
||||
@ -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<typeof options>
|
||||
|
||||
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,
|
||||
})
|
||||
|
||||
@ -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<Options>(() => import('./fix.scss'),
|
||||
async ({ settings }) => {
|
||||
const { allMutations } = await import('@/core/observer')
|
||||
const dpi = settings.options.scale === 'auto'
|
||||
|
||||
@ -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<string, KeyBindingAction> = {
|
||||
},
|
||||
longJumpBackward: {
|
||||
displayName: '长倒退',
|
||||
run: () => playerAgent.changeTime(-(getComponentSettings('keymap').options.longJumpSeconds)),
|
||||
run: () => playerAgent.changeTime(-(getComponentSettings<Options>('keymap').options.longJumpSeconds)),
|
||||
},
|
||||
longJumpForward: {
|
||||
displayName: '长前进',
|
||||
run: () => playerAgent.changeTime(getComponentSettings('keymap').options.longJumpSeconds),
|
||||
run: () => playerAgent.changeTime(getComponentSettings<Options>('keymap').options.longJumpSeconds),
|
||||
},
|
||||
jumpBackward: {
|
||||
displayName: '倒退',
|
||||
|
||||
@ -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<string, string>) => (
|
||||
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<string, string>,
|
||||
displayName: '自定义键位',
|
||||
hidden: true,
|
||||
},
|
||||
preset: {
|
||||
defaultValue: 'Default',
|
||||
displayName: '预设',
|
||||
hidden: true,
|
||||
},
|
||||
})
|
||||
export type Options = OptionsOfMetadata<typeof options>
|
||||
let config: KeyBindingConfig = null
|
||||
const parseBindings = (bindings: Record<string, string>): 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<Options>(
|
||||
() => 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()
|
||||
},
|
||||
},
|
||||
}],
|
||||
],
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
@ -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<ComponentEntry>[0]
|
||||
|
||||
export type OptionSubjects<O> = O & { [K in keyof O as `${Exclude<K, symbol>}$`]: Subject<O[K]> }
|
||||
|
||||
export class EntrySpeedComponent<O = Record<string, unknown>>
|
||||
export class EntrySpeedComponent<O extends UnknownOptions = UnknownOptions>
|
||||
implements EntryContext {
|
||||
static create: <
|
||||
OO extends Record<string, any> = 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<Record<keyof EntrySpeedComponent, keyof SpeedContext | string>> = {
|
||||
|
||||
@ -28,7 +28,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
|
||||
/** 隐藏进度条 */
|
||||
|
||||
@ -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
|
||||
/** 固定全局倍速 */
|
||||
|
||||
@ -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],
|
||||
}
|
||||
})
|
||||
|
||||
@ -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',
|
||||
)
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
@ -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<Options>(name).options.maxUpdateCount,
|
||||
})
|
||||
export const silentCheckUpdateAndReload = reload(silentCheckUpdate)
|
||||
|
||||
|
||||
@ -62,7 +62,7 @@ const optionsMetadata = defineOptionsMetadata({
|
||||
},
|
||||
})
|
||||
|
||||
type Options = OptionsOfMetadata<typeof optionsMetadata>
|
||||
export type Options = OptionsOfMetadata<typeof optionsMetadata>
|
||||
|
||||
const entry: ComponentEntry<Options> = async ({
|
||||
settings: { options: opt },
|
||||
|
||||
@ -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<I18nOptions>('i18n')
|
||||
return languageNameToCode(settings.options.language)
|
||||
}
|
||||
|
||||
@ -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<string>
|
||||
@ -11,7 +12,7 @@ export abstract class MachineTranslateProvider {
|
||||
abstract link: string
|
||||
abstract defaultLanguage: string
|
||||
protected getTargetLanguage() {
|
||||
const i18n = getComponentSettings('i18n')
|
||||
const i18n = getComponentSettings<I18nOptions>('i18n')
|
||||
if (i18n.enabled) {
|
||||
return languageNameToCode(i18n.options.language)
|
||||
}
|
||||
@ -109,7 +110,7 @@ export const [translateProviders] = registerAndGetData('i18n.machineTranslators'
|
||||
} as Record<string, MachineTranslateProvider>)
|
||||
export const translateProviderNames = Object.keys(translateProviders)
|
||||
export const getTranslator = (): MachineTranslateProvider => {
|
||||
const { options: { translator } } = getComponentSettings('i18n')
|
||||
const { options: { translator } } = getComponentSettings<I18nOptions>('i18n')
|
||||
const provider = translateProviders[translator] || translateProviders.GoogleCN
|
||||
return provider
|
||||
}
|
||||
|
||||
@ -10,3 +10,7 @@ export type LanguagePack = {
|
||||
map?: [string, Translation][]
|
||||
regex?: RegexTranslation
|
||||
}
|
||||
export type Options = {
|
||||
language: string,
|
||||
translator: string,
|
||||
}
|
||||
|
||||
@ -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 = <O extends UnknownOptions>(
|
||||
styleImport: () => Promise<{ default: string }>,
|
||||
entry: ComponentEntry,
|
||||
): ComponentEntry => (
|
||||
async context => {
|
||||
entry: ComponentEntry<O>,
|
||||
): ComponentEntry<O> => 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 = <O extends UnknownOptions>(
|
||||
name: string,
|
||||
styleImport: () => Promise<{ default: string }>,
|
||||
entry: ComponentEntry = none,
|
||||
): Pick<ComponentMetadata, 'name' | 'entry' | 'reload' | 'unload'> => {
|
||||
entry: ComponentEntry<O> = none,
|
||||
): Pick<ComponentMetadata<O>, '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()
|
||||
|
||||
@ -1,71 +1,352 @@
|
||||
import { getComponentSettings, addComponentListener } from '@/core/settings'
|
||||
import { ComponentMetadata, ComponentOptions } from './component'
|
||||
/**
|
||||
* Switch Options API
|
||||
* @module src/components/switch-options
|
||||
*
|
||||
* 通过包装原始的 ComponentMetadata 为组件提供一系列开关选项。
|
||||
* 如果组件未定义 Widget,还会提供一个默认的有相同效果的 Widget。
|
||||
*
|
||||
* API 主要函数是 {@link newSwitchComponentWrapper}。
|
||||
*/
|
||||
|
||||
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 type SwitchItemMetadata = {
|
||||
/** 开关的显示名称 */
|
||||
displayName: string
|
||||
/** 开关的默认开启状态 */
|
||||
defaultValue: boolean
|
||||
}
|
||||
export interface SwitchOptions {
|
||||
name: string
|
||||
switches: Switches
|
||||
radio?: boolean
|
||||
dimAt?: 'checked' | 'notChecked'
|
||||
|
||||
/**
|
||||
* 用于配置所有开关。
|
||||
*
|
||||
* 该定义中的每个属性都会被用于生成一个组件 option。
|
||||
* 其中 option 的键为属性加上前缀 'switch-',值为一个 `boolean`,表示开关状态。
|
||||
*/
|
||||
export type SwitchItemsMetadata<S extends string> = {
|
||||
[key in S]: SwitchItemMetadata
|
||||
}
|
||||
|
||||
/**
|
||||
* 可用于单独定义 SwitchItemsMetadata
|
||||
*/
|
||||
export const defineSwitchItemsMetadata = <S extends string>(
|
||||
c: SwitchItemsMetadata<S>,
|
||||
): SwitchItemsMetadata<S> => c
|
||||
|
||||
/**
|
||||
* 用于配置 API 的行为。使用 {@link defineSwitchMetadata} 定义。
|
||||
*/
|
||||
export interface SwitchMetadata<N extends string, S extends string> {
|
||||
/**
|
||||
* 作为键名注入到组件 `options` 中
|
||||
*
|
||||
* @see {@link SwitchOptions}
|
||||
*/
|
||||
name: N
|
||||
/** 每个开关的单独配置。可用 {@link defineSwitchItemsMetadata} 单独定义 */
|
||||
switches: SwitchItemsMetadata<S>
|
||||
/**
|
||||
* 是否单选。值为 `undefined` 时取默认值。
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
radio?: undefined | boolean
|
||||
/**
|
||||
* 控制开关变暗的时机
|
||||
*
|
||||
* `undefined`: 始终不变暗
|
||||
* `'checked'`: 当开关关闭时变暗
|
||||
* `'notChecked'`: 始终为变暗状态
|
||||
*
|
||||
* @default undefined
|
||||
*/
|
||||
dimAt?: undefined | 'checked' | 'notChecked'
|
||||
/** 配置每个开关的图标 */
|
||||
switchProps?: {
|
||||
checkedIcon?: string
|
||||
notCheckedIcon?: string
|
||||
iconPosition?: 'left' | 'right'
|
||||
}
|
||||
}
|
||||
export const createSwitchOptions = (options: SwitchOptions) => {
|
||||
if (options.radio === undefined) {
|
||||
options.radio = false
|
||||
|
||||
/**
|
||||
* 定义一个 {@link SwitchMetadata}
|
||||
*/
|
||||
export const defineSwitchMetadata = <N extends string, S extends string>(
|
||||
c: SwitchMetadata<N, S>,
|
||||
): SwitchMetadata<N, S> => c
|
||||
|
||||
/**
|
||||
* 注入组件的 options 的一部分。
|
||||
*
|
||||
* 接口中的属性值大部分来自于 {@link SwitchMetadata} 中的定义。
|
||||
* 用户在 `SwitchMetadata` 中未定义的,为其默认值。
|
||||
*
|
||||
* @see {@link SwitchOptions}
|
||||
*/
|
||||
export interface SwitchMetadataOption<N extends string, S extends string> {
|
||||
name: N
|
||||
switches: SwitchItemsMetadata<S>
|
||||
radio: boolean
|
||||
dimAt: undefined | '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
|
||||
/** 组件的 Widget 显示名称 */
|
||||
optionDisplayName: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个 SwitchMetadataOption
|
||||
*/
|
||||
const newSwitchMetadataOption = <N extends string, S extends string>(
|
||||
metadata: SwitchMetadata<N, S>,
|
||||
componentName: string,
|
||||
optionDisplayName,
|
||||
) => ({
|
||||
...metadata,
|
||||
radio: metadata.radio === undefined ? false : metadata.radio,
|
||||
dimAt: metadata.dimAt,
|
||||
componentName,
|
||||
optionDisplayName,
|
||||
})
|
||||
|
||||
/**
|
||||
* 为类型参数 `S` 加上 'switch-' 前缀。
|
||||
*/
|
||||
export type SwitchItemNames<S extends string> = `switch-${S}`
|
||||
|
||||
/**
|
||||
* API 注入的 options 中的一部分
|
||||
*/
|
||||
export type SwitchItemOptions<S extends string> = Record<SwitchItemNames<S>, boolean>
|
||||
|
||||
/**
|
||||
* 包装完成后的组件元数据中的 options 类型
|
||||
*
|
||||
* 由三部分组成:
|
||||
* 1. 组件元数据被包装前的所有 options
|
||||
* 2. 一个键值对:键为 {@link SwitchMetadata} 定义时的 `name` 属性,值为 {@link SwitchMetadataOption} 类型
|
||||
* 3. 对应 {@link SwitchItemsMetadata} 的定义内容
|
||||
* (来自于 `SwitchMetadata` 的 `switches`)。详情见该类型描述
|
||||
*/
|
||||
export type SwitchOptions<O extends UnknownOptions, N extends string, S extends string> = O & {
|
||||
N: SwitchMetadataOption<N, S>
|
||||
} & SwitchItemOptions<S>
|
||||
|
||||
/**
|
||||
* 提取 SwitchOptions 类型
|
||||
*/
|
||||
export type SwitchOptionsOfSwitchMetadata<
|
||||
O extends UnknownOptions,
|
||||
C extends SwitchMetadata<string, string>,
|
||||
> = C extends SwitchMetadata<infer N, infer S> ? SwitchOptions<O, N, S> : never
|
||||
|
||||
/**
|
||||
* 提取 SwitchOptions 类型
|
||||
*/
|
||||
export type SwitchOptionsOfMetadata<
|
||||
M extends OptionsMetadata,
|
||||
C extends SwitchMetadata<string, string>,
|
||||
> = SwitchOptionsOfSwitchMetadata<OptionsOfMetadata<M>, C>
|
||||
|
||||
/**
|
||||
* 在原始的 OptionsMetadata 中添加了该 API 注入的 options 后形成的类型
|
||||
*/
|
||||
export type SwitchOptionsMetadata<
|
||||
O extends UnknownOptions,
|
||||
N extends string,
|
||||
S extends string,
|
||||
> = OptionsMetadata<SwitchOptions<O, N, S>>
|
||||
|
||||
/**
|
||||
* 向传入的 `options` 注入 API 所需的内容
|
||||
*
|
||||
* 该函数会直接修改传入的 `options` 自身,并将其返回。
|
||||
*/
|
||||
type SwitchOptionsMetadataExtender<S extends string> = <O extends UnknownOptions, N extends string>(
|
||||
options: OptionsMetadata<O>,
|
||||
switchMetadataOption: SwitchMetadataOption<N, S>,
|
||||
) => SwitchOptionsMetadata<O, N, S>
|
||||
|
||||
/**
|
||||
* 创建一个 {@link SwitchOptionsMetadataExtender}
|
||||
*/
|
||||
const newSwitchOptionsMetadataExtender = <S extends string>(
|
||||
itemsMetadata: SwitchItemsMetadata<S>,
|
||||
): SwitchOptionsMetadataExtender<S> => {
|
||||
const optionsToExtend = {}
|
||||
const entries = Object.entries<SwitchItemMetadata>(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 <O extends UnknownOptions, N extends string>(
|
||||
options: OptionsMetadata<O>,
|
||||
switchMetadataOption: SwitchMetadataOption<N, S>,
|
||||
) => {
|
||||
optionsToExtend[switchMetadataOption.name as string] = {
|
||||
defaultValue: options,
|
||||
displayName: optionDisplayName,
|
||||
displayName: switchMetadataOption.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<O, N, S>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建默认 Widget
|
||||
*/
|
||||
const newWidget = <N extends string, S extends string>(
|
||||
options: SwitchMetadataOption<N, S>,
|
||||
): Omit<Widget, 'name'> => ({
|
||||
component: () => import('./SwitchOptions.vue').then(m => m.default),
|
||||
options,
|
||||
})
|
||||
|
||||
/**
|
||||
* 携带 API 注入的 `options` 的 `entry` 函数类型
|
||||
*/
|
||||
export type SwitchEntry<
|
||||
O extends UnknownOptions,
|
||||
N extends string,
|
||||
S extends string,
|
||||
T = unknown,
|
||||
> = ComponentEntry<SwitchOptions<O, N, S>, T>
|
||||
|
||||
/**
|
||||
* 用于被 API 包装的组件原数据,类似于 `ComponentMetadata`。
|
||||
*
|
||||
* 定义该类型时,请使用 {@link defineIncompleteSwitchComponentMetadata}。
|
||||
*
|
||||
* 在通过该函数定义组件的 `entry` 时,可以获得被 API 注入的 options 的类型提示。
|
||||
*/
|
||||
export type IncompleteSwitchComponentMetadata<
|
||||
O extends UnknownOptions,
|
||||
N extends string,
|
||||
S extends string,
|
||||
> = {
|
||||
[K in keyof ComponentMetadata<O>]: K extends 'entry'
|
||||
? SwitchEntry<O, N, S>
|
||||
: ComponentMetadata<O>[K]
|
||||
}
|
||||
|
||||
/**
|
||||
* 定义一个用于被该 API 包装的组件元数据,
|
||||
* 类似于 {@link import("./define").defineComponentMetadata}。
|
||||
*
|
||||
* 与直接定义的不同之处在于:定义 `entry` 时,可以获得被 API 注入的 options 的类型提示。
|
||||
*/
|
||||
export const defineIncompleteSwitchComponentMetadata = <
|
||||
O extends UnknownOptions,
|
||||
N extends string,
|
||||
S extends string,
|
||||
>(
|
||||
m: IncompleteSwitchComponentMetadata<O, N, S>,
|
||||
): IncompleteSwitchComponentMetadata<O, N, S> => m
|
||||
|
||||
/**
|
||||
* 包装原始 `entry` 函数并返回
|
||||
*/
|
||||
const newSwitchEntry = <O extends UnknownOptions, N extends string, S extends string>(
|
||||
component: ComponentMetadata<O> | IncompleteSwitchComponentMetadata<O, N, S>,
|
||||
): SwitchEntry<O, N, S> => (...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
|
||||
}
|
||||
|
||||
/**
|
||||
* 被包装后的 {@link ComponentMetadata}
|
||||
*/
|
||||
export type SwitchComponentMetadata<
|
||||
O extends UnknownOptions,
|
||||
N extends string,
|
||||
S extends string,
|
||||
> = ComponentMetadata<SwitchOptions<O, N, S>>
|
||||
|
||||
/**
|
||||
* 用于包装组件元数据,生成开关选项。
|
||||
*
|
||||
* 传入参数可以是普通的组件元数据,也可以是 {@link IncompleteSwitchComponentMetadata}。
|
||||
* 后者应使用 {@link defineIncompleteSwitchComponentMetadata} 定义;
|
||||
* 在通过该函数定义组件的 `entry` 时,可以获得被 API 注入的 options 的类型提示。
|
||||
*
|
||||
* 为保障组件功能的正常运行,请勿使用以 'switch-' 开头的 option。
|
||||
*
|
||||
* 若传入组件未定义 Widget,则会注入一个用于控制各开关的 Widget。
|
||||
* 此 Widget 的功能与注入组件的 options 功能相同。
|
||||
*
|
||||
* 被包装后的元数据,其 `entry` 函数是原函数的包装,用于实现相关功能。
|
||||
*
|
||||
* 该函数会直接修改传入参数自身,并将其返回。
|
||||
*
|
||||
* @param component - 被包装的组件元数据
|
||||
* @returns 包装完成的组件元数据
|
||||
*/
|
||||
export type SwitchComponentWrapper<N extends string, S extends string> = <O extends UnknownOptions>(
|
||||
component: ComponentMetadata<O> | IncompleteSwitchComponentMetadata<O, N, S>,
|
||||
) => SwitchComponentMetadata<O, N, S>
|
||||
|
||||
/**
|
||||
* 创建一个 {@link SwitchComponentWrapper}
|
||||
*
|
||||
* 传入参数请使用 {@link defineSwitchMetadata} 定义。
|
||||
*
|
||||
* @param metadata - 相关配置元数据
|
||||
* @returns 组件包装器
|
||||
*/
|
||||
export const newSwitchComponentWrapper = <N extends string, S extends string>(
|
||||
metadata: SwitchMetadata<N, S>,
|
||||
): SwitchComponentWrapper<N, S> => {
|
||||
const extendOptions = newSwitchOptionsMetadataExtender(metadata.switches)
|
||||
// 返回的 wrapper
|
||||
return <O extends UnknownOptions>(
|
||||
component: ComponentMetadata<O> | IncompleteSwitchComponentMetadata<O, N, S>,
|
||||
) => {
|
||||
const switchMetadataOption = newSwitchMetadataOption(
|
||||
metadata,
|
||||
component.name,
|
||||
`${component.displayName}选项`,
|
||||
)
|
||||
// 若没有 Widget,则注入一个
|
||||
if (!component.widget) {
|
||||
component.widget = newWidget(switchMetadataOption)
|
||||
}
|
||||
// 扩展组件 options
|
||||
extendOptions(component.options, switchMetadataOption)
|
||||
// 包装 entry 函数
|
||||
component.entry = newSwitchEntry(component)
|
||||
return component as SwitchComponentMetadata<O, N, S>
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,8 +48,7 @@ export interface ComponentTag {
|
||||
|
||||
type ComponentOptionValidator<T> = (value: T, oldValue: T) => T | undefined | null
|
||||
|
||||
// TODO: 参考 discussion #3041。当不兼容代码替换完成后将 any 改为 unknown
|
||||
export type UnknownOptions = Record<string, any>
|
||||
export type UnknownOptions = Record<string, unknown>
|
||||
|
||||
export type EmptyOptions = Record<string, never>
|
||||
|
||||
@ -81,10 +80,6 @@ export type OptionsMetadata<O extends UnknownOptions = UnknownOptions> = {
|
||||
[OptionName in keyof O]: OptionMetadata<O[OptionName]>
|
||||
}
|
||||
|
||||
// TODO: 参考 discussion #3041。当不兼容代码替换完成后删除
|
||||
export type ComponentOptions = OptionsMetadata
|
||||
export type ComponentOption = OptionMetadata
|
||||
|
||||
/** 组件标签 */
|
||||
export const componentsTags = {
|
||||
/** 视频 */
|
||||
|
||||
@ -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 = <R extends UnknownOptions = UnknownOptions>(
|
||||
component: ComponentMetadata | string,
|
||||
): ComponentSettings<R> => {
|
||||
export const getComponentSettings = <O extends UnknownOptions>(
|
||||
component: ComponentMetadata<O> | string,
|
||||
): ComponentSettings<O> => {
|
||||
let componentMetadata: ComponentMetadata
|
||||
if (typeof component === 'string') {
|
||||
if (componentsMap[component] === undefined) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user