artalk 2.0.4 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +7 -60
  2. package/dist/Artalk.css +1 -1
  3. package/dist/Artalk.es.js +5996 -1
  4. package/dist/Artalk.iife.js +266 -1
  5. package/dist/Artalk.js +266 -1
  6. package/package.json +3 -4
  7. package/src/api/index.ts +269 -0
  8. package/src/api/request.ts +125 -0
  9. package/src/artalk.ts +153 -0
  10. package/src/components/_component.ts.txt +10 -0
  11. package/src/components/action-btn.ts +169 -0
  12. package/src/components/admin/page-list.ts +194 -0
  13. package/src/components/admin/site-list-floater.ts +98 -0
  14. package/src/components/admin/site-list.ts +289 -0
  15. package/src/components/comment.ts +478 -0
  16. package/src/components/dialog.ts +63 -0
  17. package/src/components/editor-plugs/{_ExamplePlug.ts.txt → _example-plug.ts.txt} +0 -0
  18. package/src/components/editor-plugs/{EditorPlug.ts → editor-plug.ts} +3 -3
  19. package/src/components/editor-plugs/{EmoticonsPlug.less → emoticons-plug.less} +0 -0
  20. package/src/components/editor-plugs/{EmoticonsPlug.ts → emoticons-plug.ts} +9 -9
  21. package/src/components/editor-plugs/{PreviewPlug.less → preview-plug.less} +0 -0
  22. package/src/components/editor-plugs/{PreviewPlug.ts → preview-plug.ts} +10 -10
  23. package/src/components/{Editor.ts → editor.ts} +104 -102
  24. package/src/components/html/comment.html +2 -6
  25. package/src/components/html/list.html +0 -1
  26. package/src/components/html/sidebar.html +18 -4
  27. package/src/components/item-text-editor.ts +136 -0
  28. package/src/components/layer.ts +135 -0
  29. package/src/components/list-lite.ts +507 -0
  30. package/src/components/list.ts +172 -0
  31. package/src/components/pagination.ts +150 -0
  32. package/src/components/read-more-btn.ts +71 -0
  33. package/src/components/sidebar-view.ts +26 -0
  34. package/src/components/sidebar-views/comments-view.ts +66 -0
  35. package/src/components/sidebar-views/pages-view.ts +61 -0
  36. package/src/components/sidebar-views/setting-view.ts +21 -0
  37. package/src/components/sidebar-views/sites-view.ts +42 -0
  38. package/src/components/sidebar-views/transfer-view.ts +218 -0
  39. package/src/components/sidebar.ts +288 -0
  40. package/src/context.ts +42 -0
  41. package/src/default-conf.ts +42 -0
  42. package/src/lib/checker/admin-checker.ts +33 -0
  43. package/src/lib/checker/captcha-checker.ts +38 -0
  44. package/src/lib/checker/index.ts +134 -0
  45. package/src/lib/component.ts +3 -3
  46. package/src/lib/md5.js +6 -0
  47. package/src/lib/ui.ts +64 -74
  48. package/src/lib/user.ts +2 -8
  49. package/src/lib/utils.ts +30 -11
  50. package/src/main.ts +1 -1
  51. package/src/style/_color.less +8 -2
  52. package/src/style/_extend.less +81 -0
  53. package/src/style/_variables.less +3 -0
  54. package/src/style/comment.less +60 -24
  55. package/src/style/editor.less +3 -0
  56. package/src/style/item-text-editor.less +35 -0
  57. package/src/style/list.less +51 -32
  58. package/src/style/main.less +75 -1
  59. package/src/style/page-list.less +50 -0
  60. package/src/style/pagination.less +58 -0
  61. package/src/style/sidebar.less +274 -199
  62. package/src/style/site-list.less +208 -0
  63. package/tsconfig.json +27 -0
  64. package/types/artalk-config.d.ts +68 -51
  65. package/types/artalk-data.d.ts +38 -1
  66. package/types/event.d.ts +73 -0
  67. package/types/index.d.ts +1 -6
  68. package/.github/workflows/npm.yml +0 -31
  69. package/.github/workflows/pages.yml +0 -30
  70. package/dist/src/Event.d.ts +0 -59
  71. package/dist/types/artalk-config.d.ts +0 -79
  72. package/dist/types/artalk-data.d.ts +0 -153
  73. package/dist/types/global.d.ts +0 -1
  74. package/dist/types/index.d.ts +0 -8
  75. package/dist/types/vite-env.d.ts +0 -1
  76. package/example/pjax/res1.html +0 -41
  77. package/example/pjax/res2.html +0 -41
  78. package/src/Artalk.ts +0 -160
  79. package/src/Constant.ts +0 -6
  80. package/src/Context.ts +0 -34
  81. package/src/Event.d.ts +0 -59
  82. package/src/components/Comment.ts +0 -402
  83. package/src/components/Layer.ts +0 -138
  84. package/src/components/List.ts +0 -162
  85. package/src/components/ListLite.ts +0 -407
  86. package/src/components/Sidebar.ts +0 -141
  87. package/src/components/sidebar-views/AdminView.ts +0 -523
  88. package/src/components/sidebar-views/MessageView.ts +0 -48
  89. package/src/components/sidebar-views/SidebarView.ts +0 -24
  90. package/src/lib/api.ts +0 -380
  91. package/src/lib/checker.ts +0 -164
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "artalk",
3
- "version": "2.0.4",
4
- "description": "A Fast, Slight & Delightful Comment",
3
+ "version": "2.1.1",
4
+ "description": "A Selfhosted Comment System",
5
5
  "keywords": [
6
6
  "comment-system",
7
7
  "comments-widget",
@@ -20,7 +20,7 @@
20
20
  "unpkg": "./dist/Artalk.iife.js",
21
21
  "jsdelivr": "./dist/Artalk.iife.js",
22
22
  "module": "./dist/Artalk.es.js",
23
- "types": "./dist/types/index.d.ts",
23
+ "types": "./types/index.d.ts",
24
24
  "exports": {
25
25
  ".": {
26
26
  "import": "./dist/Artalk.es.js",
@@ -57,7 +57,6 @@
57
57
  "prettier": "^2.3.2",
58
58
  "typescript": "4.3.2",
59
59
  "vite": "^2.5.1",
60
- "vite-plugin-dts": "^0.7.0",
61
60
  "vite-tsconfig-paths": "^3.3.14"
62
61
  },
63
62
  "dependencies": {
@@ -0,0 +1,269 @@
1
+ import Context from '../context'
2
+ import { CommentData, ListData, UserData, PageData, SiteData, NotifyData } from '~/types/artalk-data'
3
+ import { Fetch, ToFormData, POST, GET } from './request'
4
+
5
+ export default class Api {
6
+ private ctx: Context
7
+ private baseURL: string
8
+
9
+ constructor (ctx: Context) {
10
+ this.ctx = ctx
11
+ this.baseURL = ctx.conf.server
12
+ }
13
+
14
+ // ============================
15
+ // 评论 Comment
16
+ // ============================
17
+
18
+ /** 评论 · 获取 */
19
+ public get(offset: number, type?: string, flatMode?: boolean, paramsEditor?: (params: any) => void) {
20
+ const params: any = {
21
+ page_key: this.ctx.conf.pageKey,
22
+ site_name: this.ctx.conf.site || '',
23
+ limit: this.ctx.conf.pagination?.pageSize || 15,
24
+ offset,
25
+ }
26
+
27
+ if (type) params.type = type
28
+ if (flatMode) params.flat_mode = flatMode // 平铺模式
29
+ if (this.ctx.user.checkHasBasicUserInfo()) {
30
+ params.name = this.ctx.user.data.nick
31
+ params.email = this.ctx.user.data.email
32
+ }
33
+
34
+ if (paramsEditor) paramsEditor(params)
35
+
36
+ return POST<ListData>(this.ctx, `${this.baseURL}/get`, params)
37
+ }
38
+
39
+ /** 评论 · 创建 */
40
+ public async add(comment: { nick: string, email: string, link: string, content: string, rid: number }) {
41
+ const params: any = {
42
+ name: comment.nick,
43
+ email: comment.email,
44
+ link: comment.link,
45
+ content: comment.content,
46
+ rid: comment.rid,
47
+ page_key: this.ctx.conf.pageKey,
48
+ page_title: this.ctx.conf.pageTitle || '',
49
+ }
50
+
51
+ if (this.ctx.conf.site) params.site_name = this.ctx.conf.site
52
+
53
+ const data = await POST<any>(this.ctx, `${this.baseURL}/add`, params)
54
+ return (data.comment as CommentData)
55
+ }
56
+
57
+ /** 评论 · 修改 */
58
+ public async commentEdit(data: CommentData) {
59
+ const params: any = {
60
+ ...data,
61
+ }
62
+
63
+ const d = await POST<any>(this.ctx, `${this.baseURL}/admin/comment-edit`, params)
64
+ return (d.comment as CommentData)
65
+ }
66
+
67
+ /** 评论 · 删除 */
68
+ public commentDel(commentID: number, siteName?: string) {
69
+ const params: any = {
70
+ id: String(commentID),
71
+ site_name: siteName || '',
72
+ }
73
+
74
+ return POST(this.ctx, `${this.baseURL}/admin/comment-del`, params)
75
+ }
76
+
77
+ // ============================
78
+ // 用户 User
79
+ // ============================
80
+
81
+ /** 用户 · 登录 */
82
+ public async login(name: string, email: string, password: string) {
83
+ const params: any = {
84
+ name, email, password
85
+ }
86
+
87
+ if (this.ctx.conf.site) params.site_name = this.ctx.conf.site
88
+
89
+ const data = await POST<any>(this.ctx, `${this.baseURL}/login`, params)
90
+ return (data.token as string)
91
+ }
92
+
93
+ /** 用户 · 获取 */
94
+ public userGet(name: string, email: string) {
95
+ const ctrl = new AbortController()
96
+ const { signal } = ctrl
97
+
98
+ const params: any = {
99
+ name, email, site_name: (this.ctx.conf.site || '')
100
+ }
101
+
102
+ const req = Fetch(this.ctx, `${this.baseURL}/user-get`, {
103
+ method: 'POST',
104
+ body: ToFormData(params),
105
+ signal,
106
+ }).then((json) => ({
107
+ user: json.data.user as UserData|null,
108
+ is_login: json.data.is_login as boolean,
109
+ unread: (json.data.unread || []) as NotifyData[],
110
+ unread_count: json.data.unread_count || 0,
111
+ }))
112
+
113
+ return {
114
+ req,
115
+ abort: () => { ctrl.abort() },
116
+ }
117
+ }
118
+
119
+ // ============================
120
+ // 页面 Page
121
+ // ============================
122
+
123
+ /** 页面 · 获取 */
124
+ public async pageGet(siteName?: string, offset?: number, limit?: number) {
125
+ const params: any = {
126
+ site_name: siteName || '',
127
+ offset: offset || 0,
128
+ limit: limit || 15,
129
+ }
130
+
131
+ const d = await POST<any>(this.ctx, `${this.baseURL}/admin/page-get`, params)
132
+ return (d as { pages: PageData[], total: number })
133
+ }
134
+
135
+ /** 页面 · 修改 */
136
+ public async pageEdit(data: PageData) {
137
+ const params: any = {
138
+ id: data.id,
139
+ key: data.key,
140
+ title: data.title,
141
+ admin_only: data.admin_only,
142
+ site_name: data.site_name || this.ctx.conf.site,
143
+ }
144
+
145
+ const d = await POST<any>(this.ctx, `${this.baseURL}/admin/page-edit`, params)
146
+ return (d.page as PageData)
147
+ }
148
+
149
+ /** 页面 · 删除 */
150
+ public pageDel(pageKey: string, siteName?: string) {
151
+ const params: any = {
152
+ key: String(pageKey),
153
+ site_name: siteName || '',
154
+ }
155
+
156
+ return POST(this.ctx, `${this.baseURL}/admin/page-del`, params)
157
+ }
158
+
159
+ /** 页面 · 数据更新 */
160
+ public async pageFetch(id: number) {
161
+ const params: any = {
162
+ id,
163
+ }
164
+
165
+ const d = await POST<any>(this.ctx, `${this.baseURL}/admin/page-fetch`, params)
166
+ return (d.page as PageData)
167
+ }
168
+
169
+ // ============================
170
+ // 站点 Site
171
+ // ============================
172
+
173
+ /** 站点 · 获取 */
174
+ public async siteGet() {
175
+ const params: any = {}
176
+
177
+ const d = await POST<any>(this.ctx, `${this.baseURL}/admin/site-get`, params)
178
+ return (d.sites as SiteData[])
179
+ }
180
+
181
+ /** 站点 · 创建 */
182
+ public async siteAdd(name: string, urls: string) {
183
+ const params: any = {
184
+ name, urls,
185
+ }
186
+
187
+ const d = await POST<any>(this.ctx, `${this.baseURL}/admin/site-add`, params)
188
+ return (d.site as SiteData)
189
+ }
190
+
191
+ /** 站点 · 修改 */
192
+ public async siteEdit(data: SiteData) {
193
+ const params: any = {
194
+ id: data.id,
195
+ name: data.name || '',
196
+ urls: data.urls || '',
197
+ }
198
+
199
+ const d = await POST<any>(this.ctx, `${this.baseURL}/admin/site-edit`, params)
200
+ return (d.site as SiteData)
201
+ }
202
+
203
+ /** 站点 · 删除 */
204
+ public siteDel(id: number, delContent = false) {
205
+ const params: any = { id, del_content: delContent }
206
+
207
+ return POST(this.ctx, `${this.baseURL}/admin/site-del`, params)
208
+ }
209
+
210
+ /** 导出 */
211
+ public async export() {
212
+ const d = await Fetch(this.ctx, `${this.baseURL}/admin/export`, { method: 'POST' }, 0)
213
+ return (d.data?.data || '' as string)
214
+ }
215
+
216
+ // ============================
217
+ // 杂项
218
+ // ============================
219
+
220
+ /** 投票 */
221
+ public async vote(targetID: number, type: string) {
222
+ const params: any = {
223
+ site_name: this.ctx.conf.site || '',
224
+ target_id: targetID,
225
+ type,
226
+ }
227
+
228
+ if (this.ctx.user.checkHasBasicUserInfo()) {
229
+ params.name = this.ctx.user.data.nick
230
+ params.email = this.ctx.user.data.email
231
+ }
232
+
233
+ const data = await POST<any>(this.ctx, `${this.baseURL}/vote`, params)
234
+ return (data as {up: number, down: number})
235
+ }
236
+
237
+ /** 已读标记 */
238
+ public markRead(notifyKey: string, readAll = false) {
239
+ const params: any = {
240
+ site_name: this.ctx.conf.site || '',
241
+ notify_key: notifyKey,
242
+ }
243
+
244
+ if (readAll) {
245
+ delete params.notify_key
246
+ params.read_all = true
247
+ params.name = this.ctx.user.data.nick
248
+ params.email = this.ctx.user.data.email
249
+ }
250
+
251
+ return POST(this.ctx, `${this.baseURL}/mark-read`, params)
252
+ }
253
+
254
+ // ============================
255
+ // 验证码
256
+ // ============================
257
+
258
+ /** 验证码 · 获取 */
259
+ public async captchaGet() {
260
+ const data = await GET<any>(this.ctx, `${this.baseURL}/captcha/refresh`)
261
+ return (data.img_data || '') as string
262
+ }
263
+
264
+ /** 验证码 · 检验 */
265
+ public async captchaCheck(value: string) {
266
+ const data = await GET<any>(this.ctx, `${this.baseURL}/captcha/check`, { value })
267
+ return (data.img_data || '') as string
268
+ }
269
+ }
@@ -0,0 +1,125 @@
1
+ import Context from '../context'
2
+
3
+ /** 公共请求函数 */
4
+ export async function Fetch(ctx: Context, input: RequestInfo, init: RequestInit, timeout?: number): Promise<any> {
5
+ // JWT
6
+ if (ctx.user.data.token) {
7
+ const requestHeaders: HeadersInit = new Headers()
8
+ requestHeaders.set('Authorization', `Bearer ${ctx.user.data.token}`)
9
+
10
+ init.headers = requestHeaders
11
+ }
12
+
13
+ // 请求操作
14
+ try {
15
+
16
+ let resp: Response
17
+ if ((typeof timeout !== 'number' && ctx.conf.reqTimeout === 0) || timeout === 0) {
18
+ resp = await fetch(input, init)
19
+ } else {
20
+ // 请求超时检测
21
+ resp = await timeoutPromise(timeout || ctx.conf.reqTimeout || 15000, fetch(input, init))
22
+ }
23
+
24
+ if (!resp.ok && resp.status !== 401) throw new Error(`请求响应 ${resp.status}`)
25
+
26
+ // 解析获取响应的 json
27
+ let json: any = await resp.json()
28
+
29
+ // 重新发起请求
30
+ const recall = (resolve, reject) => {
31
+ Fetch(ctx, input, init).then(d => {
32
+ resolve(d)
33
+ }).catch(err => {
34
+ reject(err)
35
+ })
36
+ }
37
+
38
+ // 请求弹出层验证
39
+ if (json.data && json.data.need_captcha) {
40
+ // 请求需要验证码
41
+ json = await (new Promise<any>((resolve, reject) => {
42
+ ctx.trigger('checker-captcha', {
43
+ imgData: json.data.img_data,
44
+ onSuccess: () => {
45
+ recall(resolve, reject)
46
+ },
47
+ onCancel: () => {
48
+ reject(json)
49
+ }
50
+ })
51
+ }))
52
+ } else if ((json.data && json.data.need_login) || resp.status === 401) {
53
+ // 请求需要管理员权限
54
+ json = await (new Promise<any>((resolve, reject) => {
55
+ ctx.trigger('checker-admin', {
56
+ onSuccess: () => {
57
+ recall(resolve, reject)
58
+ },
59
+ onCancel: () => {
60
+ reject(json)
61
+ }
62
+ })
63
+ }))
64
+ }
65
+
66
+ if (!json.success) throw json // throw 相当于 reject(json)
67
+
68
+ return json
69
+
70
+ } catch (err) {
71
+ // 错误处理
72
+ console.error(err)
73
+
74
+ if (err instanceof TypeError)
75
+ throw new Error(`网络错误`)
76
+
77
+ throw err
78
+ }
79
+ }
80
+
81
+ /** 公共 POST 请求 */
82
+ export async function POST<T>(ctx: Context, url: string, data?: {[key: string]: any}) {
83
+ const init: RequestInit = {
84
+ method: 'POST',
85
+ }
86
+ if (data) init.body = ToFormData(data)
87
+
88
+ const json = await Fetch(ctx, url, init)
89
+ return ((json.data || {}) as T)
90
+ }
91
+
92
+ /** 公共 GET 请求 */
93
+ export async function GET<T>(ctx: Context, url: string, data?: {[key: string]: any}) {
94
+ const json = await Fetch(ctx, url + (data ? (`?${new URLSearchParams(data)}`) : ''), {
95
+ method: 'GET',
96
+ })
97
+ return ((json.data || {}) as T)
98
+ }
99
+
100
+ /** 对象转 FormData */
101
+ export function ToFormData(object: {[key: string]: any}): FormData {
102
+ const formData = new FormData()
103
+ Object.keys(object).forEach(key => formData.append(key, String(object[key])))
104
+ return formData
105
+ }
106
+
107
+ /** 我靠,fetch 一个 timeout,都要丑陋的实现 */
108
+ function timeoutPromise<T>(ms: number, promise: Promise<T>): Promise<T> {
109
+ return new Promise((resolve, reject) => {
110
+ const timeoutId = setTimeout(() => {
111
+ reject(new Error("请求超时"))
112
+ }, ms)
113
+
114
+ promise.then(
115
+ (res) => {
116
+ clearTimeout(timeoutId)
117
+ resolve(res)
118
+ },
119
+ (err) => {
120
+ clearTimeout(timeoutId)
121
+ reject(err)
122
+ }
123
+ )
124
+ })
125
+ }
package/src/artalk.ts ADDED
@@ -0,0 +1,153 @@
1
+ import './style/main.less'
2
+
3
+ import Context from './context'
4
+ import ArtalkConfig from '~/types/artalk-config'
5
+ import defaultConf from './default-conf'
6
+
7
+ import CheckerLauncher from './lib/checker'
8
+ import Editor from './components/editor'
9
+ import List from './components/list'
10
+ import Sidebar from './components/sidebar'
11
+
12
+ import { GetLayerWrap } from './components/layer'
13
+ import { EventPayloadMap, Handler } from '~/types/event'
14
+
15
+ /**
16
+ * Artalk
17
+ * @link https://artalk.js.org
18
+ */
19
+ export default class Artalk {
20
+ public ctx: Context
21
+ public conf: ArtalkConfig
22
+ public $root: HTMLElement
23
+
24
+ public checkerLauncher: CheckerLauncher
25
+ public editor: Editor
26
+ public list: List
27
+ public sidebar: Sidebar
28
+
29
+ constructor (customConf: ArtalkConfig) {
30
+ // 配置
31
+ this.conf = { ...defaultConf, ...customConf }
32
+ this.conf.server = this.conf.server.replace(/\/$/, '')
33
+
34
+ // 默认 pageKey
35
+ if (!this.conf.pageKey) {
36
+ this.conf.pageKey = `${window.location.protocol}//'${window.location.host}/${window.location.pathname}`
37
+ }
38
+
39
+ // 装载元素
40
+ try {
41
+ const $root = document.querySelector<HTMLElement>(this.conf.el)
42
+ if (!$root) throw Error(`Sorry, target element "${this.conf.el}" was not found.`)
43
+ this.$root = $root
44
+ } catch (e) {
45
+ console.error(e)
46
+ throw new Error('Please check your Artalk `el` config.')
47
+ }
48
+
49
+ // Context 初始化
50
+ this.ctx = new Context(this.$root, this.conf)
51
+
52
+ // 界面初始化
53
+ this.$root.classList.add('artalk')
54
+ this.$root.innerHTML = ''
55
+ this.initDarkMode()
56
+
57
+ // 组件初始化
58
+ this.checkerLauncher = new CheckerLauncher(this.ctx)
59
+
60
+ // 编辑器
61
+ this.editor = new Editor(this.ctx)
62
+ this.$root.appendChild(this.editor.$el)
63
+
64
+ // 评论列表
65
+ this.list = new List(this.ctx)
66
+ this.$root.appendChild(this.list.$el)
67
+
68
+ // 侧边栏
69
+ this.sidebar = new Sidebar(this.ctx)
70
+ this.$root.appendChild(this.sidebar.$el)
71
+
72
+ // 评论获取
73
+ this.list.reqComments()
74
+
75
+ // 事件绑定初始化
76
+ this.initEventBind()
77
+ }
78
+
79
+ /** 事件绑定 · 初始化 */
80
+ private initEventBind() {
81
+ // 锚点快速跳转评论
82
+ window.addEventListener('hashchange', () => {
83
+ this.list.checkGoToCommentByUrlHash()
84
+ })
85
+
86
+ // 仅管理员显示控制
87
+ this.ctx.on('check-admin-show-el', () => {
88
+ const items: HTMLElement[] = []
89
+
90
+ this.$root.querySelectorAll<HTMLElement>(`[atk-only-admin-show]`).forEach(item => items.push(item))
91
+
92
+ // for layer
93
+ const { $wrap: $layerWrap } = GetLayerWrap(this.ctx)
94
+ if ($layerWrap)
95
+ $layerWrap.querySelectorAll<HTMLElement>(`[atk-only-admin-show]`).forEach(item => items.push(item))
96
+
97
+ items.forEach(($item: HTMLElement) => {
98
+ if (this.ctx.user.data.isAdmin)
99
+ $item.classList.remove('atk-hide')
100
+ else
101
+ $item.classList.add('atk-hide')
102
+ })
103
+ })
104
+
105
+ // 本地用户数据变更
106
+ this.ctx.on('user-changed', () => {
107
+ this.ctx.trigger('check-admin-show-el')
108
+ this.ctx.trigger('list-refresh-ui')
109
+ })
110
+ }
111
+
112
+ /** 暗黑模式 · 初始化 */
113
+ public initDarkMode() {
114
+ const darkModeClassName = 'atk-dark-mode'
115
+
116
+ if (this.conf.darkMode) {
117
+ this.$root.classList.add(darkModeClassName)
118
+ } else {
119
+ this.$root.classList.remove(darkModeClassName)
120
+ }
121
+
122
+ // for Layer
123
+ const { $wrap: $layerWrap } = GetLayerWrap(this.ctx)
124
+ if ($layerWrap) {
125
+ if (this.conf.darkMode) {
126
+ $layerWrap.classList.add(darkModeClassName)
127
+ } else {
128
+ $layerWrap.classList.remove(darkModeClassName)
129
+ }
130
+ }
131
+ }
132
+
133
+ /** 暗黑模式 · 设定 */
134
+ public setDarkMode(darkMode: boolean) {
135
+ this.ctx.conf.darkMode = darkMode
136
+ this.initDarkMode()
137
+ }
138
+
139
+ /** 监听事件 */
140
+ public on<K extends keyof EventPayloadMap>(name: K, handler: Handler<EventPayloadMap[K]>) {
141
+ this.ctx.on(name, handler, 'external')
142
+ }
143
+
144
+ /** 解除监听事件 */
145
+ public off<K extends keyof EventPayloadMap>(name: K, handler: Handler<EventPayloadMap[K]>) {
146
+ this.ctx.off(name, handler, 'external')
147
+ }
148
+
149
+ /** 触发事件 */
150
+ public trigger<K extends keyof EventPayloadMap>(name: K, payload?: EventPayloadMap[K]) {
151
+ this.ctx.trigger(name, payload, 'external')
152
+ }
153
+ }
@@ -0,0 +1,10 @@
1
+ import Context from '@/context'
2
+ import Component from '@/lib/component'
3
+ import * as Utils from '@/lib/utils'
4
+ import * as Ui from '@/lib/ui'
5
+
6
+ export default class Example extends Component {
7
+ constructor(ctx: Context) {
8
+ super(ctx)
9
+ }
10
+ }