artalk 2.4.1 → 2.4.3

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 (49) hide show
  1. package/dist/Artalk.css +1 -1
  2. package/dist/Artalk.es.js +206 -104
  3. package/dist/Artalk.iife.js +1 -1
  4. package/dist/Artalk.js +1 -1
  5. package/dist/ArtalkLite.css +1 -1
  6. package/dist/ArtalkLite.es.js +206 -104
  7. package/dist/ArtalkLite.iife.js +1 -1
  8. package/dist/ArtalkLite.js +1 -1
  9. package/dist/i18n/jp.js +1 -0
  10. package/dist/i18n/zh-TW.js +1 -0
  11. package/dist/src/api/index.d.ts +1 -1
  12. package/dist/src/api/system.d.ts +7 -0
  13. package/dist/src/api/user.d.ts +16 -2
  14. package/dist/src/artalk.d.ts +0 -6
  15. package/dist/src/context.d.ts +4 -0
  16. package/dist/src/i18n/index.d.ts +68 -74
  17. package/dist/src/list/list-lite.d.ts +17 -7
  18. package/dist/src/list/list.d.ts +1 -0
  19. package/dist/types/artalk-config.d.ts +5 -1
  20. package/dist/types/artalk-data.d.ts +23 -0
  21. package/dist/types/context.d.ts +3 -0
  22. package/dist/types/event.d.ts +1 -1
  23. package/package.json +4 -3
  24. package/scripts/build-i18n.mjs +46 -0
  25. package/src/api/index.ts +3 -2
  26. package/src/api/system.ts +7 -0
  27. package/src/api/user.ts +59 -2
  28. package/src/artalk.ts +0 -35
  29. package/src/comment/render.ts +2 -1
  30. package/src/context.ts +33 -3
  31. package/src/defaults.ts +2 -2
  32. package/src/editor/editor.ts +8 -8
  33. package/src/editor/plugs/upload-plug.ts +1 -1
  34. package/src/i18n/index.ts +18 -90
  35. package/src/i18n/jp.ts +83 -0
  36. package/src/i18n/zh-TW.ts +83 -0
  37. package/src/layer/sidebar-layer.ts +10 -8
  38. package/src/lib/checker/admin-checker.ts +4 -1
  39. package/src/lib/checker/captcha-checker.ts +1 -0
  40. package/src/lib/stat.ts +9 -1
  41. package/src/lib/utils.ts +8 -1
  42. package/src/list/list-lite.ts +68 -26
  43. package/src/list/list.ts +10 -18
  44. package/src/style/main.less +17 -0
  45. package/test/ArtalkI18n.html +1 -0
  46. package/types/artalk-config.d.ts +5 -1
  47. package/types/artalk-data.d.ts +23 -0
  48. package/types/context.d.ts +3 -0
  49. package/types/event.d.ts +1 -1
@@ -0,0 +1 @@
1
+ !function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(e="undefined"!=typeof globalThis?globalThis:e||self).jp=n()}(this,(function(){"use strict";var e,n,o;return e="jp",n={placeholder:"コメントを残す",noComment:"コメントなし",send:"送信",save:"保存",nick:"ニックネーム",email:"メール",link:"ウェブサイト",emoticon:"絵文字",preview:"プレビュー",image:"画像",uploadFail:"アップロードに失敗しました",commentFail:"コメントに失敗しました",restoredMsg:"コンテンツが復元されました",onlyAdminCanReply:"管理者のみが返信できます",uploadLoginMsg:"アップロードするには、名前とメールアドレスを入力してください",counter:"{count} コメント",sortLatest:"最新のもの",sortOldest:"最も古い",sortBest:"ベスト",sortAuthor:"著者",openComment:"コメントを開く",closeComment:"コメントを閉じる",listLoadFailMsg:"コメントの読み込みに失敗しました",listRetry:"クリックで再試行",loadMore:"もっと読み込む",admin:"管理者",reply:"返信",voteUp:"アップ",voteDown:"ダウン",voteFail:"投票に失敗しました",readMore:"続きを読む",actionConfirm:"確認",collapse:"折りたたむ",collapsed:"折りたたまれた",collapsedMsg:"このコメントは折りたたまれました",expand:"展開",approved:"承認されました",pending:"保留中",pendingMsg:"保留中、コメント投稿者にのみ表示されます",edit:"編集",editCancel:"編集をキャンセル",delete:"削除",deleteConfirm:"確認",pin:"ピン",unpin:"ピン外し",seconds:"秒前",minutes:"分前",hours:"時間前",days:"数日前",now:"たった今",adminCheck:"管理者パスワードを入力してください:",captchaCheck:"続けるにはCAPTCHAを入力してください:",confirm:"確認",cancel:"取消",msgCenter:"メッセージ",ctrlCenter:"コンソール",frontend:"フロントエンド",backend:"バックエンド",loading:"読み込み中",loadFail:"読み込みに失敗しました",editing:"編集中",editFail:"編集に失敗しました",deleting:"削除中",deleteFail:"削除に失敗しました",reqGot:"リクエストを取得しました",reqAborted:"リクエストがタイムアウトしたか、予期せず終了した"},o=["jp-JP"],window.ArtalkI18n||(window.ArtalkI18n={}),window.ArtalkI18n[e]=n,o&&o.forEach((e=>{window.ArtalkI18n[e]=n})),n}));
@@ -0,0 +1 @@
1
+ !function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(e="undefined"!=typeof globalThis?globalThis:e||self)["zh-TW"]=n()}(this,(function(){"use strict";var e,n,o;return e="zh-TW",n={placeholder:"鍵入內容...",noComment:"「此時無聲勝有聲」",send:"發送評論",save:"保存評論",nick:"昵稱",email:"郵箱",link:"網址",emoticon:"表情",preview:"預覽",image:"圖片",uploadFail:"上傳失敗",commentFail:"評論失敗",restoredMsg:"內容已自動恢復",onlyAdminCanReply:"僅管理員可評論",uploadLoginMsg:"填入你的名字郵箱才能上傳哦",counter:"{count} 條評論",sortLatest:"最新",sortOldest:"最早",sortBest:"最熱",sortAuthor:"作者",openComment:"打開評論",closeComment:"關閉評論",listLoadFailMsg:"無法獲取評論列表數據",listRetry:"點擊重新獲取",loadMore:"加載更多",admin:"管理員",reply:"回覆",voteUp:"贊同",voteDown:"反對",voteFail:"投票失敗",readMore:"閱讀更多",actionConfirm:"確認操作",collapse:"摺疊",collapsed:"已摺疊",collapsedMsg:"該評論已被系統或管理員摺疊",expand:"展開",approved:"已審",pending:"待審",pendingMsg:"審核中,僅本人可見。",edit:"編輯",editCancel:"取消編輯",delete:"刪除",deleteConfirm:"確認刪除",pin:"置頂",unpin:"取消置頂",seconds:"秒前",minutes:"分鐘前",hours:"小時前",days:"天前",now:"剛剛",adminCheck:"鍵入密碼來驗證管理員身份:",captchaCheck:"鍵入驗證碼繼續:",confirm:"確認",cancel:"取消",msgCenter:"通知中心",ctrlCenter:"控制中心",frontend:"前端",backend:"後端",loading:"加載中",loadFail:"加載失敗",editing:"修改中",editFail:"修改失敗",deleting:"刪除中",deleteFail:"刪除失敗",reqGot:"請求響應",reqAborted:"請求超時或意外終止"},window.ArtalkI18n||(window.ArtalkI18n={}),window.ArtalkI18n[e]=n,o&&o.forEach((e=>{window.ArtalkI18n[e]=n})),n}));
@@ -19,7 +19,7 @@ declare const ApiComponents: {
19
19
  };
20
20
  declare class Api {
21
21
  protected ctx: Context;
22
- baseURL: string;
22
+ get baseURL(): string;
23
23
  constructor(ctx: Context);
24
24
  }
25
25
  declare type TC = typeof ApiComponents;
@@ -10,4 +10,11 @@ export default class SystemApi extends ApiBase {
10
10
  getSettings(): Promise<string>;
11
11
  /** 保存配置数据 */
12
12
  saveSettings(yamlStr: string): Promise<boolean>;
13
+ /** 获取 API 版本信息 */
14
+ version(): Promise<{
15
+ app: string;
16
+ version: string;
17
+ commit_hash: string;
18
+ fe_min_version: string;
19
+ }>;
13
20
  }
@@ -1,11 +1,14 @@
1
- import { UserData, NotifyData } from '../../types/artalk-data';
1
+ import { UserData, NotifyData, UserDataForAdmin } from '../../types/artalk-data';
2
2
  import ApiBase from './api-base';
3
3
  /**
4
4
  * 用户 API
5
5
  */
6
6
  export default class UserApi extends ApiBase {
7
7
  /** 用户 · 登录 */
8
- login(name: string, email: string, password: string): Promise<string>;
8
+ login(name: string, email: string, password: string): Promise<{
9
+ token: string;
10
+ user: UserData;
11
+ }>;
9
12
  /** 用户 · 获取 */
10
13
  userGet(name: string, email: string): {
11
14
  req: Promise<{
@@ -25,4 +28,15 @@ export default class UserApi extends ApiBase {
25
28
  logout(): Promise<unknown>;
26
29
  /** 已读标记 */
27
30
  markRead(notifyKey: string, readAll?: boolean): Promise<unknown>;
31
+ /** 用户 · 列表 */
32
+ userList(offset?: number, limit?: number, type?: 'all' | 'admin' | 'in_conf'): Promise<{
33
+ users: UserDataForAdmin[];
34
+ total: number;
35
+ }>;
36
+ /** 用户 · 新增 */
37
+ userAdd(user: Partial<UserDataForAdmin>, password?: string): Promise<UserDataForAdmin>;
38
+ /** 用户 · 修改 */
39
+ userEdit(user: Partial<UserDataForAdmin>, password?: string): Promise<UserDataForAdmin>;
40
+ /** 用户 · 删除 */
41
+ userDel(userID: number): Promise<unknown>;
28
42
  }
@@ -25,18 +25,12 @@ export default class Artalk {
25
25
  private initComponents;
26
26
  /** 基本配置初始化 */
27
27
  static HandelBaseConf(customConf: Partial<ArtalkConfig>): ArtalkConfig;
28
- /** 初始化组件根据远程加载的配置 */
29
- private loadConfRemoteAndInitComponents;
30
- /** 远程加载配置 */
31
- private loadConfRemote;
32
28
  /** 事件绑定初始化 */
33
29
  private initEventBind;
34
30
  /** 语言初始化 */
35
31
  private initLocale;
36
32
  /** Layer 初始化 */
37
33
  private initLayer;
38
- /** 暗黑模式初始化 */
39
- private initDarkMode;
40
34
  /** 监听事件 */
41
35
  on<K extends keyof EventPayloadMap>(name: K, handler: Handler<EventPayloadMap[K]>): void;
42
36
  /** 解除监听事件 */
@@ -69,4 +69,8 @@ export default class Context implements ContextApi {
69
69
  [key: string]: string;
70
70
  }): string;
71
71
  setDarkMode(darkMode: boolean): void;
72
+ private darkModeMedia;
73
+ private darkModeAutoFunc?;
74
+ private refreshDarkModeConf;
75
+ updateConf(conf: Partial<ArtalkConfig>): void;
72
76
  }
@@ -206,79 +206,73 @@ export declare const internal: {
206
206
  reqAborted: string;
207
207
  };
208
208
  };
209
- export declare const external: {
210
- 'jp-JP': {};
211
- 'zh-TW': {
212
- placeholder: string;
213
- noComment: string;
214
- send: string;
215
- save: string;
216
- nick: string;
217
- email: string;
218
- link: string;
219
- emoticon: string;
220
- preview: string;
221
- image: string;
222
- uploadFail: string;
223
- commentFail: string;
224
- restoredMsg: string;
225
- onlyAdminCanReply: string;
226
- uploadLoginMsg: string;
227
- counter: string;
228
- sortLatest: string;
229
- sortOldest: string;
230
- sortBest: string;
231
- sortAuthor: string;
232
- openComment: string;
233
- closeComment: string;
234
- listLoadFailMsg: string;
235
- listRetry: string;
236
- loadMore: string;
237
- admin: string;
238
- reply: string;
239
- voteUp: string;
240
- voteDown: string;
241
- voteFail: string;
242
- readMore: string;
243
- actionConfirm: string;
244
- collapse: string;
245
- collapsed: string;
246
- collapsedMsg: string;
247
- expand: string;
248
- approved: string;
249
- pending: string;
250
- pendingMsg: string;
251
- edit: string;
252
- editCancel: string;
253
- delete: string;
254
- deleteConfirm: string;
255
- pin: string;
256
- unpin: string;
257
- seconds: string;
258
- minutes: string;
259
- hours: string;
260
- days: string;
261
- now: string;
262
- adminCheck: string;
263
- captchaCheck: string;
264
- confirm: string;
265
- cancel: string;
266
- msgCenter: string;
267
- ctrlCenter: string;
268
- frontend: string;
269
- backend: string;
270
- loading: string;
271
- loadFail: string;
272
- editing: string;
273
- editFail: string;
274
- deleting: string;
275
- deleteFail: string;
276
- reqGot: string;
277
- reqAborted: string;
278
- };
279
- 'fr-FR': {};
280
- 'de-DE': {};
281
- 'bn-IN': {};
209
+ export declare function defineLocaleExternal(lang: string, locale: I18n, aliases?: string[]): {
210
+ placeholder: string;
211
+ noComment: string;
212
+ send: string;
213
+ save: string;
214
+ nick: string;
215
+ email: string;
216
+ link: string;
217
+ emoticon: string;
218
+ preview: string;
219
+ image: string;
220
+ uploadFail: string;
221
+ commentFail: string;
222
+ restoredMsg: string;
223
+ onlyAdminCanReply: string;
224
+ uploadLoginMsg: string;
225
+ counter: string;
226
+ sortLatest: string;
227
+ sortOldest: string;
228
+ sortBest: string;
229
+ sortAuthor: string;
230
+ openComment: string;
231
+ closeComment: string;
232
+ listLoadFailMsg: string;
233
+ listRetry: string;
234
+ loadMore: string;
235
+ admin: string;
236
+ reply: string;
237
+ voteUp: string;
238
+ voteDown: string;
239
+ voteFail: string;
240
+ readMore: string;
241
+ actionConfirm: string;
242
+ collapse: string;
243
+ collapsed: string;
244
+ collapsedMsg: string;
245
+ expand: string;
246
+ approved: string;
247
+ pending: string;
248
+ pendingMsg: string;
249
+ edit: string;
250
+ editCancel: string;
251
+ delete: string;
252
+ deleteConfirm: string;
253
+ pin: string;
254
+ unpin: string;
255
+ seconds: string;
256
+ minutes: string;
257
+ hours: string;
258
+ days: string;
259
+ now: string;
260
+ adminCheck: string;
261
+ captchaCheck: string;
262
+ confirm: string;
263
+ cancel: string;
264
+ msgCenter: string;
265
+ ctrlCenter: string;
266
+ frontend: string;
267
+ backend: string;
268
+ loading: string;
269
+ loadFail: string;
270
+ editing: string;
271
+ editFail: string;
272
+ deleting: string;
273
+ deleteFail: string;
274
+ reqGot: string;
275
+ reqAborted: string;
282
276
  };
283
277
  /**
284
278
  * get a locale object by language name
@@ -290,5 +284,5 @@ declare function getLocaleSet(lang: string): I18n;
290
284
  declare function getI18n(locale: I18n | string, key: keyof I18n, args?: {
291
285
  [key: string]: string;
292
286
  }): string;
293
- export { getLocaleSet, getI18n };
287
+ export { getLocaleSet };
294
288
  export default getI18n;
@@ -9,14 +9,23 @@ export default class ListLite extends Component {
9
9
  protected $commentsWrap: HTMLElement;
10
10
  protected data?: ListData;
11
11
  protected isLoading: boolean;
12
- noCommentText: string;
12
+ noCommentText?: string;
13
+ /** 嵌套模式下的排序方式 */
14
+ private _nestSortBy?;
15
+ get nestSortBy(): ListNest.SortByType;
16
+ set nestSortBy(val: ListNest.SortByType);
13
17
  /** 平铺模式 */
14
- flatMode: boolean;
15
- /** 嵌套模式 */
16
- nestSortBy: ListNest.SortByType;
17
- /** 分页 */
18
- pageMode: 'pagination' | 'read-more';
19
- pageSize: number;
18
+ private _flatMode?;
19
+ get flatMode(): boolean;
20
+ set flatMode(val: boolean);
21
+ /** 分页方式 */
22
+ _pageMode?: 'pagination' | 'read-more';
23
+ get pageMode(): 'pagination' | 'read-more';
24
+ set pageMode(val: 'pagination' | 'read-more');
25
+ /** 每页数量 (每次请求获取量) */
26
+ private _pageSize?;
27
+ get pageSize(): number;
28
+ set pageSize(val: number);
20
29
  scrollListenerAt?: HTMLElement;
21
30
  repositionAt?: HTMLElement;
22
31
  protected pagination?: Pagination;
@@ -35,6 +44,7 @@ export default class ListLite extends Component {
35
44
  setLoading(val: boolean, isFirstLoad?: boolean): void;
36
45
  /** 评论获取 */
37
46
  fetchComments(offset: number): Promise<void>;
47
+ private confLoaded;
38
48
  protected onLoad(data: ListData, offset: number): void;
39
49
  /** 分页模式 */
40
50
  private refreshPagination;
@@ -20,6 +20,7 @@ export default class List extends ListLite {
20
20
  /** 管理员设置页面信息 */
21
21
  adminPageEditSave(): void;
22
22
  showUnreadBadge(count: number): void;
23
+ private dropdownLoaded;
23
24
  /** 初始化选择下拉层 */
24
25
  protected initDropdown(): void;
25
26
  updateUnread(notifies: NotifyData[]): void;
@@ -1,4 +1,5 @@
1
1
  import { I18n } from '../src/i18n'
2
+ import { CommentData } from './artalk-data'
2
3
 
3
4
  export default interface ArtalkConfig {
4
5
  /** 装载元素 */
@@ -39,6 +40,9 @@ export default interface ArtalkConfig {
39
40
  default: string
40
41
  }
41
42
 
43
+ /** 头像链接生成器 */
44
+ avatarURLBuilder?: (comment: CommentData) => string,
45
+
42
46
  /** 分页配置 */
43
47
  pagination: {
44
48
  /** 每次请求获取数量 */
@@ -103,7 +107,7 @@ export default interface ArtalkConfig {
103
107
  /** 版本检测 */
104
108
  versionCheck: boolean
105
109
 
106
- /** 应用后端配置 */
110
+ /** 引用后端配置 */
107
111
  useBackendConf: boolean
108
112
 
109
113
  /** 语言本地化 */
@@ -159,6 +159,29 @@ export interface UserData {
159
159
 
160
160
  /** 是否属于管理员 */
161
161
  is_admin: boolean
162
+
163
+ /** 所属站点名 */
164
+ site_names: string[]
165
+
166
+ /** 所属站点名 (字符串) */
167
+ site_names_raw: string,
168
+
169
+ /** 是否允许接收邮件通知 */
170
+ receive_email: boolean
171
+ }
172
+
173
+ export interface UserDataForAdmin extends UserData {
174
+ /** 最后一次操作 IP */
175
+ last_ip: string
176
+
177
+ /** 最后一次操作 UA */
178
+ last_ua: string
179
+
180
+ /** 是否存在于配置文件中 */
181
+ is_in_conf: boolean
182
+
183
+ /** 评论数 */
184
+ comment_count: number
162
185
  }
163
186
 
164
187
  export interface NotifyData {
@@ -143,4 +143,7 @@ export default interface ContextApi {
143
143
 
144
144
  /** 设置暗黑模式 */
145
145
  setDarkMode(darkMode: boolean): void
146
+
147
+ /** 更新配置 */
148
+ updateConf(conf: Partial<ArtalkConfig>): void
146
149
  }
@@ -9,7 +9,7 @@ export interface EventPayloadMap {
9
9
  'editor-submit': undefined // 编辑器提交时
10
10
  'editor-submitted': undefined // 编辑器提交后
11
11
  'user-changed': LocalUser // 本地用户数据变更时
12
- 'conf-updated': undefined // Artalk 配置变更时
12
+ 'conf-loaded': undefined // Artalk 配置变更时
13
13
  'sidebar-show': undefined // 侧边栏显示
14
14
  'sidebar-hide': undefined // 侧边栏隐藏
15
15
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "artalk",
3
- "version": "2.4.1",
4
- "backendMinVersion": "2.2.0",
3
+ "version": "2.4.3",
4
+ "backendMinVersion": "2.3.0",
5
5
  "license": "LGPL-3.0",
6
6
  "description": "A Selfhosted Comment System",
7
7
  "keywords": [
@@ -34,8 +34,9 @@
34
34
  },
35
35
  "scripts": {
36
36
  "dev": "vite",
37
- "build": "pnpm lint && vite build && pnpm build:lite",
37
+ "build": "pnpm lint && vite build && pnpm build:lite && pnpm build:i18n",
38
38
  "build:lite": "vite build --config vite-lite.config.ts",
39
+ "build:i18n": "node scripts/build-i18n.mjs",
39
40
  "build:demo": "vite build --config vite-demo.config.ts",
40
41
  "serve": "vite preview",
41
42
  "deploy": "gh-pages -d deploy",
@@ -0,0 +1,46 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+ import { build } from 'vite'
4
+ import { fileURLToPath } from 'url'
5
+
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
7
+ const i18nPath = path.join(__dirname, '../src/i18n/')
8
+ const outDir = path.resolve(__dirname, '../dist/i18n')
9
+
10
+ // empty outDir before build
11
+ fs.rmSync(outDir, { recursive: true, force: true })
12
+
13
+ const libraries = []
14
+
15
+ fs.readdirSync(i18nPath).forEach(f => {
16
+ if (['index.ts'].includes(f)) return
17
+
18
+ const filename = path.join(i18nPath, f)
19
+ const lang = path.parse(filename).name
20
+ const content = fs.readFileSync(filename);
21
+
22
+ // only compile the external locale
23
+ if (!content.toString().includes('defineLocaleExternal')) return
24
+
25
+ libraries.push({
26
+ entry: filename,
27
+ name: lang,
28
+ fileName: (format) => ((format == "umd") ? `${lang}.js` : `${lang}.${format}.js`),
29
+ })
30
+ })
31
+
32
+ libraries.forEach(async (lib) => {
33
+ await build({
34
+ build: {
35
+ target: 'es2015',
36
+ outDir: outDir,
37
+ minify: 'terser',
38
+ emptyOutDir: false,
39
+ lib: {
40
+ ...lib,
41
+ formats: ['umd'],
42
+ },
43
+ },
44
+ configFile: false, // prevent load any vite config file
45
+ })
46
+ })
package/src/api/index.ts CHANGED
@@ -18,11 +18,12 @@ const ApiComponents = {
18
18
 
19
19
  class Api {
20
20
  protected ctx: Context
21
- public baseURL: string
21
+ public get baseURL() {
22
+ return `${this.ctx.conf.server}/api`
23
+ }
22
24
 
23
25
  constructor (ctx: Context) {
24
26
  this.ctx = ctx
25
- this.baseURL = `${ctx.conf.server}/api`
26
27
 
27
28
  Object.entries(ApiComponents).forEach(([key, ApiComponent]) => {
28
29
  this[key] = new ApiComponent(this, this.ctx)
package/src/api/system.ts CHANGED
@@ -36,4 +36,11 @@ export default class SystemApi extends ApiBase {
36
36
  })
37
37
  return data
38
38
  }
39
+
40
+ /** 获取 API 版本信息 */
41
+ public async version() {
42
+ const resp = await fetch(`${this.api.baseURL}/version`, { method: 'POST' })
43
+ const data = await resp.json()
44
+ return data as { app: string, version: string, commit_hash: string, fe_min_version: string }
45
+ }
39
46
  }
package/src/api/user.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { UserData, NotifyData } from '~/types/artalk-data'
1
+ import { UserData, NotifyData, UserDataForAdmin } from '~/types/artalk-data'
2
2
  import ApiBase from './api-base'
3
3
  import { ToFormData } from './request'
4
4
 
@@ -15,7 +15,7 @@ export default class UserApi extends ApiBase {
15
15
  if (this.ctx.conf.site) params.site_name = this.ctx.conf.site
16
16
 
17
17
  const data = await this.POST<any>('/login', params)
18
- return (data.token as string)
18
+ return data as { token: string, user: UserData }
19
19
  }
20
20
 
21
21
  /** 用户 · 获取 */
@@ -72,4 +72,61 @@ export default class UserApi extends ApiBase {
72
72
 
73
73
  return this.POST(`/mark-read`, params)
74
74
  }
75
+
76
+ /** 用户 · 列表 */
77
+ public async userList(offset?: number, limit?: number, type?: 'all'|'admin'|'in_conf') {
78
+ const params: any = {
79
+ offset: offset || 0,
80
+ limit: limit || 15,
81
+ }
82
+
83
+ if (type) params.type = type
84
+
85
+ const d = await this.POST<any>('/admin/user-get', params)
86
+ return (d as { users: UserDataForAdmin[], total: number })
87
+ }
88
+
89
+ /** 用户 · 新增 */
90
+ public async userAdd(user: Partial<UserDataForAdmin>, password?: string) {
91
+ const params: any = {
92
+ name: user.name || '',
93
+ email: user.email || '',
94
+ password: password || '',
95
+ link: user.link || '',
96
+ is_admin: user.is_admin || false,
97
+ site_names: user.site_names_raw || '',
98
+ receive_email: user.receive_email || true,
99
+ badge_name: user.badge_name || '',
100
+ badge_color: user.badge_color || '',
101
+ }
102
+
103
+ const d = await this.POST<any>('/admin/user-add', params)
104
+ return (d.user as UserDataForAdmin)
105
+ }
106
+
107
+ /** 用户 · 修改 */
108
+ public async userEdit(user: Partial<UserDataForAdmin>, password?: string) {
109
+ const params: any = {
110
+ id: user.id,
111
+ name: user.name || '',
112
+ email: user.email || '',
113
+ password: password || '',
114
+ link: user.link || '',
115
+ is_admin: user.is_admin || false,
116
+ site_names: user.site_names_raw || '',
117
+ receive_email: user.receive_email || true,
118
+ badge_name: user.badge_name || '',
119
+ badge_color: user.badge_color || '',
120
+ }
121
+
122
+ const d = await this.POST<any>('/admin/user-edit', params)
123
+ return (d.user as UserDataForAdmin)
124
+ }
125
+
126
+ /** 用户 · 删除 */
127
+ public userDel(userID: number) {
128
+ return this.POST('/admin/user-del', {
129
+ id: String(userID)
130
+ })
131
+ }
75
132
  }
package/src/artalk.ts CHANGED
@@ -48,9 +48,6 @@ export default class Artalk {
48
48
  /* 初始化 Context */
49
49
  this.ctx = new ConcreteContext(this.conf, this.$root)
50
50
 
51
- // @ts-ignore 远程加载引用后端的配置
52
- if (this.conf.useBackendConf) return this.loadConfRemoteAndInitComponents()
53
-
54
51
  /* 初始化组件 */
55
52
  this.initComponents()
56
53
  }
@@ -59,7 +56,6 @@ export default class Artalk {
59
56
  private initComponents() {
60
57
  this.initLocale()
61
58
  this.initLayer()
62
- this.initDarkMode()
63
59
  Utils.initMarked(this.ctx)
64
60
 
65
61
  const Components: { [name: string]: () => void } = {
@@ -91,7 +87,6 @@ export default class Artalk {
91
87
  sidebarLayer: () => {
92
88
  const sidebarLayer = new SidebarLayer(this.ctx)
93
89
  this.ctx.setSidebarLayer(sidebarLayer)
94
- this.$root.appendChild(sidebarLayer.$el)
95
90
  },
96
91
 
97
92
  // 默认事件绑定
@@ -148,24 +143,6 @@ export default class Artalk {
148
143
  return conf
149
144
  }
150
145
 
151
- /** 初始化组件根据远程加载的配置 */
152
- private async loadConfRemoteAndInitComponents() {
153
- await this.loadConfRemote()
154
- this.initComponents()
155
- return this
156
- }
157
-
158
- /** 远程加载配置 */
159
- private async loadConfRemote() {
160
- Ui.showLoading(this.$root)
161
- let backendConf = {}
162
- try {
163
- backendConf = await this.ctx.getApi().system.conf()
164
- } catch (err) { console.error("Load config from remote err", err) }
165
- this.ctx.conf = Utils.mergeDeep(this.ctx.conf, backendConf)
166
- Ui.hideLoading(this.$root)
167
- }
168
-
169
146
  /** 事件绑定初始化 */
170
147
  private initEventBind() {
171
148
  // 锚点快速跳转评论
@@ -194,18 +171,6 @@ export default class Artalk {
194
171
  Layer.BodyOrgPaddingRight = document.body.style.paddingRight
195
172
  }
196
173
 
197
- /** 暗黑模式初始化 */
198
- private initDarkMode() {
199
- if (this.conf.darkMode === 'auto') {
200
- // 自动切换暗黑模式,事件监听
201
- const darkModeMedia = window.matchMedia('(prefers-color-scheme: dark)')
202
- darkModeMedia.addEventListener('change', (e) => { this.setDarkMode(e.matches) })
203
- this.setDarkMode(darkModeMedia.matches)
204
- } else {
205
- this.setDarkMode(this.conf.darkMode || false)
206
- }
207
- }
208
-
209
174
  /** 监听事件 */
210
175
  public on<K extends keyof EventPayloadMap>(name: K, handler: Handler<EventPayloadMap[K]>) {
211
176
  this.ctx.on(name, handler, 'external')
@@ -58,7 +58,8 @@ export default class CommentRender {
58
58
  private renderAvatar() {
59
59
  const $avatar = this.$el.querySelector<HTMLElement>('.atk-avatar')!
60
60
  const $avatarImg = Utils.createElement<HTMLImageElement>('<img />')
61
- $avatarImg.src = this.comment.getGravatarURL()
61
+ const avatarURLBuilder = this.ctx.conf.avatarURLBuilder
62
+ $avatarImg.src = avatarURLBuilder ? avatarURLBuilder(this.data) : this.comment.getGravatarURL()
62
63
  if (this.data.link) {
63
64
  const $avatarA = Utils.createElement<HTMLLinkElement>('<a target="_blank" rel="noreferrer noopener nofollow"></a>')
64
65
  $avatarA.href = Utils.isValidURL(this.data.link) ? this.data.link : `https://${this.data.link}`