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.
- package/README.md +7 -60
- package/dist/Artalk.css +1 -1
- package/dist/Artalk.es.js +5996 -1
- package/dist/Artalk.iife.js +266 -1
- package/dist/Artalk.js +266 -1
- package/package.json +3 -4
- package/src/api/index.ts +269 -0
- package/src/api/request.ts +125 -0
- package/src/artalk.ts +153 -0
- package/src/components/_component.ts.txt +10 -0
- package/src/components/action-btn.ts +169 -0
- package/src/components/admin/page-list.ts +194 -0
- package/src/components/admin/site-list-floater.ts +98 -0
- package/src/components/admin/site-list.ts +289 -0
- package/src/components/comment.ts +478 -0
- package/src/components/dialog.ts +63 -0
- package/src/components/editor-plugs/{_ExamplePlug.ts.txt → _example-plug.ts.txt} +0 -0
- package/src/components/editor-plugs/{EditorPlug.ts → editor-plug.ts} +3 -3
- package/src/components/editor-plugs/{EmoticonsPlug.less → emoticons-plug.less} +0 -0
- package/src/components/editor-plugs/{EmoticonsPlug.ts → emoticons-plug.ts} +9 -9
- package/src/components/editor-plugs/{PreviewPlug.less → preview-plug.less} +0 -0
- package/src/components/editor-plugs/{PreviewPlug.ts → preview-plug.ts} +10 -10
- package/src/components/{Editor.ts → editor.ts} +104 -102
- package/src/components/html/comment.html +2 -6
- package/src/components/html/list.html +0 -1
- package/src/components/html/sidebar.html +18 -4
- package/src/components/item-text-editor.ts +136 -0
- package/src/components/layer.ts +135 -0
- package/src/components/list-lite.ts +507 -0
- package/src/components/list.ts +172 -0
- package/src/components/pagination.ts +150 -0
- package/src/components/read-more-btn.ts +71 -0
- package/src/components/sidebar-view.ts +26 -0
- package/src/components/sidebar-views/comments-view.ts +66 -0
- package/src/components/sidebar-views/pages-view.ts +61 -0
- package/src/components/sidebar-views/setting-view.ts +21 -0
- package/src/components/sidebar-views/sites-view.ts +42 -0
- package/src/components/sidebar-views/transfer-view.ts +218 -0
- package/src/components/sidebar.ts +288 -0
- package/src/context.ts +42 -0
- package/src/default-conf.ts +42 -0
- package/src/lib/checker/admin-checker.ts +33 -0
- package/src/lib/checker/captcha-checker.ts +38 -0
- package/src/lib/checker/index.ts +134 -0
- package/src/lib/component.ts +3 -3
- package/src/lib/md5.js +6 -0
- package/src/lib/ui.ts +64 -74
- package/src/lib/user.ts +2 -8
- package/src/lib/utils.ts +30 -11
- package/src/main.ts +1 -1
- package/src/style/_color.less +8 -2
- package/src/style/_extend.less +81 -0
- package/src/style/_variables.less +3 -0
- package/src/style/comment.less +60 -24
- package/src/style/editor.less +3 -0
- package/src/style/item-text-editor.less +35 -0
- package/src/style/list.less +51 -32
- package/src/style/main.less +75 -1
- package/src/style/page-list.less +50 -0
- package/src/style/pagination.less +58 -0
- package/src/style/sidebar.less +274 -199
- package/src/style/site-list.less +208 -0
- package/tsconfig.json +27 -0
- package/types/artalk-config.d.ts +68 -51
- package/types/artalk-data.d.ts +38 -1
- package/types/event.d.ts +73 -0
- package/types/index.d.ts +1 -6
- package/.github/workflows/npm.yml +0 -31
- package/.github/workflows/pages.yml +0 -30
- package/dist/src/Event.d.ts +0 -59
- package/dist/types/artalk-config.d.ts +0 -79
- package/dist/types/artalk-data.d.ts +0 -153
- package/dist/types/global.d.ts +0 -1
- package/dist/types/index.d.ts +0 -8
- package/dist/types/vite-env.d.ts +0 -1
- package/example/pjax/res1.html +0 -41
- package/example/pjax/res2.html +0 -41
- package/src/Artalk.ts +0 -160
- package/src/Constant.ts +0 -6
- package/src/Context.ts +0 -34
- package/src/Event.d.ts +0 -59
- package/src/components/Comment.ts +0 -402
- package/src/components/Layer.ts +0 -138
- package/src/components/List.ts +0 -162
- package/src/components/ListLite.ts +0 -407
- package/src/components/Sidebar.ts +0 -141
- package/src/components/sidebar-views/AdminView.ts +0 -523
- package/src/components/sidebar-views/MessageView.ts +0 -48
- package/src/components/sidebar-views/SidebarView.ts +0 -24
- package/src/lib/api.ts +0 -380
- 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
|
+
}
|
package/src/lib/component.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
import Context from '../
|
|
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 '../
|
|
2
|
+
import Context from '../context'
|
|
3
3
|
|
|
4
4
|
/** 显示加载 */
|
|
5
5
|
export function showLoading(parentElem: HTMLElement|Context) {
|
|
6
|
-
if (parentElem instanceof Context) parentElem = parentElem
|
|
7
|
-
|
|
8
|
-
let
|
|
9
|
-
if (
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
parentElem.appendChild(
|
|
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
|
|
32
|
+
if (parentElem instanceof Context) parentElem = parentElem.$root
|
|
24
33
|
|
|
25
|
-
const
|
|
26
|
-
if (
|
|
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(
|
|
31
|
-
const
|
|
32
|
-
|
|
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
|
|
35
|
-
const
|
|
45
|
+
const rect = el.getBoundingClientRect()
|
|
46
|
+
const elemTop = rect.top + docViewTop
|
|
47
|
+
const elemBottom = elemTop + rect.height
|
|
36
48
|
|
|
37
|
-
return elemBottom <= docViewBottom
|
|
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
|
|
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
|
-
|
|
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 {
|
|
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 '../
|
|
3
|
+
import Context from '../context'
|
|
4
4
|
|
|
5
|
-
export function createElement<E extends HTMLElement = HTMLElement>
|
|
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
|
|
11
|
+
export function getHeight(el: HTMLElement) {
|
|
12
12
|
return parseFloat(getComputedStyle(el, null).height.replace('px', ''))
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export function htmlEncode
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
package/src/style/_color.less
CHANGED
|
@@ -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: #
|
|
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: #
|
|
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
|
}
|
package/src/style/_extend.less
CHANGED
|
@@ -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;
|