Skip to content

Commit

Permalink
refactor(ui/conf): introduce DeepPartial type for ui config (#996)
Browse files Browse the repository at this point in the history
- Replace `Partial<ArtalkConfig>` with `ArtalkConfigPartial` for better type clarity.
- Introduce `DeepPartial` type for nested partial configurations.
- Update default configuration handling to use `RequiredExcept` and `FunctionKeys`.
- Adjust related functions and interfaces to use `ArtalkConfigPartial`.
  • Loading branch information
qwqcode authored Oct 5, 2024
1 parent 3571a18 commit f1ae566
Show file tree
Hide file tree
Showing 9 changed files with 101 additions and 73 deletions.
10 changes: 5 additions & 5 deletions ui/artalk/src/artalk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import * as Stat from './plugins/stat'
import { Api } from './api'
import type { TInjectedServices } from './service'
import { GlobalPlugins, PluginOptions, load } from './load'
import type { ArtalkConfig, EventPayloadMap, ArtalkPlugin, ContextApi } from '@/types'
import type { ArtalkConfigPartial, EventPayloadMap, ArtalkPlugin, ContextApi } from '@/types'

/**
* Artalk
Expand All @@ -18,7 +18,7 @@ import type { ArtalkConfig, EventPayloadMap, ArtalkPlugin, ContextApi } from '@/
export default class Artalk {
public ctx!: ContextApi

constructor(conf: Partial<ArtalkConfig>) {
constructor(conf: ArtalkConfigPartial) {
// Init Config
const handledConf = handelCustomConf(conf, true)

Expand Down Expand Up @@ -49,7 +49,7 @@ export default class Artalk {
}

/** Update config of Artalk */
public update(conf: Partial<ArtalkConfig>) {
public update(conf: ArtalkConfigPartial) {
this.ctx.updateConf(conf)
return this
}
Expand Down Expand Up @@ -92,7 +92,7 @@ export default class Artalk {
// ===========================

/** Init Artalk */
public static init(conf: Partial<ArtalkConfig>): Artalk {
public static init(conf: ArtalkConfigPartial): Artalk {
return new Artalk(conf)
}

Expand All @@ -103,7 +103,7 @@ export default class Artalk {
}

/** Load count widget */
public static loadCountWidget(c: Partial<ArtalkConfig>) {
public static loadCountWidget(c: ArtalkConfigPartial) {
const conf = handelCustomConf(c, true)

Stat.initCountWidget({
Expand Down
17 changes: 7 additions & 10 deletions ui/artalk/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { ApiOptions } from './api/options'
import { mergeDeep } from './lib/merge-deep'
import { createApiHandlers } from './api'
import Defaults from './defaults'
import type { ArtalkConfig, ContextApi } from '@/types'
import type { ArtalkConfig, ArtalkConfigPartial, ContextApi } from '@/types'

/**
* Handle the custom config which is provided by the user
Expand All @@ -11,14 +11,11 @@ import type { ArtalkConfig, ContextApi } from '@/types'
* @param full - If `full` is `true`, the return value will be the complete config for Artalk instance creation
* @returns The config for Artalk instance creation
*/
export function handelCustomConf(customConf: Partial<ArtalkConfig>, full: true): ArtalkConfig
export function handelCustomConf(
customConf: Partial<ArtalkConfig>,
full?: false,
): Partial<ArtalkConfig>
export function handelCustomConf(customConf: Partial<ArtalkConfig>, full = false) {
export function handelCustomConf(customConf: ArtalkConfigPartial, full: true): ArtalkConfig
export function handelCustomConf(customConf: ArtalkConfigPartial, full?: false): ArtalkConfigPartial
export function handelCustomConf(customConf: ArtalkConfigPartial, full = false) {
// 合并默认配置
const conf: Partial<ArtalkConfig> = full ? mergeDeep(Defaults, customConf) : customConf
const conf: ArtalkConfigPartial = full ? mergeDeep(Defaults, customConf) : customConf

// 绑定元素
if (conf.el && typeof conf.el === 'string') {
Expand Down Expand Up @@ -59,7 +56,7 @@ export function handelCustomConf(customConf: Partial<ArtalkConfig>, full = false
* @param conf - The Server response config for control the frontend of Artalk remotely
* @returns The config for Artalk instance creation
*/
export function handleConfFormServer(conf: Partial<ArtalkConfig>) {
export function handleConfFormServer(conf: ArtalkConfigPartial): ArtalkConfigPartial {
const ExcludedKeys: (keyof ArtalkConfig)[] = [
'el',
'pageKey',
Expand Down Expand Up @@ -95,7 +92,7 @@ export function handleConfFormServer(conf: Partial<ArtalkConfig>) {
* @param ctx - If `ctx` not provided, `checkAdmin` and `checkCaptcha` will be disabled
* @returns ApiOptions for Api client instance creation
*/
export function convertApiOptions(conf: Partial<ArtalkConfig>, ctx?: ContextApi): ApiOptions {
export function convertApiOptions(conf: ArtalkConfigPartial, ctx?: ContextApi): ApiOptions {
return {
baseURL: `${conf.server}/api/v2`,
siteName: conf.site || '',
Expand Down
3 changes: 2 additions & 1 deletion ui/artalk/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { watchConf } from './lib/watch-conf'

import type {
ArtalkConfig,
ArtalkConfigPartial,
CommentData,
ListFetchParams,
ContextApi,
Expand Down Expand Up @@ -165,7 +166,7 @@ class Context implements ContextApi {
this.updateConf({ darkMode })
}

updateConf(nConf: Partial<ArtalkConfig>): void {
updateConf(nConf: ArtalkConfigPartial): void {
this.conf = mergeDeep(this.conf, handelCustomConf(nConf, false))
this.mounted && this.events.trigger('updated', this.conf)
}
Expand Down
16 changes: 15 additions & 1 deletion ui/artalk/src/defaults.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import type { ArtalkConfig } from '@/types'

const defaults: ArtalkConfig = {
type RequiredExcept<T, K extends keyof T> = Required<Omit<T, K>> & Pick<T, K>
type FunctionKeys<T> = Exclude<
{ [K in keyof T]: NonNullable<T[K]> extends (...args: any[]) => any ? K : never }[keyof T],
undefined
>
type ExcludedKeys = FunctionKeys<ArtalkConfig>

const defaults: RequiredExcept<ArtalkConfig, ExcludedKeys> = {
el: '',
pageKey: '',
pageTitle: '',
Expand Down Expand Up @@ -45,12 +52,19 @@ const defaults: ArtalkConfig = {
scrollable: false,
},

pvAdd: true,
imgUpload: true,
imgLazyLoad: 'native',
reqTimeout: 15000,
versionCheck: true,
useBackendConf: true,
listUnreadHighlight: false,

locale: 'en',
apiVersion: '',
pluginURLs: [],
markedReplacers: [],
markedOptions: {},
}

if (ARTALK_LITE) {
Expand Down
4 changes: 2 additions & 2 deletions ui/artalk/src/load.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { DefaultPlugins } from './plugins'
import type { ArtalkConfig, ArtalkPlugin, ContextApi } from '@/types'
import type { ArtalkConfigPartial, ArtalkPlugin, ContextApi } from '@/types'
import { handleConfFormServer } from '@/config'
import { showErrorDialog } from '@/components/error-dialog'

Expand Down Expand Up @@ -37,7 +37,7 @@ export async function load(ctx: ContextApi) {
})

// Initial config
let conf: Partial<ArtalkConfig> = {
let conf: ArtalkConfigPartial = {
apiVersion: data.version?.version, // version info
}

Expand Down
115 changes: 65 additions & 50 deletions ui/artalk/src/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,156 +4,171 @@ import type { EditorApi } from './editor'
import type { I18n } from '@/i18n'

export interface ArtalkConfig {
/** 装载元素 */
/** Element selector or Element to mount the Artalk */
el: string | HTMLElement

/** 页面唯一标识(完整 URL) */
/** Unique page identifier */
pageKey: string

/** 页面标题 */
/** Title of the page */
pageTitle: string

/** 服务器地址 */
/** Server address */
server: string

/** 站点名 */
/** Site name */
site: string

/** 评论框占位字符 */
/** Placeholder text for the comment input box */
placeholder: string

/** 评论为空时显示字符 */
/** Text to display when there are no comments */
noComment: string

/** 发送按钮文字 */
/** Text for the send button */
sendBtn: string

/** 评论框穿梭(显示在待回复评论后面) */
/** Movable comment box (display below the comment to be replied) */
editorTravel: boolean

/** 表情包 */
/** Emoticons settings */
emoticons: object | any[] | string | false

/** Gravatar 头像 */
/** Gravatar avatar settings */
gravatar: {
/** API 地址 */
/** API endpoint */
mirror: string
/** API 参数 */
/** API parameters */
params: string
}

/** 头像链接生成器 */
/** Avatar URL generator function */
avatarURLBuilder?: (comment: CommentData) => string

/** 分页配置 */
/** Pagination settings */
pagination: {
/** 每次请求获取数量 */
/** Number of comments to fetch per request */
pageSize: number
/** 阅读更多模式 */
/** "Read more" mode */
readMore: boolean
/** 滚动到底部自动加载 */
/** Automatically load more comments when scrolled to the bottom */
autoLoad: boolean
}

/** 内容限高 */
/** Height limit configuration */
heightLimit: {
/** 评论内容限高 */
/** Maximum height for comment content */
content: number

/** 子评论区域限高 */
/** Maximum height for child comments */
children: number

/** 滚动限高 */
/** Whether the content is scrollable */
scrollable: boolean
}

/** 评论投票按钮 */
/** Voting feature for comments */
vote: boolean

/** 评论投票反对按钮 */
/** Downvote button for comments */
voteDown: boolean

/** 评论预览功能 */
/** Preview feature for comments */
preview: boolean

/** 评论数绑定元素 Selector */
/** Selector for the element binding to the comment count */
countEl: string

/** PV 数绑定元素 Selector */
/** Selector for the element binding to the page views (PV) count */
pvEl: string

/** 统计组件 PageKey 属性名 */
/** Attribute name for the PageKey in statistics components */
statPageKeyAttr: string

/** 夜间模式 */
/** Dark mode settings */
darkMode: boolean | 'auto'

/** 请求超时(单位:秒) */
/** Request timeout (in seconds) */
reqTimeout: number

/** 平铺模式 */
/** Flat mode for comment display */
flatMode: boolean | 'auto'

/** 嵌套模式 · 最大层数 */
/** Maximum number of levels for nested comments */
nestMax: number

/** 嵌套模式 · 排序方式 */
/** Sorting order for nested comments */
nestSort: 'DATE_ASC' | 'DATE_DESC'

/** 显示 UA 徽标 */
/** Display UA badge (user agent badge) */
uaBadge: boolean

/** 评论列表排序功能 (显示 Dropdown) */
/** Show sorting dropdown for comment list */
listSort: boolean

/** 图片上传功能 */
/** Enable image upload feature */
imgUpload: boolean

/** 图片上传器 */
/** Image uploader function */
imgUploader?: (file: File) => Promise<string>

/** Image lazy load */
/** Image lazy load mode */
imgLazyLoad?: 'native' | 'data-src'

/** 版本检测 */
/** Enable version check */
versionCheck: boolean

/** 引用后端配置 */
/** Use backend configuration */
useBackendConf: boolean

/** 语言本地化 */
/** Localization settings */
locale: I18n | string

/** 后端版本 (系统数据,用户不允许更改) */
/** Backend API version (system data, not allowed for user modification) */
apiVersion?: string

/** Plugin script urls */
/** URLs for plugin scripts */
pluginURLs?: string[]

/** Replacer for marked */
/** Replacers for the marked (Markdown parser) */
markedReplacers?: ((raw: string) => string)[]

/** Marked options */
/** Options for the marked (Markdown parser) */
markedOptions?: MarkedOptions

/** 列表请求参数修改器 */
/** Modifier for list fetch request parameters */
listFetchParamsModifier?: (params: any) => void

/**
* Date formatter for custom date format
* @param date - Date object
* Custom date formatter
* @param date - The Date object to format
* @returns Formatted date string
*/
dateFormatter?: (date: Date) => string

// TODO consider merge list related config into one object, or flatten all to keep simple (keep consistency)
remoteConfModifier?: (conf: Partial<ArtalkConfig>) => void
/** Custom configuration modifier for remote configuration (referenced by artalk-sidebar) */
// TODO: Consider merging list-related configuration into a single object, or flatten everything for simplicity and consistency
remoteConfModifier?: (conf: DeepPartial<ArtalkConfig>) => void

/** List unread highlight (enable by default in artalk-sidebar) */
listUnreadHighlight?: boolean

/** The relative element for scrolling (useful if artalk is in a scrollable container) */
scrollRelativeTo?: () => HTMLElement

/** Page view increment when comment list is loaded */
pvAdd?: boolean

/** Callback before submitting a comment */
beforeSubmit?: (editor: EditorApi, next: () => void) => void
}

type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K]
}

export type ArtalkConfigPartial = DeepPartial<ArtalkConfig>

/**
* Local User Data (in localStorage)
*
Expand Down
3 changes: 2 additions & 1 deletion ui/artalk/src/types/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Marked } from 'marked'
import type {
SidebarShowPayload,
EventPayloadMap,
ArtalkConfigPartial,
ArtalkConfig,
CommentData,
DataManagerApi,
Expand Down Expand Up @@ -114,7 +115,7 @@ export interface ContextApi extends EventManagerFuncs<EventPayloadMap> {
getEl(): HTMLElement

/** 更新配置 */
updateConf(conf: Partial<ArtalkConfig>): void
updateConf(conf: ArtalkConfigPartial): void

/** 监听配置更新 */
watchConf<T extends (keyof ArtalkConfig)[]>(
Expand Down
Loading

0 comments on commit f1ae566

Please sign in to comment.