Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(ui/conf): introduce DeepPartial type for ui config #996

Merged
merged 1 commit into from
Oct 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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