Merge pull request #3715 from timongh/tools-to-define-api

define API 收尾工作完成
This commit is contained in:
Grant Howard 2022-10-07 22:07:32 +08:00 committed by GitHub
commit df95d9dfe7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 595 additions and 259 deletions

View File

@ -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(),
},
}
})

View File

@ -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,
})

View File

@ -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)

View File

@ -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: {

View File

@ -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) {

View File

@ -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'

View File

@ -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 = {

View File

@ -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,
})

View File

@ -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,
})

View File

@ -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'

View File

@ -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: '倒退',

View File

@ -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()
},
},
}],
],
})
})
},

View File

@ -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>> = {

View File

@ -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
/** 隐藏进度条 */

View File

@ -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
/** 固定全局倍速 */

View File

@ -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],
}
})

View File

@ -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',
)
},
})
})

View File

@ -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)

View File

@ -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 },

View File

@ -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)
}

View File

@ -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
}

View File

@ -10,3 +10,7 @@ export type LanguagePack = {
map?: [string, Translation][]
regex?: RegexTranslation
}
export type Options = {
language: string,
translator: string,
}

View File

@ -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()

View File

@ -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>
}
}

View File

@ -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 = {
/** 视频 */

View File

@ -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) {