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,135 @@
|
|
|
1
|
+
import Context from '../context'
|
|
2
|
+
import Component from '../lib/component'
|
|
3
|
+
import * as Utils from '../lib/utils'
|
|
4
|
+
import * as Ui from '../lib/ui'
|
|
5
|
+
|
|
6
|
+
export default class Layer extends Component {
|
|
7
|
+
private name: string
|
|
8
|
+
private $wrap: HTMLElement
|
|
9
|
+
private $mask: HTMLElement
|
|
10
|
+
|
|
11
|
+
private maskClickHideEnable: boolean = true
|
|
12
|
+
|
|
13
|
+
private bodyStyleOrgOverflow = ''
|
|
14
|
+
private bodyStyleOrgPaddingRight = ''
|
|
15
|
+
|
|
16
|
+
constructor (ctx: Context, name: string, el?: HTMLElement) {
|
|
17
|
+
super(ctx)
|
|
18
|
+
|
|
19
|
+
this.name = name
|
|
20
|
+
const { $wrap, $mask } = GetLayerWrap(ctx)
|
|
21
|
+
this.$wrap = $wrap
|
|
22
|
+
this.$mask = $mask
|
|
23
|
+
|
|
24
|
+
this.$el = this.$wrap.querySelector(`[data-layer-name="${name}"].atk-layer-item`)!
|
|
25
|
+
if (this.$el === null) {
|
|
26
|
+
// 若传递 layer 元素为空
|
|
27
|
+
if (!el) {
|
|
28
|
+
this.$el = Utils.createElement()
|
|
29
|
+
this.$el.classList.add('atk-layer-item')
|
|
30
|
+
} else {
|
|
31
|
+
this.$el = el
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
this.$el.setAttribute('data-layer-name', name)
|
|
35
|
+
this.$el.style.display = 'none'
|
|
36
|
+
|
|
37
|
+
// 添加到 layers wrap 中
|
|
38
|
+
this.$wrap.append(this.$el)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
getName () {
|
|
42
|
+
return this.name
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
getWrapEl () {
|
|
46
|
+
return this.$wrap
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
getEl () {
|
|
50
|
+
return this.$el
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private static hideTimeoutList: number[] = []
|
|
54
|
+
|
|
55
|
+
show () {
|
|
56
|
+
Layer.hideTimeoutList.forEach(item => {
|
|
57
|
+
clearTimeout(item)
|
|
58
|
+
})
|
|
59
|
+
Layer.hideTimeoutList = []
|
|
60
|
+
|
|
61
|
+
this.$wrap.style.display = 'block'
|
|
62
|
+
this.$mask.style.display = 'block'
|
|
63
|
+
this.$mask.classList.add('atk-fade-in')
|
|
64
|
+
this.$el.style.display = ''
|
|
65
|
+
|
|
66
|
+
this.$mask.onclick = () => {
|
|
67
|
+
if (this.maskClickHideEnable) this.hide()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// body style 禁止滚动 + 防抖
|
|
71
|
+
this.bodyStyleOrgOverflow = document.body.style.overflow
|
|
72
|
+
this.bodyStyleOrgPaddingRight = document.body.style.paddingRight
|
|
73
|
+
document.body.style.overflow = 'hidden'
|
|
74
|
+
|
|
75
|
+
const bpr = parseInt(window.getComputedStyle(document.body, null).getPropertyValue('padding-right'), 10)
|
|
76
|
+
document.body.style.paddingRight = `${Ui.getScrollBarWidth() + bpr || 0}px`
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
hide () {
|
|
80
|
+
Layer.hideTimeoutList.push(window.setTimeout(() => {
|
|
81
|
+
this.$wrap.style.display = 'none'
|
|
82
|
+
// body style 禁止滚动解除
|
|
83
|
+
document.body.style.overflow = this.bodyStyleOrgOverflow
|
|
84
|
+
document.body.style.paddingRight = this.bodyStyleOrgPaddingRight
|
|
85
|
+
}, 450))
|
|
86
|
+
|
|
87
|
+
this.$wrap.classList.add('atk-fade-out')
|
|
88
|
+
Layer.hideTimeoutList.push(window.setTimeout(() => {
|
|
89
|
+
this.$wrap.style.display = 'none'
|
|
90
|
+
this.$wrap.classList.remove('atk-fade-out')
|
|
91
|
+
}, 200))
|
|
92
|
+
|
|
93
|
+
this.$el.style.display = 'none'
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
setMaskClickHide (enable: boolean) {
|
|
97
|
+
this.maskClickHideEnable = enable
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** 销毁 - 无动画 */
|
|
101
|
+
disposeNow () {
|
|
102
|
+
document.body.style.overflow = ''
|
|
103
|
+
this.$el.remove()
|
|
104
|
+
// this.$el dispose
|
|
105
|
+
this.checkCleanLayer()
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** 销毁 */
|
|
109
|
+
dispose () {
|
|
110
|
+
this.hide()
|
|
111
|
+
this.$el.remove()
|
|
112
|
+
// this.$el dispose
|
|
113
|
+
this.checkCleanLayer()
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
checkCleanLayer () {
|
|
117
|
+
if (this.getWrapEl().querySelectorAll('.atk-layer-item').length === 0) {
|
|
118
|
+
this.$wrap.style.display = 'none'
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function GetLayerWrap (ctx: Context): { $wrap: HTMLElement, $mask: HTMLElement } {
|
|
124
|
+
let $wrap = document.querySelector<HTMLElement>(`.atk-layer-wrap#ctx-${ctx.cid}`)
|
|
125
|
+
if (!$wrap) {
|
|
126
|
+
$wrap = Utils.createElement(
|
|
127
|
+
`<div class="atk-layer-wrap" id="ctx-${ctx.cid}" style="display: none;"><div class="atk-layer-mask"></div></div>`
|
|
128
|
+
)
|
|
129
|
+
document.body.appendChild($wrap)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const $mask = $wrap.querySelector<HTMLElement>('.atk-layer-mask')!
|
|
133
|
+
|
|
134
|
+
return { $wrap, $mask }
|
|
135
|
+
}
|
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
import Context from '../context'
|
|
2
|
+
import Component from '../lib/component'
|
|
3
|
+
import * as Utils from '../lib/utils'
|
|
4
|
+
import * as Ui from '../lib/ui'
|
|
5
|
+
import Api from '../api'
|
|
6
|
+
import Comment from './comment'
|
|
7
|
+
import { ListData, CommentData, NotifyData, ApiVersionData } from '~/types/artalk-data'
|
|
8
|
+
import Pagination from './pagination'
|
|
9
|
+
import ReadMoreBtn from './read-more-btn'
|
|
10
|
+
|
|
11
|
+
export default class ListLite extends Component {
|
|
12
|
+
private $parent: HTMLElement
|
|
13
|
+
|
|
14
|
+
private $commentsWrap: HTMLElement
|
|
15
|
+
public comments: Comment[] = []
|
|
16
|
+
|
|
17
|
+
public data?: ListData
|
|
18
|
+
private pageSize: number = 15 // 每次请求获取量
|
|
19
|
+
private offset: number = 0
|
|
20
|
+
public type?: 'all'|'mentions'|'mine'|'pending'
|
|
21
|
+
|
|
22
|
+
public noCommentText: string
|
|
23
|
+
public renderComment?: (comment: Comment) => void
|
|
24
|
+
public paramsEditor?: (params: any) => void
|
|
25
|
+
public onAfterLoad?: (data: ListData) => void
|
|
26
|
+
|
|
27
|
+
private isLoading: boolean = false
|
|
28
|
+
public isFirstLoad = true
|
|
29
|
+
|
|
30
|
+
public flatMode?: boolean
|
|
31
|
+
|
|
32
|
+
public pageMode: 'pagination'|'read-more' = 'pagination'
|
|
33
|
+
public pagination?: Pagination
|
|
34
|
+
public readMoreBtn?: ReadMoreBtn
|
|
35
|
+
public autoLoadScrollEvent?: any
|
|
36
|
+
public autoLoadListenerAt?: HTMLElement
|
|
37
|
+
|
|
38
|
+
public unread: NotifyData[] = []
|
|
39
|
+
public unreadHighlight = false
|
|
40
|
+
|
|
41
|
+
constructor (ctx: Context, $parent: HTMLElement) {
|
|
42
|
+
super(ctx)
|
|
43
|
+
|
|
44
|
+
this.$parent = $parent
|
|
45
|
+
this.$el = Utils.createElement(
|
|
46
|
+
`<div class="atk-list-lite">
|
|
47
|
+
<div class="atk-list-comments-wrap"></div>
|
|
48
|
+
</div>`)
|
|
49
|
+
this.$commentsWrap = this.$el.querySelector('.atk-list-comments-wrap')!
|
|
50
|
+
|
|
51
|
+
// 查看更多
|
|
52
|
+
this.pageSize = this.conf.pagination ? (this.conf.pagination.pageSize || this.pageSize) : this.pageSize
|
|
53
|
+
|
|
54
|
+
this.noCommentText = this.conf.noComment || '无评论'
|
|
55
|
+
|
|
56
|
+
// 评论时间自动更新
|
|
57
|
+
window.setInterval(() => {
|
|
58
|
+
this.$el.querySelectorAll<HTMLElement>('[data-atk-comment-date]').forEach(el => {
|
|
59
|
+
const date = el.getAttribute('data-atk-comment-date')
|
|
60
|
+
el.innerText = Utils.timeAgo(new Date(Number(date)))
|
|
61
|
+
})
|
|
62
|
+
}, 30 * 1000) // 30s 更新一次
|
|
63
|
+
|
|
64
|
+
this.ctx.on('unread-update', (data) => (this.updateUnread(data.notifies)))
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
public async reqComments(offset: number = 0) {
|
|
68
|
+
if (offset === 0 && this.pageMode !== 'pagination') { this.clearAllComments() }
|
|
69
|
+
|
|
70
|
+
// set loading
|
|
71
|
+
const showLoading = () => {
|
|
72
|
+
this.isLoading = true
|
|
73
|
+
if (offset === 0) Ui.showLoading(this.$el)
|
|
74
|
+
else if (this.pageMode === 'read-more') this.readMoreBtn!.setLoading(true)
|
|
75
|
+
else if (this.pageMode === 'pagination') this.pagination!.setLoading(true)
|
|
76
|
+
}
|
|
77
|
+
const hideLoading = () => {
|
|
78
|
+
this.isLoading = false
|
|
79
|
+
if (offset === 0) Ui.hideLoading(this.$el)
|
|
80
|
+
else if (this.pageMode === 'read-more') this.readMoreBtn!.setLoading(false)
|
|
81
|
+
else if (this.pageMode === 'pagination') this.pagination!.setLoading(false)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
showLoading()
|
|
85
|
+
this.ctx.trigger('comments-load')
|
|
86
|
+
|
|
87
|
+
let listData: ListData
|
|
88
|
+
try {
|
|
89
|
+
listData = await new Api(this.ctx).get(offset, this.type, this.flatMode, this.paramsEditor)
|
|
90
|
+
} catch (e: any) {
|
|
91
|
+
this.onError(e.msg || String(e))
|
|
92
|
+
throw e
|
|
93
|
+
} finally {
|
|
94
|
+
hideLoading()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// version check
|
|
98
|
+
if (this.ctx.conf.versionCheck) {
|
|
99
|
+
const needUpdate = this.apiVersionCheck(listData.api_version || {})
|
|
100
|
+
if (needUpdate) return
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// load data
|
|
104
|
+
this.offset = offset
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
this.onLoad(listData, offset)
|
|
108
|
+
|
|
109
|
+
if (this.onAfterLoad) {
|
|
110
|
+
this.onAfterLoad(listData)
|
|
111
|
+
}
|
|
112
|
+
} catch (e: any) {
|
|
113
|
+
this.onError(String(e))
|
|
114
|
+
throw e
|
|
115
|
+
} finally {
|
|
116
|
+
hideLoading()
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
public onLoad(data: ListData, offset: number) {
|
|
121
|
+
Ui.setError(this.$el, null)
|
|
122
|
+
|
|
123
|
+
if (this.pageMode === 'pagination') {
|
|
124
|
+
this.clearAllComments()
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
this.data = data
|
|
128
|
+
this.importComments(data.comments)
|
|
129
|
+
|
|
130
|
+
// onLoad 时初始化
|
|
131
|
+
if (this.isFirstLoad) {
|
|
132
|
+
this.onLoadInit()
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (this.pageMode === 'pagination') {
|
|
136
|
+
this.pagination!.update(offset, this.data?.total_parents || 0)
|
|
137
|
+
}
|
|
138
|
+
if (this.pageMode === 'read-more') {
|
|
139
|
+
if (this.hasMoreComments) this.readMoreBtn!.show()
|
|
140
|
+
else this.readMoreBtn!.hide()
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
this.ctx.trigger('unread-update', { notifies: data.unread || [] })
|
|
144
|
+
|
|
145
|
+
this.isFirstLoad = false
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** 初始化加载更多 */
|
|
149
|
+
onLoadInit() {
|
|
150
|
+
if (this.autoLoadScrollEvent) {
|
|
151
|
+
const at = this.autoLoadListenerAt || document
|
|
152
|
+
at.removeEventListener('scroll', this.autoLoadScrollEvent)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (this.pageMode === 'read-more') {
|
|
156
|
+
// 阅读更多按钮
|
|
157
|
+
const readMoreBtn = new ReadMoreBtn({
|
|
158
|
+
pageSize: this.pageSize,
|
|
159
|
+
total: 0,
|
|
160
|
+
onClick: async () => {
|
|
161
|
+
const offset = this.offset + this.pageSize
|
|
162
|
+
await this.reqComments(offset)
|
|
163
|
+
},
|
|
164
|
+
})
|
|
165
|
+
if (this.readMoreBtn) this.readMoreBtn.$el.replaceWith(readMoreBtn.$el)
|
|
166
|
+
else this.$el.append(readMoreBtn.$el)
|
|
167
|
+
this.readMoreBtn = readMoreBtn
|
|
168
|
+
|
|
169
|
+
// 滚动到底部自动加载
|
|
170
|
+
if (this.conf.pagination?.autoLoad) {
|
|
171
|
+
this.autoLoadScrollEvent = () => {
|
|
172
|
+
if (this.pageMode !== 'read-more') return
|
|
173
|
+
if (!this.hasMoreComments) return
|
|
174
|
+
if (this.isLoading) return
|
|
175
|
+
|
|
176
|
+
const $target = this.$el.querySelector<HTMLElement>('.atk-list-comments-wrap > .atk-comment-wrap:nth-last-child(3)') // 获取倒数第3个评论元素
|
|
177
|
+
if (!$target) return
|
|
178
|
+
if (Ui.isVisible($target, this.autoLoadListenerAt)) {
|
|
179
|
+
// 加载更多
|
|
180
|
+
this.readMoreBtn!.click()
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const at = this.autoLoadListenerAt || document
|
|
184
|
+
at.addEventListener('scroll', this.autoLoadScrollEvent)
|
|
185
|
+
}
|
|
186
|
+
} else if (this.pageMode === 'pagination') {
|
|
187
|
+
// 分页条
|
|
188
|
+
const pagination = new Pagination(this.parentCommentsCount, {
|
|
189
|
+
pageSize: this.pageSize,
|
|
190
|
+
onChange: async (offset) => {
|
|
191
|
+
await this.reqComments(offset)
|
|
192
|
+
// 滚动到第一个评论的位置
|
|
193
|
+
if (this.$parent) {
|
|
194
|
+
let topPos = 0
|
|
195
|
+
if (!this.autoLoadListenerAt && this.$parent) {
|
|
196
|
+
topPos = Utils.getOffset(this.$parent).top
|
|
197
|
+
}
|
|
198
|
+
const at = this.autoLoadListenerAt || window
|
|
199
|
+
at.scroll({
|
|
200
|
+
top: topPos,
|
|
201
|
+
left: 0,
|
|
202
|
+
})
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
})
|
|
206
|
+
if (this.pagination) this.pagination.$el.replaceWith(pagination.$el)
|
|
207
|
+
else this.$el.append(pagination.$el)
|
|
208
|
+
this.pagination = pagination
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
public onError(msg: any) {
|
|
213
|
+
msg = String(msg)
|
|
214
|
+
console.error(msg)
|
|
215
|
+
if (this.isFirstLoad || this.pageMode === 'pagination') {
|
|
216
|
+
const errEl = Utils.createElement(`<span>${msg},无法获取评论列表数据<br/></span>`)
|
|
217
|
+
const retryBtn = Utils.createElement('<span style="cursor:pointer;">点击重新获取</span>')
|
|
218
|
+
retryBtn.onclick = () => {
|
|
219
|
+
this.reqComments(this.offset)
|
|
220
|
+
}
|
|
221
|
+
errEl.appendChild(retryBtn)
|
|
222
|
+
const adminBtn = Utils.createElement('<span atk-only-admin-show> | <span style="cursor:pointer;">打开控制台</span></span>')
|
|
223
|
+
adminBtn.onclick = () => {
|
|
224
|
+
this.ctx.trigger('sidebar-show')
|
|
225
|
+
}
|
|
226
|
+
if (!this.ctx.user.data.isAdmin) {
|
|
227
|
+
adminBtn.classList.add('atk-hide')
|
|
228
|
+
}
|
|
229
|
+
errEl.appendChild(adminBtn)
|
|
230
|
+
Ui.setError(this.$el, errEl)
|
|
231
|
+
} else {
|
|
232
|
+
this.readMoreBtn?.showErr(`获取失败`)
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** 刷新界面 */
|
|
237
|
+
public refreshUI() {
|
|
238
|
+
// 评论为空界面
|
|
239
|
+
const noComment = this.comments.length <= 0
|
|
240
|
+
let noCommentEl = this.$commentsWrap.querySelector<HTMLElement>('.atk-list-no-comment')
|
|
241
|
+
|
|
242
|
+
if (noComment) {
|
|
243
|
+
if (!noCommentEl) {
|
|
244
|
+
noCommentEl = Utils.createElement('<div class="atk-list-no-comment"></div>')
|
|
245
|
+
this.$commentsWrap.appendChild(noCommentEl)
|
|
246
|
+
noCommentEl.innerHTML = this.noCommentText
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (!noComment && noCommentEl)
|
|
251
|
+
noCommentEl.remove()
|
|
252
|
+
|
|
253
|
+
// 仅管理员显示控制
|
|
254
|
+
this.ctx.trigger('check-admin-show-el')
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** 创建新评论 */
|
|
258
|
+
public createComment(data: CommentData) {
|
|
259
|
+
const comment = new Comment(this.ctx, data)
|
|
260
|
+
comment.afterRender = () => {
|
|
261
|
+
if (this.renderComment) this.renderComment(comment)
|
|
262
|
+
}
|
|
263
|
+
comment.onDelete = (c) => {
|
|
264
|
+
this.deleteComment(c)
|
|
265
|
+
this.refreshUI()
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return comment
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** 导入评论 · 通过请求数据 */
|
|
272
|
+
public importComments(rawData: CommentData[]) {
|
|
273
|
+
// 查找并导入所有子评论
|
|
274
|
+
const queryImportChildren = (parentC: Comment) => {
|
|
275
|
+
const children = rawData.filter(o => o.rid === parentC.data.id)
|
|
276
|
+
if (children.length === 0) return
|
|
277
|
+
|
|
278
|
+
children.forEach((itemData: CommentData) => {
|
|
279
|
+
itemData.is_allow_reply = parentC.data.is_allow_reply
|
|
280
|
+
const childC = this.createComment(itemData)
|
|
281
|
+
childC.render()
|
|
282
|
+
parentC.putChild(childC)
|
|
283
|
+
queryImportChildren(childC) // 递归
|
|
284
|
+
})
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// 开始处理 rawData
|
|
288
|
+
if (!this.flatMode) {
|
|
289
|
+
rawData.filter((o) => o.rid === 0).forEach((rootCommentData: CommentData) => {
|
|
290
|
+
if (rootCommentData.is_collapsed) rootCommentData.is_allow_reply = false
|
|
291
|
+
const rootComment = this.createComment(rootCommentData)
|
|
292
|
+
rootComment.render()
|
|
293
|
+
this.comments.push(rootComment) // 将评论导入 comments 总表中
|
|
294
|
+
|
|
295
|
+
this.$commentsWrap.appendChild(rootComment.getEl())
|
|
296
|
+
rootComment.playFadeInAnim()
|
|
297
|
+
|
|
298
|
+
queryImportChildren(rootComment)
|
|
299
|
+
})
|
|
300
|
+
} else {
|
|
301
|
+
// 平铺模式
|
|
302
|
+
rawData.forEach((commentData: CommentData) => {
|
|
303
|
+
this.putCommentFlatMode(commentData, rawData, 'append')
|
|
304
|
+
})
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
this.eachComment(this.comments, (c) => {
|
|
308
|
+
this.checkMoreHide(c)
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
this.refreshUI()
|
|
312
|
+
this.ctx.trigger('comments-loaded')
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** 导入评论 · 平铺模式 */
|
|
316
|
+
private putCommentFlatMode(commentItem: CommentData, comments: CommentData[], insertMode: 'append'|'prepend') {
|
|
317
|
+
if (commentItem.is_collapsed) commentItem.is_allow_reply = false
|
|
318
|
+
const comment = this.createComment(commentItem)
|
|
319
|
+
if (commentItem.rid !== 0) {
|
|
320
|
+
const rComment = comments.find(o => o.id === commentItem.rid)
|
|
321
|
+
if (rComment) comment.replyTo = rComment
|
|
322
|
+
}
|
|
323
|
+
comment.render()
|
|
324
|
+
|
|
325
|
+
// 将评论导入 comments 总表中
|
|
326
|
+
if (insertMode === 'append') {
|
|
327
|
+
this.comments.push(comment)
|
|
328
|
+
} else {
|
|
329
|
+
this.comments.unshift(comment)
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (commentItem.visible) {
|
|
333
|
+
if (insertMode === 'append') {
|
|
334
|
+
this.$commentsWrap.appendChild(comment.getEl())
|
|
335
|
+
} else {
|
|
336
|
+
this.$commentsWrap.prepend(comment.getEl())
|
|
337
|
+
}
|
|
338
|
+
comment.playFadeInAnim()
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
this.checkMoreHide(comment)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/** 插入评论 · 首部添加 */
|
|
345
|
+
public insertComment(commentData: CommentData) {
|
|
346
|
+
if (!this.flatMode) {
|
|
347
|
+
const comment = this.createComment(commentData)
|
|
348
|
+
comment.render()
|
|
349
|
+
|
|
350
|
+
if (commentData.rid !== 0) {
|
|
351
|
+
this.findComment(commentData.rid)?.putChild(comment)
|
|
352
|
+
} else {
|
|
353
|
+
this.$commentsWrap.prepend(comment.getEl())
|
|
354
|
+
this.comments.unshift(comment)
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
Ui.scrollIntoView(comment.getEl()) // 滚动到可以见
|
|
358
|
+
comment.playFadeInAnim() // 播放评论渐出动画
|
|
359
|
+
|
|
360
|
+
this.checkMoreHide(comment)
|
|
361
|
+
} else {
|
|
362
|
+
this.putCommentFlatMode(commentData, this.comments.map(c => c.data), 'prepend')
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (this.data) this.data.total += 1 // 评论数增加 1
|
|
366
|
+
this.refreshUI() // 更新 list 界面
|
|
367
|
+
this.ctx.trigger('comments-loaded')
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
checkMoreHide(c: Comment) {
|
|
371
|
+
const childrenH = this.ctx.conf.heightLimit?.children
|
|
372
|
+
const contentH = this.ctx.conf.heightLimit?.content
|
|
373
|
+
const isChildrenLimit = typeof childrenH === 'number' && childrenH > 0
|
|
374
|
+
const isContentLimit = typeof contentH === 'number' && contentH > 0
|
|
375
|
+
|
|
376
|
+
// 子评论内容过多隐藏
|
|
377
|
+
if (isChildrenLimit && c.getIsRoot()) {
|
|
378
|
+
c.checkMoreHide(c.$children, childrenH || 300)
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// 评论内容过多隐藏
|
|
382
|
+
if (isContentLimit) {
|
|
383
|
+
c.checkMoreHide(c.$content, contentH || 200)
|
|
384
|
+
if (c.$replyTo) c.checkMoreHide(c.$replyTo, contentH || 200) // 平铺模式回复内容
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/** 获取评论总数 (包括子评论) */
|
|
389
|
+
get commentsCount(): number {
|
|
390
|
+
return Number(this.data?.total) || 0
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
get parentCommentsCount() {
|
|
394
|
+
return Number(this.data?.total_parents) || 0
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/** 是否还有更多的评论 */
|
|
398
|
+
get hasMoreComments(): boolean {
|
|
399
|
+
if (!this.data) return false
|
|
400
|
+
return this.data.total_parents > (this.offset + this.pageSize)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/** 遍历操作 Comment (包括子评论) */
|
|
404
|
+
public eachComment(commentList: Comment[], action: (comment: Comment, levelList: Comment[]) => boolean|void) {
|
|
405
|
+
if (commentList.length === 0) return
|
|
406
|
+
commentList.every((item) => {
|
|
407
|
+
if (action(item, commentList) === false) return false
|
|
408
|
+
this.eachComment(item.getChildren(), action)
|
|
409
|
+
return true
|
|
410
|
+
})
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/** 查找评论项 */
|
|
414
|
+
public findComment(id: number, src?: Comment[]): Comment|null {
|
|
415
|
+
if (!src) src = this.comments
|
|
416
|
+
let comment: Comment|null = null
|
|
417
|
+
this.eachComment(src, (item) => {
|
|
418
|
+
if (item.data.id === id) {
|
|
419
|
+
comment = item
|
|
420
|
+
return false
|
|
421
|
+
}
|
|
422
|
+
return true
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
return comment
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/** 获取评论总数 */
|
|
429
|
+
public getCommentCount(): number {
|
|
430
|
+
let count = 0
|
|
431
|
+
this.eachComment(this.comments, () => { count++ })
|
|
432
|
+
return count
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/** 删除评论 */
|
|
436
|
+
public deleteComment(comment: number|Comment) {
|
|
437
|
+
let findComment: Comment|null
|
|
438
|
+
if (typeof comment === 'number') {
|
|
439
|
+
findComment = this.findComment(comment)
|
|
440
|
+
if (!findComment) throw Error(`未找到评论 ${comment}`)
|
|
441
|
+
} else findComment = comment
|
|
442
|
+
|
|
443
|
+
findComment.getEl().remove()
|
|
444
|
+
this.eachComment(this.comments, (item, levelList) => {
|
|
445
|
+
if (item === findComment) {
|
|
446
|
+
levelList.splice(levelList.indexOf(item), 1)
|
|
447
|
+
return false
|
|
448
|
+
}
|
|
449
|
+
return true
|
|
450
|
+
})
|
|
451
|
+
|
|
452
|
+
this.refreshUI()
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/** 清空所有评论 */
|
|
456
|
+
public clearAllComments() {
|
|
457
|
+
this.$commentsWrap.innerHTML = ''
|
|
458
|
+
this.data = undefined
|
|
459
|
+
this.comments = []
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
public updateUnread(notifies: NotifyData[]) {
|
|
463
|
+
this.unread = notifies
|
|
464
|
+
|
|
465
|
+
// 高亮评论
|
|
466
|
+
if (this.unreadHighlight) {
|
|
467
|
+
this.eachComment(this.comments, (comment) => {
|
|
468
|
+
const notify = this.unread.find(o => o.comment_id === comment.data.id)
|
|
469
|
+
if (notify) {
|
|
470
|
+
comment.setUnread(true)
|
|
471
|
+
comment.setOpenURL(notify.read_link)
|
|
472
|
+
comment.openEvt = () => {
|
|
473
|
+
this.unread = this.unread.filter(o => o.comment_id !== comment.data.id) // remove
|
|
474
|
+
this.ctx.trigger('unread-update', {
|
|
475
|
+
notifies: this.unread
|
|
476
|
+
})
|
|
477
|
+
}
|
|
478
|
+
} else {
|
|
479
|
+
comment.setUnread(false)
|
|
480
|
+
}
|
|
481
|
+
})
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
public apiVersionCheck(versionData: ApiVersionData): boolean {
|
|
486
|
+
const needVersion = versionData?.fe_min_version || '0.0.0'
|
|
487
|
+
const needUpdate = Utils.versionCompare(needVersion, ARTALK_VERSION) === 1
|
|
488
|
+
if (needUpdate) {
|
|
489
|
+
// 需要更新
|
|
490
|
+
const errEl = Utils.createElement(`<div>前端 Artalk 版本已过时,请更新以获得完整体验<br/>`
|
|
491
|
+
+ `若您是站点管理员,请前往 “<a href="https://artalk.js.org/" target="_blank">官方文档</a>” 获取帮助`
|
|
492
|
+
+ `<br/><br/>`
|
|
493
|
+
+ `<span style="color: var(--at-color-meta);">前端版本 ${ARTALK_VERSION},需求版本 >= ${needVersion}</span><br/><br/>`
|
|
494
|
+
+ `</div>`)
|
|
495
|
+
const ignoreBtn = Utils.createElement('<span style="cursor:pointer;">忽略</span>')
|
|
496
|
+
ignoreBtn.onclick = () => {
|
|
497
|
+
Ui.setError(this.ctx, null)
|
|
498
|
+
this.ctx.conf.versionCheck = false
|
|
499
|
+
this.reqComments(0)
|
|
500
|
+
}
|
|
501
|
+
errEl.append(ignoreBtn)
|
|
502
|
+
Ui.setError(this.ctx, errEl, '<span class="atk-warn-title">Artalk Warn</span>')
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return needUpdate
|
|
506
|
+
}
|
|
507
|
+
}
|