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
@@ -0,0 +1,134 @@
1
+ import Context from '@/context'
2
+ import Layer from '@/components/layer'
3
+ import Dialog from '@/components/dialog'
4
+ import * as Utils from '../utils'
5
+ import * as Ui from '../ui'
6
+ import { CheckerPayload } from '~/types/event'
7
+ import CaptchaChecker from './captcha-checker'
8
+ import AdminChecker from './admin-checker'
9
+
10
+ /**
11
+ * Checker 发射台
12
+ */
13
+ export default class CheckerLauncher {
14
+ public ctx: Context
15
+
16
+ public launched: Checker[] = []
17
+ public submitCaptchaVal?: string
18
+ public submitCaptchaImgData?: string
19
+
20
+ constructor(ctx: Context) {
21
+ this.ctx = ctx
22
+
23
+ this.initEventBind()
24
+ }
25
+
26
+ /** 初始化事件绑定 */
27
+ public initEventBind() {
28
+ this.ctx.on('checker-captcha', (conf) => {
29
+ if (conf.imgData) {
30
+ this.submitCaptchaImgData = conf.imgData
31
+ }
32
+ this.fire(CaptchaChecker, conf)
33
+ })
34
+
35
+ this.ctx.on('checker-admin', (conf) => {
36
+ this.fire(AdminChecker, conf)
37
+ })
38
+ }
39
+
40
+ public fire(checker: Checker, payload: CheckerPayload) {
41
+ if (this.launched.includes(checker)) return // 阻止同时 fire 相同的 checker
42
+ this.launched.push(checker)
43
+
44
+ const layer = new Layer(this.ctx, `checker-${new Date().getTime()}`)
45
+ layer.setMaskClickHide(false)
46
+ layer.show()
47
+
48
+ const formEl = Utils.createElement()
49
+ formEl.appendChild(checker.body(this))
50
+
51
+ const input = Utils.createElement<HTMLInputElement>(
52
+ `<input id="check" type="${checker.inputType || 'text'}" autocomplete="off" required placeholder="">`
53
+ )
54
+ formEl.appendChild(input)
55
+ setTimeout(() => input.focus(), 80) // 延迟 Focus
56
+
57
+ input.onkeyup = (evt) => {
58
+ if (evt.key === 'Enter' || evt.keyCode === 13) {
59
+ // 按下回车键
60
+ evt.preventDefault()
61
+ layer
62
+ .getEl()
63
+ .querySelector<HTMLButtonElement>('button[data-action="confirm"]')!
64
+ .click()
65
+ }
66
+ }
67
+
68
+ let btnTextOrg: string | undefined
69
+ const dialog = new Dialog(formEl)
70
+
71
+ // 确认按钮
72
+ dialog.setYes((btnEl) => {
73
+ const inputVal = input.value.trim()
74
+ if (!btnTextOrg) btnTextOrg = btnEl.innerText
75
+ const btnTextSet = (btnText: string) => {
76
+ btnEl.innerText = btnText
77
+ btnEl.classList.add('error')
78
+ }
79
+ const btnTextRestore = () => {
80
+ btnEl.innerText = btnTextOrg || ''
81
+ btnEl.classList.remove('error')
82
+ }
83
+
84
+ btnEl.innerText = '加载中...'
85
+
86
+ checker
87
+ .request(this, inputVal)
88
+ .then((data) => {
89
+ // 请求成功
90
+ this.done(checker, layer)
91
+ if (checker.onSuccess) checker.onSuccess(this, data, inputVal, formEl)
92
+ if (payload.onSuccess) payload.onSuccess(inputVal, dialog.$el)
93
+ })
94
+ .catch((err) => {
95
+ // 请求失败
96
+ btnTextSet(String(err.msg || String(err)))
97
+ if (checker.onError) checker.onError(this, err, inputVal, formEl)
98
+
99
+ const tf = setTimeout(() => btnTextRestore(), 3000)
100
+ input.onfocus = () => {
101
+ btnTextRestore()
102
+ clearTimeout(tf)
103
+ }
104
+ })
105
+
106
+ return false
107
+ })
108
+
109
+ // 取消按钮
110
+ dialog.setNo(() => {
111
+ this.done(checker, layer)
112
+ if (payload.onCancel) payload.onCancel()
113
+ return false
114
+ })
115
+
116
+ layer.getEl().append(dialog.$el)
117
+
118
+ if (payload.onMount) payload.onMount(dialog.$el) // onMount
119
+ }
120
+
121
+ private done(checker: Checker, layer: Layer) {
122
+ layer.disposeNow()
123
+ this.launched = this.launched.filter(c => c !== checker)
124
+ }
125
+ }
126
+
127
+ export interface Checker {
128
+ el?: HTMLElement
129
+ inputType?: 'password' | 'text'
130
+ body: (launcher: CheckerLauncher) => HTMLElement
131
+ request: (launcher: CheckerLauncher, inputVal: string) => Promise<string>
132
+ onSuccess?: (launcher: CheckerLauncher, data: string, inputVal: string, formEl: HTMLElement) => void
133
+ onError?: (launcher: CheckerLauncher, err: any, inputVal: string, formEl: HTMLElement) => void
134
+ }
@@ -1,8 +1,8 @@
1
- import { ArtalkConfig } from '~/types/artalk-config'
2
- import Context from '../Context'
1
+ import ArtalkConfig from '~/types/artalk-config'
2
+ import Context from '../context'
3
3
 
4
4
  export default class Component {
5
- public el!: HTMLElement
5
+ public $el!: HTMLElement
6
6
 
7
7
  public ctx: Context
8
8
  public readonly conf: ArtalkConfig
package/src/lib/md5.js ADDED
@@ -0,0 +1,6 @@
1
+ /* eslint-disable */
2
+
3
+ // https://stackoverflow.com/questions/14733374/how-to-generate-an-md5-file-hash-in-javascript-node-js#answer-33486055
4
+ var MD5 = function(d){var r = M(V(Y(X(d),8*d.length)));return r.toLowerCase()};function M(d){for(var _,m="0123456789ABCDEF",f="",r=0;r<d.length;r++)_=d.charCodeAt(r),f+=m.charAt(_>>>4&15)+m.charAt(15&_);return f}function X(d){for(var _=Array(d.length>>2),m=0;m<_.length;m++)_[m]=0;for(m=0;m<8*d.length;m+=8)_[m>>5]|=(255&d.charCodeAt(m/8))<<m%32;return _}function V(d){for(var _="",m=0;m<32*d.length;m+=8)_+=String.fromCharCode(d[m>>5]>>>m%32&255);return _}function Y(d,_){d[_>>5]|=128<<_%32,d[14+(_+64>>>9<<4)]=_;for(var m=1732584193,f=-271733879,r=-1732584194,i=271733878,n=0;n<d.length;n+=16){var h=m,t=f,g=r,e=i;f=md5_ii(f=md5_ii(f=md5_ii(f=md5_ii(f=md5_hh(f=md5_hh(f=md5_hh(f=md5_hh(f=md5_gg(f=md5_gg(f=md5_gg(f=md5_gg(f=md5_ff(f=md5_ff(f=md5_ff(f=md5_ff(f,r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+0],7,-680876936),f,r,d[n+1],12,-389564586),m,f,d[n+2],17,606105819),i,m,d[n+3],22,-1044525330),r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+4],7,-176418897),f,r,d[n+5],12,1200080426),m,f,d[n+6],17,-1473231341),i,m,d[n+7],22,-45705983),r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+8],7,1770035416),f,r,d[n+9],12,-1958414417),m,f,d[n+10],17,-42063),i,m,d[n+11],22,-1990404162),r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+12],7,1804603682),f,r,d[n+13],12,-40341101),m,f,d[n+14],17,-1502002290),i,m,d[n+15],22,1236535329),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+1],5,-165796510),f,r,d[n+6],9,-1069501632),m,f,d[n+11],14,643717713),i,m,d[n+0],20,-373897302),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+5],5,-701558691),f,r,d[n+10],9,38016083),m,f,d[n+15],14,-660478335),i,m,d[n+4],20,-405537848),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+9],5,568446438),f,r,d[n+14],9,-1019803690),m,f,d[n+3],14,-187363961),i,m,d[n+8],20,1163531501),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+13],5,-1444681467),f,r,d[n+2],9,-51403784),m,f,d[n+7],14,1735328473),i,m,d[n+12],20,-1926607734),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+5],4,-378558),f,r,d[n+8],11,-2022574463),m,f,d[n+11],16,1839030562),i,m,d[n+14],23,-35309556),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+1],4,-1530992060),f,r,d[n+4],11,1272893353),m,f,d[n+7],16,-155497632),i,m,d[n+10],23,-1094730640),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+13],4,681279174),f,r,d[n+0],11,-358537222),m,f,d[n+3],16,-722521979),i,m,d[n+6],23,76029189),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+9],4,-640364487),f,r,d[n+12],11,-421815835),m,f,d[n+15],16,530742520),i,m,d[n+2],23,-995338651),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+0],6,-198630844),f,r,d[n+7],10,1126891415),m,f,d[n+14],15,-1416354905),i,m,d[n+5],21,-57434055),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+12],6,1700485571),f,r,d[n+3],10,-1894986606),m,f,d[n+10],15,-1051523),i,m,d[n+1],21,-2054922799),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+8],6,1873313359),f,r,d[n+15],10,-30611744),m,f,d[n+6],15,-1560198380),i,m,d[n+13],21,1309151649),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+4],6,-145523070),f,r,d[n+11],10,-1120210379),m,f,d[n+2],15,718787259),i,m,d[n+9],21,-343485551),m=safe_add(m,h),f=safe_add(f,t),r=safe_add(r,g),i=safe_add(i,e)}return Array(m,f,r,i)}function md5_cmn(d,_,m,f,r,i){return safe_add(bit_rol(safe_add(safe_add(_,d),safe_add(f,i)),r),m)}function md5_ff(d,_,m,f,r,i,n){return md5_cmn(_&m|~_&f,d,_,r,i,n)}function md5_gg(d,_,m,f,r,i,n){return md5_cmn(_&f|m&~f,d,_,r,i,n)}function md5_hh(d,_,m,f,r,i,n){return md5_cmn(_^m^f,d,_,r,i,n)}function md5_ii(d,_,m,f,r,i,n){return md5_cmn(m^(_|~f),d,_,r,i,n)}function safe_add(d,_){var m=(65535&d)+(65535&_);return(d>>16)+(_>>16)+(m>>16)<<16|65535&m}function bit_rol(d,_){return d<<_|d>>>32-_}
5
+
6
+ export default MD5
package/src/lib/ui.ts CHANGED
@@ -1,45 +1,56 @@
1
1
  import * as Utils from './utils'
2
- import Context from '../Context'
2
+ import Context from '../context'
3
3
 
4
4
  /** 显示加载 */
5
5
  export function showLoading(parentElem: HTMLElement|Context) {
6
- if (parentElem instanceof Context) parentElem = parentElem.rootEl
7
-
8
- let loadingEl = parentElem.querySelector<HTMLElement>('.atk-loading')
9
- if (!loadingEl) {
10
- loadingEl = Utils.createElement(`
11
- <div class="atk-loading" style="display: none;">
12
- <div class="atk-loading-spinner">
13
- <svg viewBox="25 25 50 50"><circle cx="50" cy="50" r="20" fill="none" stroke-width="2" stroke-miterlimit="10"></circle></svg>
14
- </div>
15
- </div>`)
16
- parentElem.appendChild(loadingEl)
6
+ if (parentElem instanceof Context) parentElem = parentElem.$root
7
+
8
+ let $loading = parentElem.querySelector<HTMLElement>('.atk-loading')
9
+ if (!$loading) {
10
+ $loading = Utils.createElement(
11
+ `<div class="atk-loading atk-fade-in" style="display: none;">
12
+ <div class="atk-loading-spinner">
13
+ <svg viewBox="25 25 50 50"><circle cx="50" cy="50" r="20" fill="none" stroke-width="2" stroke-miterlimit="10"></circle></svg>
14
+ </div>
15
+ </div>`)
16
+ parentElem.appendChild($loading)
17
+ }
18
+ $loading.style.display = ''
19
+
20
+ // spinner 延迟显示,若加载等待时间太短,没必要显示(闪一下即可)
21
+ const $spinner = $loading.querySelector<HTMLElement>('.atk-loading-spinner')
22
+ if ($spinner) {
23
+ $spinner.style.display = 'none'
24
+ window.setTimeout(() => {
25
+ $spinner.style.display = ''
26
+ }, 500)
17
27
  }
18
- loadingEl.style.display = ''
19
28
  }
20
29
 
21
30
  /** 隐藏加载 */
22
31
  export function hideLoading(parentElem: HTMLElement|Context) {
23
- if (parentElem instanceof Context) parentElem = parentElem.rootEl
32
+ if (parentElem instanceof Context) parentElem = parentElem.$root
24
33
 
25
- const loadingEl = parentElem.querySelector<HTMLElement>('.atk-loading')
26
- if (loadingEl) loadingEl.style.display = 'none'
34
+ const $loading = parentElem.querySelector<HTMLElement>('.atk-loading')
35
+ if ($loading) $loading.style.display = 'none'
27
36
  }
28
37
 
29
38
  /** 元素是否用户可见 */
30
- export function isVisible(elem: HTMLElement) {
31
- const docViewTop = window.scrollY
32
- const docViewBottom = docViewTop + document.documentElement.clientHeight
39
+ export function isVisible(el: HTMLElement, viewport: HTMLElement = document.documentElement) {
40
+ const viewportHeight = viewport.clientHeight
41
+
42
+ const docViewTop = viewport.scrollTop
43
+ const docViewBottom = docViewTop + viewportHeight
33
44
 
34
- const elemTop = Utils.getOffset(elem).top
35
- const elemBottom = elemTop + elem.offsetHeight
45
+ const rect = el.getBoundingClientRect()
46
+ const elemTop = rect.top + docViewTop
47
+ const elemBottom = elemTop + rect.height
36
48
 
37
- return elemBottom <= docViewBottom && elemTop >= docViewTop
49
+ return (elemBottom <= docViewBottom) //&& (elemTop >= docViewTop) 注释因为假如 el 比 viewport 还高就会失效
38
50
  }
39
51
 
40
52
  /** 滚动到元素中心 */
41
53
  export function scrollIntoView(elem: HTMLElement, enableAnim: boolean = true) {
42
- if (isVisible(elem)) return
43
54
  const top =
44
55
  Utils.getOffset(elem).top +
45
56
  Utils.getHeight(elem) / 2 -
@@ -49,7 +60,7 @@ export function scrollIntoView(elem: HTMLElement, enableAnim: boolean = true) {
49
60
  window.scroll({
50
61
  top: top > 0 ? top : 0,
51
62
  left: 0,
52
- behavior: 'smooth',
63
+ // behavior: 'smooth',
53
64
  })
54
65
  } else {
55
66
  // 无动画
@@ -57,53 +68,6 @@ export function scrollIntoView(elem: HTMLElement, enableAnim: boolean = true) {
57
68
  }
58
69
  }
59
70
 
60
- /** 显示对话框 */
61
- export function buildDialog (
62
- html: HTMLElement,
63
- onConfirm?: (btnElem: HTMLElement) => boolean | void,
64
- onCancel?: () => boolean | void,
65
- ): HTMLElement {
66
- const dialogElem = Utils.createElement(
67
- `<div class="atk-layer-dialog-wrap">
68
- <div class="atk-layer-dialog">
69
- <div class="atk-layer-dialog-content"></div>
70
- <div class="atk-layer-dialog-action">
71
- </div>`
72
- )
73
-
74
- // 按钮
75
- const actionElem = dialogElem.querySelector<HTMLElement>('.atk-layer-dialog-action')!
76
- const onclick =
77
- (f: (btnElem: HTMLElement) => boolean | void) =>
78
- (evt: Event) => {
79
- const returnVal = f(evt.currentTarget as HTMLElement)
80
- if (returnVal === undefined || returnVal === true) {
81
- dialogElem.remove()
82
- }
83
- }
84
-
85
- if (typeof onConfirm === 'function') {
86
- const btn = Utils.createElement<HTMLButtonElement>(
87
- '<button data-action="confirm">确定</button>'
88
- )
89
- btn.onclick = onclick(onConfirm)
90
- actionElem.appendChild(btn)
91
- }
92
-
93
- if (typeof onCancel === 'function') {
94
- const btn = Utils.createElement<HTMLButtonElement>(
95
- '<button data-action="cancel">取消</button>'
96
- )
97
- btn.onclick = onclick(onCancel)
98
- actionElem.appendChild(btn)
99
- }
100
-
101
- // 内容
102
- dialogElem.querySelector('.atk-layer-dialog-content')!.appendChild(html)
103
-
104
- return dialogElem
105
- }
106
-
107
71
  /** 显示消息 */
108
72
  export function showNotify(
109
73
  wrapElem: HTMLElement,
@@ -168,8 +132,8 @@ export function playFadeOutAnim(elem: HTMLElement, after?: () => void) {
168
132
  }
169
133
 
170
134
  /** 设置全局错误 */
171
- export function setError(parentElem: HTMLElement|Context, html: string | HTMLElement | null) {
172
- if (parentElem instanceof Context) parentElem = parentElem.rootEl
135
+ export function setError(parentElem: HTMLElement|Context, html: string | HTMLElement | null, title: string = '<span class="atk-error-title">Artalk Error</span>') {
136
+ if (parentElem instanceof Context) parentElem = parentElem.$root
173
137
 
174
138
  let elem = parentElem.querySelector<HTMLElement>('.atk-error-layer')
175
139
  if (html === null) {
@@ -178,7 +142,7 @@ export function setError(parentElem: HTMLElement|Context, html: string | HTMLEle
178
142
  }
179
143
  if (!elem) {
180
144
  elem = Utils.createElement(
181
- '<div class="atk-error-layer"><span class="atk-error-title">Artalk Error</span><span class="atk-error-text"></span></div>'
145
+ `<div class="atk-error-layer">${title}<span class="atk-error-text"></span></div>`
182
146
  )
183
147
  parentElem.appendChild(elem)
184
148
  }
@@ -193,3 +157,29 @@ export function setError(parentElem: HTMLElement|Context, html: string | HTMLEle
193
157
  errorTextEl.innerText = html
194
158
  }
195
159
  }
160
+
161
+ export function getScrollBarWidth() {
162
+ const inner = document.createElement('p')
163
+ inner.style.width = '100%'
164
+ inner.style.height = '200px'
165
+
166
+ const outer = document.createElement('div')
167
+ outer.style.position = 'absolute'
168
+ outer.style.top = '0px'
169
+ outer.style.left = '0px'
170
+ outer.style.visibility = 'hidden'
171
+ outer.style.width = '200px'
172
+ outer.style.height = '150px'
173
+ outer.style.overflow = 'hidden'
174
+ outer.appendChild(inner)
175
+
176
+ document.body.appendChild(outer)
177
+ const w1 = inner.offsetWidth
178
+ outer.style.overflow = 'scroll'
179
+ let w2 = inner.offsetWidth
180
+ if (w1 === w2) w2 = outer.clientWidth
181
+
182
+ document.body.removeChild(outer)
183
+
184
+ return (w1 - w2)
185
+ }
package/src/lib/user.ts CHANGED
@@ -1,13 +1,7 @@
1
- import { ArtalkConfig } from "~/types/artalk-config"
1
+ import ArtalkConfig, { LocalUser } from "~/types/artalk-config"
2
2
 
3
3
  export default class User {
4
- public data: {
5
- nick: string,
6
- email: string,
7
- link: string,
8
- token: string,
9
- isAdmin: boolean
10
- }
4
+ public data: LocalUser
11
5
 
12
6
  constructor (conf: ArtalkConfig) {
13
7
  // 从 localStorage 导入
package/src/lib/utils.ts CHANGED
@@ -1,37 +1,37 @@
1
1
  import libMarked from 'marked'
2
2
  import hanabi from 'hanabi'
3
- import Context from '../Context'
3
+ import Context from '../context'
4
4
 
5
- export function createElement<E extends HTMLElement = HTMLElement> (htmlStr: string = ''): E {
5
+ export function createElement<E extends HTMLElement = HTMLElement>(htmlStr: string = ''): E {
6
6
  const div = document.createElement('div')
7
7
  div.innerHTML = htmlStr.trim()
8
8
  return (div.firstElementChild || div) as E
9
9
  }
10
10
 
11
- export function getHeight (el: HTMLElement) {
11
+ export function getHeight(el: HTMLElement) {
12
12
  return parseFloat(getComputedStyle(el, null).height.replace('px', ''))
13
13
  }
14
14
 
15
- export function htmlEncode (str: string) {
15
+ export function htmlEncode(str: string) {
16
16
  const temp = document.createElement('div')
17
17
  temp.innerText = str
18
18
  const output = temp.innerHTML
19
19
  return output
20
20
  }
21
21
 
22
- export function htmlDecode (str: string) {
22
+ export function htmlDecode(str: string) {
23
23
  const temp = document.createElement('div')
24
24
  temp.innerHTML = str
25
25
  const output = temp.innerText
26
26
  return output
27
27
  }
28
28
 
29
- export function getQueryParam (name: string) {
29
+ export function getQueryParam(name: string) {
30
30
  const match = RegExp(`[?&]${name}=([^&]*)`).exec(window.location.search);
31
31
  return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
32
32
  }
33
33
 
34
- export function getOffset (el: HTMLElement) {
34
+ export function getOffset(el: HTMLElement) {
35
35
  const rect = el.getBoundingClientRect()
36
36
  return {
37
37
  top: rect.top + window.scrollY,
@@ -39,7 +39,7 @@ export function getOffset (el: HTMLElement) {
39
39
  }
40
40
  }
41
41
 
42
- export function padWithZeros (vNumber: number, width: number) {
42
+ export function padWithZeros(vNumber: number, width: number) {
43
43
  let numAsString = vNumber.toString()
44
44
  while (numAsString.length < width) {
45
45
  numAsString = `0${numAsString}`
@@ -47,7 +47,7 @@ export function padWithZeros (vNumber: number, width: number) {
47
47
  return numAsString
48
48
  }
49
49
 
50
- export function dateFormat (date: Date) {
50
+ export function dateFormat(date: Date) {
51
51
  const vDay = padWithZeros(date.getDate(), 2)
52
52
  const vMonth = padWithZeros(date.getMonth() + 1, 2)
53
53
  const vYear = padWithZeros(date.getFullYear(), 2)
@@ -57,7 +57,7 @@ export function dateFormat (date: Date) {
57
57
  return `${vYear}-${vMonth}-${vDay}`
58
58
  }
59
59
 
60
- export function timeAgo (date: Date) {
60
+ export function timeAgo(date: Date) {
61
61
  try {
62
62
  const oldTime = date.getTime()
63
63
  const currTime = new Date().getTime()
@@ -95,12 +95,31 @@ export function timeAgo (date: Date) {
95
95
  }
96
96
  }
97
97
 
98
+ export function getGravatarURL(ctx: Context, emailMD5: string) {
99
+ return `${(ctx.conf.gravatar?.mirror || '').replace(/\/$/, '')}/${emailMD5}?d=${encodeURIComponent(ctx.conf.gravatar?.default || '')}&s=80`
100
+ }
101
+
98
102
  export function sleep(ms: number) {
99
103
  return new Promise(resolve => setTimeout(resolve, ms));
100
104
  }
101
105
 
106
+ /** 版本号比较(a < b :-1 | 0 | b < a :1) */
107
+ export function versionCompare(a: string, b: string) {
108
+ const pa = a.split('.')
109
+ const pb = b.split('.')
110
+ for (let i = 0; i < 3; i++) {
111
+ const na = Number(pa[i])
112
+ const nb = Number(pb[i])
113
+ if (na > nb) return 1
114
+ if (nb > na) return -1
115
+ if (!Number.isNaN(na) && Number.isNaN(nb)) return 1
116
+ if (Number.isNaN(na) && !Number.isNaN(nb)) return -1
117
+ }
118
+ return 0
119
+ }
120
+
102
121
  let markedInstance: any = null
103
- export function marked (ctx: Context, src: string): string {
122
+ export function marked(ctx: Context, src: string): string {
104
123
  if (!markedInstance) {
105
124
  const renderer = new libMarked.Renderer()
106
125
  const linkRenderer = renderer.link
package/src/main.ts CHANGED
@@ -1,3 +1,3 @@
1
- import Artalk from "./Artalk"
1
+ import Artalk from './artalk'
2
2
 
3
3
  export default Artalk
@@ -2,6 +2,7 @@
2
2
  * {
3
3
  --at-color-font: #2a2e2e;
4
4
  --at-color-deep: #2a2e2e;
5
+ --at-color-sub: #757575;
5
6
  --at-color-grey: #999;
6
7
  --at-color-meta: #757e91;
7
8
  --at-color-border: #eceff2;
@@ -14,13 +15,16 @@
14
15
  --at-color-main: #0083ff;
15
16
  --at-color-red: #ff5652;
16
17
  --at-color-pink: #fa5a57;
17
- --at-color-yellow: #ffa928;
18
+ --at-color-yellow: #ff7c37;
19
+ --at-color-green: #4caf50;
18
20
  --at-color-quote: #687a86;
21
+ --at-color-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0), rgba(255, 255, 255));
19
22
  }
20
23
 
21
24
  &.atk-dark-mode * {
22
25
  --at-color-font: #fff;
23
26
  --at-color-deep: #e7e7e7;
27
+ --at-color-sub: #e7e7e7;
24
28
  --at-color-grey: #fff;
25
29
  --at-color-meta: #fff;
26
30
  --at-color-border: #2d3235;
@@ -33,7 +37,9 @@
33
37
  --at-color-main: #0083ff;
34
38
  --at-color-red: #ff5652;
35
39
  --at-color-pink: #fa5a57;
36
- --at-color-yellow: #ffa928;
40
+ --at-color-yellow: #ff7c37;
41
+ --at-color-green: #4caf50;
37
42
  --at-color-quote: #687a86;
43
+ --at-color-gradient: linear-gradient(180deg, rgba(30, 34, 36, 0), rgba(30, 34, 36, 1));
38
44
  }
39
45
  }
@@ -1,6 +1,7 @@
1
1
  .atk-slim-scrollbar() {
2
2
  &::-webkit-scrollbar {
3
3
  width: 4px;
4
+ height: 4px;
4
5
  background: transparent;
5
6
  }
6
7
 
@@ -17,3 +18,83 @@
17
18
  background: rgba(41,47,53,.77);
18
19
  }
19
20
  }
21
+
22
+ .atk-list-text-actions() {
23
+ display: flex;
24
+ flex-direction: column;
25
+ flex: auto;
26
+ padding: 0 35px;
27
+ padding-top: 10px;
28
+ padding-bottom: 0;
29
+ flex-wrap: wrap;
30
+ align-content: flex-start;
31
+
32
+ .atk-item {
33
+ position: relative;
34
+ font-size: 17px;
35
+ max-width: 5em;
36
+ display: inline-flex;
37
+ margin-right: 15px;
38
+ margin-bottom: 6px;
39
+ cursor: pointer;
40
+ user-select: none;
41
+
42
+ &.atk-green:after {
43
+ position: absolute;
44
+ content: ' ';
45
+ height: 5px;
46
+ width: 100%;
47
+ left: 0;
48
+ bottom: 5px;
49
+ background: rgba(50, 199, 134, 0.39);
50
+ }
51
+
52
+ &.atk-yellow:after {
53
+ position: absolute;
54
+ content: ' ';
55
+ height: 5px;
56
+ width: 100%;
57
+ left: 0;
58
+ bottom: 5px;
59
+ background: rgba(255, 203, 0, 39%);
60
+ }
61
+ }
62
+ }
63
+
64
+ .atk-list-btn-actions() {
65
+ display: flex;
66
+ flex-direction: row;
67
+ align-items: center;
68
+ padding: 0 20px;
69
+
70
+ .atk-item {
71
+ &:hover {
72
+ background: @color-bg-grey;
73
+ }
74
+
75
+ &:not(:last-child) {
76
+ margin-right: 15px;
77
+ }
78
+
79
+ cursor: pointer;
80
+ user-select: none;
81
+ display: flex;
82
+ width: 35px;
83
+ height: 35px;
84
+ border-radius: 2px;
85
+ align-items: center;
86
+ justify-content: center;
87
+
88
+ &.atk-item-close {
89
+ margin-left: 10px;
90
+ }
91
+ }
92
+
93
+ .atk-icon {
94
+ background-color: @color-meta;
95
+ }
96
+
97
+ .atk-icon-yes {
98
+ background-color: @color-main;
99
+ }
100
+ }
@@ -1,5 +1,6 @@
1
1
  @color-font: var(--at-color-font);
2
2
  @color-deep: var(--at-color-deep);
3
+ @color-sub: var(--at-color-sub);
3
4
  @color-grey: var(--at-color-grey);
4
5
  @color-meta: var(--at-color-meta);
5
6
  @color-light: var(--at-color-light);
@@ -13,7 +14,9 @@
13
14
  @color-red: var(--at-color-red);
14
15
  @color-pink: var(--at-color-pink);
15
16
  @color-yellow: var(--at-color-yellow);
17
+ @color-green: var(--at-color-green);
16
18
  @color-quote: var(--at-color-quote);
19
+ @color-gradient: var(--at-color-gradient);
17
20
 
18
21
  @font-family: "Helvetica Neue", "PingFangSC-Light", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;
19
22
  @font-size: 14px;