artalk 2.2.12 → 2.3.0

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 (73) hide show
  1. package/dist/Artalk.css +1 -1
  2. package/dist/Artalk.es.js +3216 -2883
  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 +5096 -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 +68 -50
  14. package/src/comment/actions.ts +87 -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 +200 -13
  23. package/src/defaults.ts +16 -5
  24. package/src/editor/editor.html +18 -0
  25. package/src/editor/editor.ts +420 -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 +1 -0
  29. package/src/{components/editor-plugs → editor/plugs}/emoticons-plug.ts +65 -63
  30. package/src/editor/plugs/header-input-plug.ts +79 -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 +125 -0
  34. package/src/i18n/en.ts +77 -0
  35. package/src/i18n/index.ts +21 -0
  36. package/src/i18n/zh-CN.ts +81 -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 -46
  41. package/src/lib/checker/admin-checker.ts +7 -6
  42. package/src/lib/checker/captcha-checker.ts +5 -5
  43. package/src/lib/checker/index.ts +22 -17
  44. package/src/lib/component.ts +7 -2
  45. package/src/lib/ui.ts +3 -10
  46. package/src/lib/user.ts +20 -3
  47. package/src/lib/utils.ts +23 -9
  48. package/src/list/index.ts +3 -0
  49. package/src/{components → list}/list-lite.ts +80 -99
  50. package/src/{components → list}/list-nest.ts +0 -0
  51. package/src/{components/html → list}/list.html +3 -6
  52. package/src/{components → list}/list.ts +37 -35
  53. package/src/style/comment.less +23 -10
  54. package/src/style/editor.less +16 -10
  55. package/src/style/list.less +4 -7
  56. package/src/style/main.less +3 -1
  57. package/src/style/pagination.less +1 -1
  58. package/test/ArtalkLite.html +15 -0
  59. package/types/artalk-config.d.ts +50 -32
  60. package/types/context.d.ts +128 -0
  61. package/types/event.d.ts +6 -66
  62. package/types/global.d.ts +1 -0
  63. package/{vite-common.config.ts → vite-base.config.ts} +6 -4
  64. package/vite-demo.config.ts +2 -4
  65. package/vite-lite.config.ts +22 -0
  66. package/vite.config.ts +9 -12
  67. package/src/components/_component.ts.txt +0 -10
  68. package/src/components/comment.ts +0 -605
  69. package/src/components/editor-plugs/_example-plug.ts.txt +0 -29
  70. package/src/components/editor-plugs/editor-plug.ts +0 -20
  71. package/src/components/editor-plugs/preview-plug.ts +0 -48
  72. package/src/components/editor.ts +0 -607
  73. 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,14 +1,17 @@
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
9
  import Api from '../api'
11
10
 
11
+ export interface SidebarShowPayload {
12
+ view?: 'comments'|'sites'|'pages'|'transfer'
13
+ }
14
+
12
15
  export default class SidebarLayer extends Component {
13
16
  public layer?: Layer
14
17
  public $header: HTMLElement
@@ -29,15 +32,13 @@ export default class SidebarLayer extends Component {
29
32
  }
30
33
 
31
34
  // event
32
- this.ctx.on('sidebar-show', (conf) => (this.show(conf || {})))
33
- this.ctx.on('sidebar-hide', () => (this.hide()))
34
35
  this.ctx.on('user-changed', () => { this.firstShow = true })
35
36
  }
36
37
 
37
38
  private firstShow = true
38
39
 
39
40
  /** 显示 */
40
- public async show(conf: SidebarShowPayload) {
41
+ public async show(conf: SidebarShowPayload = {}) {
41
42
  this.$el.style.transform = '' // 动画清除,防止二次打开失效
42
43
 
43
44
  // 获取 Layer
@@ -46,7 +47,7 @@ export default class SidebarLayer extends Component {
46
47
  this.layer.afterHide = () => {
47
48
  // 防止评论框被吞
48
49
  if (this.ctx.conf.editorTravel === true) {
49
- this.ctx.trigger('editor-travel-back')
50
+ this.ctx.editorTravelBack()
50
51
  }
51
52
  }
52
53
  }
@@ -62,12 +63,12 @@ export default class SidebarLayer extends Component {
62
63
 
63
64
  // 管理员身份验证 (若身份失效,弹出验证窗口)
64
65
  ;(async () => {
65
- const resp = await new Api(this.ctx).loginStatus()
66
+ const resp = await this.ctx.getApi().loginStatus()
66
67
  if (resp.is_admin && !resp.is_login) {
67
68
  this.layer?.hide()
68
69
  this.firstShow = true
69
70
 
70
- this.ctx.trigger('checker-admin', {
71
+ this.ctx.checkAdmin({
71
72
  onSuccess: () => {
72
73
  setTimeout(() => {
73
74
  this.show(conf)
@@ -79,7 +80,7 @@ export default class SidebarLayer extends Component {
79
80
  })()
80
81
 
81
82
  // 清空 unread
82
- this.ctx.trigger('unread-update', { notifies: [] })
83
+ this.ctx.updateNotifies([])
83
84
 
84
85
  // 第一次加载
85
86
  if (this.firstShow) {
@@ -87,7 +88,7 @@ export default class SidebarLayer extends Component {
87
88
  this.$iframe = Utils.createElement<HTMLIFrameElement>('<iframe></iframe>')
88
89
 
89
90
  // 准备 Iframe 参数
90
- const baseURL = (import.meta.env.MODE === 'development') ? 'http://localhost:23367/'
91
+ const baseURL = (import.meta.env.DEV) ? 'http://localhost:23367/'
91
92
  : Utils.getURLBasedOnApi(this.ctx, '/sidebar/')
92
93
 
93
94
  const query: any = {
@@ -115,6 +116,8 @@ export default class SidebarLayer extends Component {
115
116
  if (!this.conf.darkMode && isIframeSrcDarkMode)
116
117
  this.iframeLoad(this.$iframe!.src.replace('&darkMode=1', ''))
117
118
  }
119
+
120
+ this.ctx.trigger('sidebar-show')
118
121
  }
119
122
 
120
123
  /** 隐藏 */
@@ -123,9 +126,11 @@ export default class SidebarLayer extends Component {
123
126
  this.$el.style.transform = ''
124
127
 
125
128
  this.layer?.hide()
129
+
130
+ this.ctx.trigger('sidebar-hide')
126
131
  }
127
132
 
128
- public iframeLoad(src: string) {
133
+ private iframeLoad(src: string) {
129
134
  if (!this.$iframe) return
130
135
 
131
136
  this.$iframe.src = src
@@ -135,39 +140,5 @@ export default class SidebarLayer extends Component {
135
140
  this.$iframe.onload = () => {
136
141
  Ui.hideLoading(this.$iframeWrap)
137
142
  }
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
143
  }
173
144
  }
@@ -12,19 +12,20 @@ const AdminChecker: Checker = {
12
12
  password: inputVal
13
13
  }
14
14
 
15
- return new Api(that.ctx).login(data.name, data.email, data.password)
15
+ return that.ctx.getApi().login(data.name, data.email, data.password)
16
16
  },
17
17
 
18
18
  body(that, ctx) {
19
- return Utils.createElement('<span>敲入密码来验证管理员身份:</span>')
19
+ return Utils.createElement(`<span>${that.ctx.$t('adminCheck')}</span>`)
20
20
  },
21
21
 
22
22
  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()
23
+ that.ctx.user.update({
24
+ isAdmin: true,
25
+ token: userToken
26
+ })
26
27
  that.ctx.trigger('user-changed', that.ctx.user.data)
27
- that.ctx.trigger('list-reload')
28
+ that.ctx.listReload()
28
29
  },
29
30
 
30
31
  onError(that, ctx, err, inputVal, formEl) {}
@@ -5,7 +5,7 @@ import { Checker } from '.'
5
5
 
6
6
  const CaptchaChecker: Checker = {
7
7
  request(that, ctx, inputVal) {
8
- return new Api(that.ctx).captchaCheck(inputVal)
8
+ return that.ctx.getApi().captchaCheck(inputVal)
9
9
  },
10
10
 
11
11
  body(that, ctx) {
@@ -35,7 +35,7 @@ const CaptchaChecker: Checker = {
35
35
  if (stop) return
36
36
  let isPass = false
37
37
  try {
38
- const resp = await new Api(that.ctx).captchaStatus()
38
+ const resp = await that.ctx.getApi().captchaStatus()
39
39
  isPass = resp.is_pass
40
40
  } catch { isPass = false }
41
41
  if (isPass) {
@@ -55,18 +55,18 @@ const CaptchaChecker: Checker = {
55
55
 
56
56
  // 图片验证方式
57
57
  const elem = Utils.createElement(
58
- `<span><img class="atk-captcha-img" src="${that.captchaConf.imgData || ''}" alt="验证码">敲入验证码继续:</span>`
58
+ `<span><img class="atk-captcha-img" src="${that.captchaConf.imgData || ''}">${that.ctx.$t('captchaCheck')}</span>`
59
59
  );
60
60
 
61
61
  // 刷新验证码
62
62
  elem.querySelector<HTMLElement>('.atk-captcha-img')!.onclick = () => {
63
63
  const imgEl = elem.querySelector('.atk-captcha-img')
64
- new Api(that.ctx).captchaGet()
64
+ that.ctx.getApi().captchaGet()
65
65
  .then((imgData) => {
66
66
  imgEl!.setAttribute('src', imgData)
67
67
  })
68
68
  .catch((err) => {
69
- console.error('验证码获取失败 ', err)
69
+ console.error('Failed to get captcha image ', err)
70
70
  })
71
71
  }
72
72
  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
  }
package/src/lib/ui.ts CHANGED
@@ -1,10 +1,7 @@
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
-
4
+ export function showLoading(parentElem: HTMLElement, conf?: { transparentBg?: boolean }) {
8
5
  let $loading = parentElem.querySelector<HTMLElement>('.atk-loading')
9
6
  if (!$loading) {
10
7
  $loading = Utils.createElement(
@@ -29,9 +26,7 @@ export function showLoading(parentElem: HTMLElement|Context, conf?: { transparen
29
26
  }
30
27
 
31
28
  /** 隐藏加载 */
32
- export function hideLoading(parentElem: HTMLElement|Context) {
33
- if (parentElem instanceof Context) parentElem = parentElem.$root
34
-
29
+ export function hideLoading(parentElem: HTMLElement) {
35
30
  const $loading = parentElem.querySelector<HTMLElement>('.atk-loading')
36
31
  if ($loading) $loading.style.display = 'none'
37
32
  }
@@ -133,9 +128,7 @@ export function playFadeOutAnim(elem: HTMLElement, after?: () => void) {
133
128
  }
134
129
 
135
130
  /** 设置全局错误 */
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
-
131
+ export function setError(parentElem: HTMLElement, html: string | HTMLElement | null, title: string = '<span class="atk-error-title">Artalk Error</span>') {
139
132
  let elem = parentElem.querySelector<HTMLElement>('.atk-error-layer')
140
133
  if (html === null) {
141
134
  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