artalk 2.2.12 → 2.3.2

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 (78) hide show
  1. package/dist/Artalk.css +1 -1
  2. package/dist/Artalk.es.js +1535 -1175
  3. package/dist/Artalk.iife.js +1 -1
  4. package/dist/Artalk.js +1 -1
  5. package/dist/ArtalkLite.css +1 -0
  6. package/dist/ArtalkLite.es.js +5123 -0
  7. package/dist/ArtalkLite.iife.js +1 -0
  8. package/dist/ArtalkLite.js +1 -0
  9. package/index.html +2 -2
  10. package/package.json +6 -5
  11. package/src/api/index.ts +20 -1
  12. package/src/api/request.ts +7 -7
  13. package/src/artalk.ts +117 -121
  14. package/src/comment/actions.ts +86 -0
  15. package/src/{components/html → comment}/comment.html +1 -1
  16. package/src/comment/comment.ts +191 -0
  17. package/src/comment/index.ts +3 -0
  18. package/src/comment/render.ts +425 -0
  19. package/src/components/action-btn.ts +6 -3
  20. package/src/components/dialog.ts +7 -3
  21. package/src/components/read-more-btn.ts +6 -2
  22. package/src/context.ts +204 -13
  23. package/src/defaults.ts +16 -5
  24. package/src/editor/editor.html +18 -0
  25. package/src/editor/editor.ts +419 -0
  26. package/src/editor/index.ts +3 -0
  27. package/src/editor/plugs/editor-plug.ts +51 -0
  28. package/src/{components/editor-plugs → editor/plugs}/emoticons-plug.less +2 -1
  29. package/src/{components/editor-plugs → editor/plugs}/emoticons-plug.ts +65 -63
  30. package/src/editor/plugs/header-input-plug.ts +78 -0
  31. package/src/{components/editor-plugs → editor/plugs}/preview-plug.less +0 -0
  32. package/src/editor/plugs/preview-plug.ts +40 -0
  33. package/src/editor/plugs/upload-plug.ts +124 -0
  34. package/src/i18n/en.ts +78 -0
  35. package/src/i18n/index.ts +21 -0
  36. package/src/i18n/zh-CN.ts +82 -0
  37. package/src/layer/index.ts +4 -0
  38. package/src/{components → layer}/layer.ts +9 -9
  39. package/src/{components/html → layer}/sidebar-layer.html +0 -0
  40. package/src/{components → layer}/sidebar-layer.ts +17 -47
  41. package/src/lib/checker/admin-checker.ts +7 -7
  42. package/src/lib/checker/captcha-checker.ts +5 -6
  43. package/src/lib/checker/index.ts +22 -17
  44. package/src/lib/component.ts +7 -2
  45. package/src/lib/stat.ts +63 -0
  46. package/src/lib/ui.ts +6 -12
  47. package/src/lib/user.ts +20 -3
  48. package/src/lib/utils.ts +23 -9
  49. package/src/list/index.ts +3 -0
  50. package/src/{components → list}/list-lite.ts +80 -100
  51. package/src/{components → list}/list-nest.ts +0 -0
  52. package/src/{components/html → list}/list.html +3 -6
  53. package/src/{components → list}/list.ts +44 -42
  54. package/src/style/_variables.less +1 -2
  55. package/src/style/comment.less +26 -13
  56. package/src/style/editor.less +22 -14
  57. package/src/style/list.less +6 -9
  58. package/src/style/main.less +9 -8
  59. package/src/style/pagination.less +1 -1
  60. package/test/ArtalkCount.html +16 -0
  61. package/test/ArtalkI18n.html +15 -0
  62. package/test/ArtalkLite.html +15 -0
  63. package/types/artalk-config.d.ts +50 -32
  64. package/types/context.d.ts +128 -0
  65. package/types/event.d.ts +6 -66
  66. package/types/global.d.ts +1 -0
  67. package/types/plug.d.ts +4 -0
  68. package/{vite-common.config.ts → vite-base.config.ts} +6 -4
  69. package/vite-demo.config.ts +2 -4
  70. package/vite-lite.config.ts +22 -0
  71. package/vite.config.ts +9 -12
  72. package/src/components/_component.ts.txt +0 -10
  73. package/src/components/comment.ts +0 -605
  74. package/src/components/editor-plugs/_example-plug.ts.txt +0 -29
  75. package/src/components/editor-plugs/editor-plug.ts +0 -20
  76. package/src/components/editor-plugs/preview-plug.ts +0 -48
  77. package/src/components/editor.ts +0 -607
  78. package/src/components/html/editor.html +0 -18
@@ -1,4 +1,4 @@
1
- import Context from '../context'
1
+ import Context from '~/types/context'
2
2
  import Component from '../lib/component'
3
3
  import * as Utils from '../lib/utils'
4
4
  import * as Ui from '../lib/ui'
@@ -40,19 +40,19 @@ export default class Layer extends Component {
40
40
  this.$wrap.append(this.$el)
41
41
  }
42
42
 
43
- getName () {
43
+ getName() {
44
44
  return this.name
45
45
  }
46
46
 
47
- getWrapEl () {
47
+ getWrapEl() {
48
48
  return this.$wrap
49
49
  }
50
50
 
51
- getEl () {
51
+ getEl() {
52
52
  return this.$el
53
53
  }
54
54
 
55
- show () {
55
+ show() {
56
56
  this.fireAllActionTimer()
57
57
 
58
58
  this.$wrap.style.display = 'block'
@@ -126,7 +126,7 @@ export default class Layer extends Component {
126
126
  }
127
127
 
128
128
  /** 销毁 - 无动画 */
129
- disposeNow () {
129
+ disposeNow() {
130
130
  this.$el.remove()
131
131
  this.pageBodyScrollBarShow()
132
132
  // this.$el dispose
@@ -134,21 +134,21 @@ export default class Layer extends Component {
134
134
  }
135
135
 
136
136
  /** 销毁 */
137
- dispose () {
137
+ dispose() {
138
138
  this.hide()
139
139
  this.$el.remove()
140
140
  // this.$el dispose
141
141
  this.checkCleanLayer()
142
142
  }
143
143
 
144
- checkCleanLayer () {
144
+ checkCleanLayer() {
145
145
  if (this.getWrapEl().querySelectorAll('.atk-layer-item').length === 0) {
146
146
  this.$wrap.style.display = 'none'
147
147
  }
148
148
  }
149
149
  }
150
150
 
151
- export function GetLayerWrap (ctx: Context): { $wrap: HTMLElement, $mask: HTMLElement } {
151
+ export function GetLayerWrap(ctx: Context): { $wrap: HTMLElement, $mask: HTMLElement } {
152
152
  let $wrap = document.querySelector<HTMLElement>(`.atk-layer-wrap#ctx-${ctx.cid}`)
153
153
  if (!$wrap) {
154
154
  $wrap = Utils.createElement(
@@ -1,13 +1,15 @@
1
1
  import '../style/sidebar-layer.less'
2
2
 
3
- import Context from '@/context'
3
+ import Context from '~/types/context'
4
4
  import Component from '@/lib/component'
5
5
  import * as Utils from '@/lib/utils'
6
6
  import * as Ui from '@/lib/ui'
7
- import { SidebarShowPayload } from '~/types/event'
8
- import SidebarHTML from './html/sidebar-layer.html?raw'
7
+ import SidebarHTML from './sidebar-layer.html?raw'
9
8
  import Layer from './layer'
10
- import Api from '../api'
9
+
10
+ export interface SidebarShowPayload {
11
+ view?: 'comments'|'sites'|'pages'|'transfer'
12
+ }
11
13
 
12
14
  export default class SidebarLayer extends Component {
13
15
  public layer?: Layer
@@ -29,15 +31,13 @@ export default class SidebarLayer extends Component {
29
31
  }
30
32
 
31
33
  // event
32
- this.ctx.on('sidebar-show', (conf) => (this.show(conf || {})))
33
- this.ctx.on('sidebar-hide', () => (this.hide()))
34
34
  this.ctx.on('user-changed', () => { this.firstShow = true })
35
35
  }
36
36
 
37
37
  private firstShow = true
38
38
 
39
39
  /** 显示 */
40
- public async show(conf: SidebarShowPayload) {
40
+ public async show(conf: SidebarShowPayload = {}) {
41
41
  this.$el.style.transform = '' // 动画清除,防止二次打开失效
42
42
 
43
43
  // 获取 Layer
@@ -46,7 +46,7 @@ export default class SidebarLayer extends Component {
46
46
  this.layer.afterHide = () => {
47
47
  // 防止评论框被吞
48
48
  if (this.ctx.conf.editorTravel === true) {
49
- this.ctx.trigger('editor-travel-back')
49
+ this.ctx.editorTravelBack()
50
50
  }
51
51
  }
52
52
  }
@@ -62,12 +62,12 @@ export default class SidebarLayer extends Component {
62
62
 
63
63
  // 管理员身份验证 (若身份失效,弹出验证窗口)
64
64
  ;(async () => {
65
- const resp = await new Api(this.ctx).loginStatus()
65
+ const resp = await this.ctx.getApi().loginStatus()
66
66
  if (resp.is_admin && !resp.is_login) {
67
67
  this.layer?.hide()
68
68
  this.firstShow = true
69
69
 
70
- this.ctx.trigger('checker-admin', {
70
+ this.ctx.checkAdmin({
71
71
  onSuccess: () => {
72
72
  setTimeout(() => {
73
73
  this.show(conf)
@@ -79,7 +79,7 @@ export default class SidebarLayer extends Component {
79
79
  })()
80
80
 
81
81
  // 清空 unread
82
- this.ctx.trigger('unread-update', { notifies: [] })
82
+ this.ctx.updateNotifies([])
83
83
 
84
84
  // 第一次加载
85
85
  if (this.firstShow) {
@@ -87,7 +87,7 @@ export default class SidebarLayer extends Component {
87
87
  this.$iframe = Utils.createElement<HTMLIFrameElement>('<iframe></iframe>')
88
88
 
89
89
  // 准备 Iframe 参数
90
- const baseURL = (import.meta.env.MODE === 'development') ? 'http://localhost:23367/'
90
+ const baseURL = (import.meta.env.DEV) ? 'http://localhost:23367/'
91
91
  : Utils.getURLBasedOnApi(this.ctx, '/sidebar/')
92
92
 
93
93
  const query: any = {
@@ -115,6 +115,8 @@ export default class SidebarLayer extends Component {
115
115
  if (!this.conf.darkMode && isIframeSrcDarkMode)
116
116
  this.iframeLoad(this.$iframe!.src.replace('&darkMode=1', ''))
117
117
  }
118
+
119
+ this.ctx.trigger('sidebar-show')
118
120
  }
119
121
 
120
122
  /** 隐藏 */
@@ -123,9 +125,11 @@ export default class SidebarLayer extends Component {
123
125
  this.$el.style.transform = ''
124
126
 
125
127
  this.layer?.hide()
128
+
129
+ this.ctx.trigger('sidebar-hide')
126
130
  }
127
131
 
128
- public iframeLoad(src: string) {
132
+ private iframeLoad(src: string) {
129
133
  if (!this.$iframe) return
130
134
 
131
135
  this.$iframe.src = src
@@ -135,39 +139,5 @@ export default class SidebarLayer extends Component {
135
139
  this.$iframe.onload = () => {
136
140
  Ui.hideLoading(this.$iframeWrap)
137
141
  }
138
-
139
- // this.checkReqStatus(src) // 判不准,删了,没啥用
140
- }
141
-
142
- loadingTimer: number|null = null
143
-
144
- // 测试可访问性 (由于 iframe 测不准,需要额外请求)
145
- public async checkReqStatus(url: string) {
146
- if (this.loadingTimer !== null) window.clearTimeout(this.loadingTimer)
147
-
148
- this.loadingTimer = window.setTimeout(async () => {
149
- try {
150
- await fetch(url)
151
- } catch (err) {
152
- console.log(err)
153
- // 请求失败,显示错误提示
154
- const $errAlert = Utils.createElement(
155
- `<div class="atk-err-alert">` +
156
- ` <div class="atk-title">侧边栏似乎打开失败</div>` +
157
- ` <div class="atk-text"><span id="AtkReload">重新加载</span> / <span id="AtkCancel">取消</span></div>` +
158
- `</div>`
159
- )
160
- const $reloadBtn = $errAlert.querySelector<HTMLElement>('#AtkReload')!
161
- const $cancelBtn = $errAlert.querySelector<HTMLElement>('#AtkCancel')!
162
- $reloadBtn.onclick = () => {
163
- this.iframeLoad(url)
164
- $errAlert.remove()
165
- }
166
- $cancelBtn.onclick = () => { // 提供取消按钮,防止误判
167
- $errAlert.remove()
168
- }
169
- this.$iframeWrap.append($errAlert)
170
- }
171
- }, 2000) // 2s 后开始检测
172
142
  }
173
143
  }
@@ -1,4 +1,3 @@
1
- import Api from '@/api'
2
1
  import * as Utils from '../utils'
3
2
  import { Checker } from '.'
4
3
 
@@ -12,19 +11,20 @@ const AdminChecker: Checker = {
12
11
  password: inputVal
13
12
  }
14
13
 
15
- return new Api(that.ctx).login(data.name, data.email, data.password)
14
+ return that.ctx.getApi().login(data.name, data.email, data.password)
16
15
  },
17
16
 
18
17
  body(that, ctx) {
19
- return Utils.createElement('<span>敲入密码来验证管理员身份:</span>')
18
+ return Utils.createElement(`<span>${that.ctx.$t('adminCheck')}</span>`)
20
19
  },
21
20
 
22
21
  onSuccess(that, ctx, userToken, inputVal, formEl) {
23
- that.ctx.user.data.isAdmin = true
24
- that.ctx.user.data.token = userToken
25
- that.ctx.user.save()
22
+ that.ctx.user.update({
23
+ isAdmin: true,
24
+ token: userToken
25
+ })
26
26
  that.ctx.trigger('user-changed', that.ctx.user.data)
27
- that.ctx.trigger('list-reload')
27
+ that.ctx.listReload()
28
28
  },
29
29
 
30
30
  onError(that, ctx, err, inputVal, formEl) {}
@@ -1,11 +1,10 @@
1
- import Api from '@/api'
2
1
  import * as Utils from '../utils'
3
2
  import * as Ui from '../ui'
4
3
  import { Checker } from '.'
5
4
 
6
5
  const CaptchaChecker: Checker = {
7
6
  request(that, ctx, inputVal) {
8
- return new Api(that.ctx).captchaCheck(inputVal)
7
+ return that.ctx.getApi().captchaCheck(inputVal)
9
8
  },
10
9
 
11
10
  body(that, ctx) {
@@ -35,7 +34,7 @@ const CaptchaChecker: Checker = {
35
34
  if (stop) return
36
35
  let isPass = false
37
36
  try {
38
- const resp = await new Api(that.ctx).captchaStatus()
37
+ const resp = await that.ctx.getApi().captchaStatus()
39
38
  isPass = resp.is_pass
40
39
  } catch { isPass = false }
41
40
  if (isPass) {
@@ -55,18 +54,18 @@ const CaptchaChecker: Checker = {
55
54
 
56
55
  // 图片验证方式
57
56
  const elem = Utils.createElement(
58
- `<span><img class="atk-captcha-img" src="${that.captchaConf.imgData || ''}" alt="验证码">敲入验证码继续:</span>`
57
+ `<span><img class="atk-captcha-img" src="${that.captchaConf.imgData || ''}">${that.ctx.$t('captchaCheck')}</span>`
59
58
  );
60
59
 
61
60
  // 刷新验证码
62
61
  elem.querySelector<HTMLElement>('.atk-captcha-img')!.onclick = () => {
63
62
  const imgEl = elem.querySelector('.atk-captcha-img')
64
- new Api(that.ctx).captchaGet()
63
+ that.ctx.getApi().captchaGet()
65
64
  .then((imgData) => {
66
65
  imgEl!.setAttribute('src', imgData)
67
66
  })
68
67
  .catch((err) => {
69
- console.error('验证码获取失败 ', err)
68
+ console.error('Failed to get captcha image ', err)
70
69
  })
71
70
  }
72
71
  return elem
@@ -1,12 +1,22 @@
1
- import Context from '@/context'
2
- import Layer from '@/components/layer'
1
+ import Context from '~/types/context'
3
2
  import Dialog from '@/components/dialog'
4
- import { CheckerPayload } from '~/types/event'
3
+ import Layer from '@/layer'
5
4
  import * as Utils from '../utils'
6
5
  import * as Ui from '../ui'
7
6
  import CaptchaChecker from './captcha-checker'
8
7
  import AdminChecker from './admin-checker'
9
8
 
9
+ export interface CheckerCaptchaPayload extends CheckerPayload {
10
+ imgData?: string
11
+ iframe?: string
12
+ }
13
+
14
+ export interface CheckerPayload {
15
+ onSuccess?: (inputVal: string, dialogEl?: HTMLElement) => void
16
+ onMount?: (dialogEl: HTMLElement) => void
17
+ onCancel?: () => void
18
+ }
19
+
10
20
  /**
11
21
  * Checker 发射台
12
22
  */
@@ -18,22 +28,17 @@ export default class CheckerLauncher {
18
28
 
19
29
  constructor(ctx: Context) {
20
30
  this.ctx = ctx
21
-
22
- this.initEventBind()
23
31
  }
24
32
 
25
- /** 初始化事件绑定 */
26
- public initEventBind() {
27
- this.ctx.on('checker-captcha', (conf) => {
28
- if (conf.imgData) this.captchaConf.imgData = conf.imgData
29
- if (conf.iframe) this.captchaConf.iframe = conf.iframe
33
+ public checkCaptcha(payload: CheckerCaptchaPayload) {
34
+ this.captchaConf.imgData = payload.imgData
35
+ this.captchaConf.iframe = payload.iframe
30
36
 
31
- this.fire(CaptchaChecker, conf)
32
- })
37
+ this.fire(CaptchaChecker, payload)
38
+ }
33
39
 
34
- this.ctx.on('checker-admin', (conf) => {
35
- this.fire(AdminChecker, conf)
36
- })
40
+ public checkAdmin(payload: CheckerPayload) {
41
+ this.fire(AdminChecker, payload)
37
42
  }
38
43
 
39
44
  public fire(checker: Checker, payload: CheckerPayload) {
@@ -87,7 +92,7 @@ export default class CheckerLauncher {
87
92
  }
88
93
 
89
94
  let btnTextOrg: string | undefined
90
- const dialog = new Dialog(formEl)
95
+ const dialog = new Dialog(this.ctx, formEl)
91
96
 
92
97
  // 确认按钮
93
98
  dialog.setYes((btnEl) => {
@@ -104,7 +109,7 @@ export default class CheckerLauncher {
104
109
  btnEl.classList.remove('error')
105
110
  }
106
111
 
107
- btnEl.innerText = '加载中...'
112
+ btnEl.innerText = `${this.ctx.$t('loading')}...`
108
113
 
109
114
  // 发送请求
110
115
  checker
@@ -1,7 +1,8 @@
1
1
  import ArtalkConfig from '~/types/artalk-config'
2
- import Context from '../context'
2
+ import Context from '~/types/context'
3
+ import { I18n } from '../i18n'
3
4
 
4
- export default class Component {
5
+ export default abstract class Component {
5
6
  public $el!: HTMLElement
6
7
 
7
8
  public ctx: Context
@@ -11,4 +12,8 @@ export default class Component {
11
12
  this.ctx = ctx
12
13
  this.conf = ctx.conf
13
14
  }
15
+
16
+ public $t(key: keyof I18n, args: {[key: string]: string} = {}): string {
17
+ return this.ctx.$t(key, args)
18
+ }
14
19
  }
@@ -0,0 +1,63 @@
1
+ import ContextApi from '~/types/context'
2
+ import ArtalkPlug from '~/types/plug'
3
+
4
+ export interface CountConf {
5
+ ctx: ContextApi
6
+
7
+ /** 是否增加当前页面 PV 数 */
8
+ pvAdd?: boolean
9
+ }
10
+
11
+ export const PvCountWidget: ArtalkPlug = (ctx: ContextApi) => {
12
+ initCountWidget({ ctx, pvAdd: true })
13
+ }
14
+
15
+ /** 初始化评论数和 PV 数量展示元素 */
16
+ export async function initCountWidget(p: CountConf) {
17
+ // 评论数
18
+ const countEl = p.ctx.conf.countEl
19
+ if (countEl && document.querySelector(countEl)) {
20
+ handleStatCount(p, { api: 'page_comment', countEl })
21
+ }
22
+
23
+ // PV
24
+ const curtPagePvNum = p.pvAdd ? await p.ctx.getApi().pv() : undefined
25
+ const pvEl = p.ctx.conf.pvEl
26
+ if (pvEl && document.querySelector(pvEl)) {
27
+ handleStatCount(p, {
28
+ api: 'page_pv',
29
+ countEl: pvEl,
30
+ curtPageCount: curtPagePvNum,
31
+ })
32
+ }
33
+ }
34
+
35
+ export async function handleStatCount(
36
+ p: CountConf,
37
+ args: {
38
+ api: 'page_pv' | 'page_comment'
39
+ countEl: string
40
+ curtPageCount?: number
41
+ }
42
+ ) {
43
+ let pageCounts: { [key: string]: number } = {}
44
+
45
+ // 当前页面的统计数
46
+ const curtPageKey = p.ctx.conf.pageKey
47
+ if (args.curtPageCount) pageCounts[curtPageKey] = args.curtPageCount
48
+
49
+ // 查询其他页面的统计数
50
+ let queryPageKeys = Array.from(document.querySelectorAll(args.countEl))
51
+ .map((e) => e.getAttribute('data-page-key') || curtPageKey)
52
+ .filter((pageKey) => pageCounts[pageKey] === undefined)
53
+ queryPageKeys = [...new Set(queryPageKeys)] // 去重
54
+ if (queryPageKeys.length > 0) {
55
+ const counts: any = await p.ctx.getApi().stat(args.api, queryPageKeys)
56
+ pageCounts = { ...pageCounts, ...counts }
57
+ }
58
+
59
+ document.querySelectorAll(args.countEl).forEach((el) => {
60
+ const pageKey = el.getAttribute('data-page-key') || curtPageKey
61
+ el.innerHTML = `${Number(pageCounts[pageKey] || 0)}`
62
+ })
63
+ }
package/src/lib/ui.ts CHANGED
@@ -1,11 +1,9 @@
1
1
  import * as Utils from './utils'
2
- import Context from '../context'
3
2
 
4
3
  /** 显示加载 */
5
- export function showLoading(parentElem: HTMLElement|Context, conf?: { transparentBg?: boolean }) {
6
- if (parentElem instanceof Context) parentElem = parentElem.$root
7
-
8
- let $loading = parentElem.querySelector<HTMLElement>('.atk-loading')
4
+ export function showLoading(parentElem: HTMLElement, conf?: { transparentBg?: boolean }) {
5
+ // Use :scope https://developer.mozilla.org/en-US/docs/Web/CSS/:scope
6
+ let $loading = parentElem.querySelector<HTMLElement>(':scope > .atk-loading')
9
7
  if (!$loading) {
10
8
  $loading = Utils.createElement(
11
9
  `<div class="atk-loading atk-fade-in" style="display: none;">
@@ -29,10 +27,8 @@ export function showLoading(parentElem: HTMLElement|Context, conf?: { transparen
29
27
  }
30
28
 
31
29
  /** 隐藏加载 */
32
- export function hideLoading(parentElem: HTMLElement|Context) {
33
- if (parentElem instanceof Context) parentElem = parentElem.$root
34
-
35
- const $loading = parentElem.querySelector<HTMLElement>('.atk-loading')
30
+ export function hideLoading(parentElem: HTMLElement) {
31
+ const $loading = parentElem.querySelector<HTMLElement>(':scope > .atk-loading')
36
32
  if ($loading) $loading.style.display = 'none'
37
33
  }
38
34
 
@@ -133,9 +129,7 @@ export function playFadeOutAnim(elem: HTMLElement, after?: () => void) {
133
129
  }
134
130
 
135
131
  /** 设置全局错误 */
136
- export function setError(parentElem: HTMLElement|Context, html: string | HTMLElement | null, title: string = '<span class="atk-error-title">Artalk Error</span>') {
137
- if (parentElem instanceof Context) parentElem = parentElem.$root
138
-
132
+ export function setError(parentElem: HTMLElement, html: string | HTMLElement | null, title: string = '<span class="atk-error-title">Artalk Error</span>') {
139
133
  let elem = parentElem.querySelector<HTMLElement>('.atk-error-layer')
140
134
  if (html === null) {
141
135
  if (elem !== null) elem.remove()
package/src/lib/user.ts CHANGED
@@ -1,9 +1,13 @@
1
1
  import ArtalkConfig, { LocalUser } from "~/types/artalk-config"
2
+ import Context from '~/types/context'
2
3
 
3
4
  export default class User {
5
+ public ctx: Context
4
6
  public data: LocalUser
5
7
 
6
- constructor (conf: ArtalkConfig) {
8
+ public constructor(ctx: Context) {
9
+ this.ctx = ctx
10
+
7
11
  // 从 localStorage 导入
8
12
  const localUser = JSON.parse(window.localStorage.getItem('ArtalkUser') || '{}')
9
13
  this.data = {
@@ -16,12 +20,25 @@ export default class User {
16
20
  }
17
21
 
18
22
  /** 保存用户到 localStorage 中 */
19
- public save () {
23
+ public update(obj: Partial<LocalUser> = {}) {
24
+ Object.entries(obj).forEach(([key, value]) => {
25
+ this.data[key] = value
26
+ })
27
+
20
28
  window.localStorage.setItem('ArtalkUser', JSON.stringify(this.data))
29
+ this.ctx.trigger('user-changed', this.ctx.user.data)
30
+ }
31
+
32
+ /** 注销,清除用户登录状态 */
33
+ public logout() {
34
+ this.update({
35
+ token: '',
36
+ isAdmin: false
37
+ })
21
38
  }
22
39
 
23
40
  /** 是否已填写基本用户信息 */
24
- public checkHasBasicUserInfo () {
41
+ public checkHasBasicUserInfo() {
25
42
  return !!this.data.nick && !!this.data.email
26
43
  }
27
44
  }
package/src/lib/utils.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { marked as libMarked } from 'marked'
2
2
  import insane from 'insane'
3
3
  import hanabi from 'hanabi'
4
- import Context from '../context'
4
+ import Context from '~/types/context'
5
5
 
6
6
  export function createElement<E extends HTMLElement = HTMLElement>(htmlStr: string = ''): E {
7
7
  const div = document.createElement('div')
@@ -58,7 +58,7 @@ export function dateFormat(date: Date) {
58
58
  return `${vYear}-${vMonth}-${vDay}`
59
59
  }
60
60
 
61
- export function timeAgo(date: Date) {
61
+ export function timeAgo(date: Date, ctx: Context) {
62
62
  try {
63
63
  const oldTime = date.getTime()
64
64
  const currTime = new Date().getTime()
@@ -77,16 +77,16 @@ export function timeAgo(date: Date) {
77
77
  // 计算相差秒数
78
78
  const leave3 = leave2 % (60 * 1000) // 计算分钟数后剩余的毫秒数
79
79
  const seconds = Math.round(leave3 / 1000)
80
- return `${seconds} 秒前`
80
+ return `${seconds} ${ctx.$t('seconds')}`
81
81
  }
82
- return `${minutes} 分钟前`
82
+ return `${minutes} ${ctx.$t('minutes')}`
83
83
  }
84
- return `${hours} 小时前`
84
+ return `${hours} ${ctx.$t('hours')}`
85
85
  }
86
- if (days < 0) return '刚刚'
86
+ if (days < 0) return ctx.$t('now')
87
87
 
88
88
  if (days < 8) {
89
- return `${days} 天前`
89
+ return `${days} ${ctx.$t('days')}`
90
90
  }
91
91
 
92
92
  return dateFormat(date)
@@ -118,7 +118,7 @@ export function onImagesLoaded($container: HTMLElement, event: Function) {
118
118
  }
119
119
 
120
120
  export function getGravatarURL(ctx: Context, emailMD5: string) {
121
- return `${(ctx.conf.gravatar?.mirror || '').replace(/\/$/, '')}/${emailMD5}?d=${encodeURIComponent(ctx.conf.gravatar?.default || '')}&s=80`
121
+ return `${(ctx.conf.gravatar.mirror).replace(/\/$/, '')}/${emailMD5}?d=${encodeURIComponent(ctx.conf.gravatar.default)}&s=80`
122
122
  }
123
123
 
124
124
  export function sleep(ms: number) {
@@ -142,6 +142,8 @@ export function versionCompare(a: string, b: string) {
142
142
 
143
143
  /** 初始化 marked */
144
144
  export function initMarked(ctx: Context) {
145
+ try { if (!libMarked.name) return } catch { return }
146
+
145
147
  const renderer = new libMarked.Renderer()
146
148
  const orgLinkRenderer = renderer.link
147
149
  renderer.link = (href, title, text) => {
@@ -186,9 +188,21 @@ export function initMarked(ctx: Context) {
186
188
 
187
189
  /** 解析 markdown */
188
190
  export function marked(ctx: Context, src: string): string {
191
+ let markedContent = ctx.markedInstance?.parse(src)
192
+ if (!markedContent) {
193
+ // 无 Markdown 模式简单处理
194
+ markedContent = src
195
+ // .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
196
+ .replace(/```\s*([^]+?.*?[^]+?[^]+?)```/g, (_, code) => `<pre><code>${hanabi(code)}</code></pre>`)
197
+ // .replace(/`([^`]+?)`/g, '<code>$1</code>')
198
+ .replace(/!\[(.*?)\]\((.*?)\)/g, (_, alt, imgSrc) => `<img src="${imgSrc}" alt="${alt}" />`)
199
+ .replace(/\[(.*?)\]\((.*?)\)/g, (_, text, link) => `<a href="${link}" target="_blank">${text}</a>`)
200
+ .replace(/\n/g, '<br>')
201
+ }
202
+
189
203
  // @link https://github.com/markedjs/marked/discussions/1232
190
204
  // @link https://gist.github.com/lionel-rowe/bb384465ba4e4c81a9c8dada84167225
191
- let dest = insane(ctx.markedInstance.parse(src), {
205
+ let dest = insane(markedContent, {
192
206
  allowedClasses: {},
193
207
  allowedSchemes: ['http', 'https', 'mailto'],
194
208
  allowedTags: [
@@ -0,0 +1,3 @@
1
+ import List from './list'
2
+
3
+ export default List