artalk 2.4.4 → 2.5.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 +27 -0
- package/dist/Artalk.css +1 -1
- package/dist/Artalk.es.js +7066 -6256
- package/dist/Artalk.iife.js +1 -1
- package/dist/Artalk.js +1 -1
- package/dist/ArtalkLite.css +1 -1
- package/dist/ArtalkLite.es.js +5095 -4396
- package/dist/ArtalkLite.iife.js +1 -1
- package/dist/ArtalkLite.js +1 -1
- package/dist/i18n/jp.js +1 -1
- package/dist/i18n/zh-TW.js +1 -1
- package/dist/src/api/index.d.ts +2 -2
- package/dist/src/api/system.d.ts +5 -2
- package/dist/src/artalk.d.ts +42 -34
- package/dist/src/comment/comment.d.ts +2 -2
- package/dist/src/comment/height-limit.d.ts +30 -0
- package/dist/src/comment/render-ctx.d.ts +23 -0
- package/dist/src/comment/render.d.ts +2 -50
- package/dist/src/comment/renders/actions.d.ts +5 -0
- package/dist/src/comment/renders/avatar.d.ts +5 -0
- package/dist/src/comment/renders/content.d.ts +5 -0
- package/dist/src/comment/renders/header.d.ts +5 -0
- package/dist/src/comment/renders/index.d.ts +2 -0
- package/dist/src/comment/renders/pending.d.ts +5 -0
- package/dist/src/comment/renders/reply-at.d.ts +5 -0
- package/dist/src/comment/renders/reply-to.d.ts +5 -0
- package/dist/src/components/dialog.d.ts +1 -1
- package/dist/src/config.d.ts +8 -0
- package/dist/src/context.d.ts +16 -29
- package/dist/src/editor/edit-mode.d.ts +9 -0
- package/dist/src/editor/editor.d.ts +37 -45
- package/dist/src/editor/funcs.d.ts +2 -0
- package/dist/src/editor/mover.d.ts +8 -0
- package/dist/src/editor/plug-manager.d.ts +17 -0
- package/dist/src/editor/plugs/editor-plug.d.ts +13 -1
- package/dist/src/editor/reply.d.ts +9 -0
- package/dist/src/editor/submit.d.ts +16 -0
- package/dist/src/editor/ui.d.ts +25 -0
- package/dist/src/i18n/external.d.ts +70 -0
- package/dist/src/i18n/index.d.ts +11 -75
- package/dist/src/layer/index.d.ts +2 -2
- package/dist/src/layer/layer.d.ts +1 -1
- package/dist/src/lib/checker/captcha-renders.d.ts +5 -0
- package/dist/src/lib/checker/index.d.ts +16 -10
- package/dist/src/lib/dark-mode.d.ts +4 -0
- package/dist/src/lib/marked.d.ts +9 -0
- package/dist/src/lib/user.d.ts +6 -3
- package/dist/src/lib/utils.d.ts +1 -5
- package/dist/src/list/list-lite.d.ts +15 -11
- package/dist/src/list/list-nest.d.ts +2 -2
- package/dist/src/list/list-ui.d.ts +16 -0
- package/dist/src/list/list.d.ts +0 -1
- package/dist/src/list/paginator/adaptors/index.d.ts +12 -0
- package/dist/src/list/paginator/adaptors/pagination.d.ts +9 -0
- package/dist/src/list/paginator/adaptors/read-more.d.ts +10 -0
- package/dist/src/list/paginator/holder.d.ts +16 -0
- package/dist/src/list/paginator/index.d.ts +10 -0
- package/dist/src/service.d.ts +46 -0
- package/dist/types/artalk-config.d.ts +5 -2
- package/dist/types/artalk-data.d.ts +3 -3
- package/dist/types/context.d.ts +13 -20
- package/dist/types/global.d.ts +1 -0
- package/package.json +6 -7
- package/src/api/comment.ts +7 -7
- package/src/api/page.ts +0 -2
- package/src/api/request.ts +6 -4
- package/src/api/system.ts +3 -12
- package/src/api/upload.ts +3 -4
- package/src/api/user.ts +6 -8
- package/src/artalk.ts +103 -158
- package/src/comment/comment.ts +7 -6
- package/src/comment/height-limit.ts +100 -0
- package/src/comment/render-ctx.ts +30 -0
- package/src/comment/render.ts +31 -375
- package/src/comment/renders/actions.ts +114 -0
- package/src/comment/renders/avatar.ts +22 -0
- package/src/comment/renders/content.ts +39 -0
- package/src/comment/renders/header.ts +84 -0
- package/src/comment/renders/index.ts +19 -0
- package/src/comment/renders/pending.ts +12 -0
- package/src/comment/renders/reply-at.ts +16 -0
- package/src/comment/renders/reply-to.ts +24 -0
- package/src/config.ts +65 -0
- package/src/context.ts +33 -86
- package/src/defaults.ts +1 -0
- package/src/editor/edit-mode.ts +103 -0
- package/src/editor/editor.ts +101 -395
- package/src/editor/funcs.ts +97 -0
- package/src/editor/mover.ts +43 -0
- package/src/editor/plug-manager.ts +172 -0
- package/src/editor/plugs/editor-plug.ts +20 -2
- package/src/editor/plugs/{emoticons-plug.less → emoticons-plug.scss} +5 -4
- package/src/editor/plugs/emoticons-plug.ts +2 -1
- package/src/editor/plugs/header-input-plug.ts +11 -10
- package/src/editor/plugs/{preview-plug.less → preview-plug.scss} +1 -1
- package/src/editor/plugs/preview-plug.ts +8 -4
- package/src/editor/plugs/upload-plug.ts +12 -14
- package/src/editor/reply.ts +62 -0
- package/src/editor/submit.ts +99 -0
- package/src/editor/ui.ts +36 -0
- package/src/i18n/external.ts +10 -0
- package/src/i18n/index.ts +22 -19
- package/src/i18n/jp.ts +1 -1
- package/src/i18n/zh-TW.ts +1 -1
- package/src/layer/index.ts +2 -2
- package/src/layer/layer.ts +4 -4
- package/src/layer/sidebar-layer.ts +4 -2
- package/src/lib/checker/admin.ts +37 -0
- package/src/lib/checker/captcha-renders.ts +68 -0
- package/src/lib/checker/captcha.ts +26 -0
- package/src/lib/checker/index.ts +36 -20
- package/src/lib/dark-mode.ts +48 -0
- package/src/lib/marked.ts +133 -0
- package/src/lib/user.ts +13 -7
- package/src/lib/utils.ts +1 -123
- package/src/list/list-lite.ts +59 -162
- package/src/list/list-ui.ts +137 -0
- package/src/list/list.ts +33 -60
- package/src/list/paginator/adaptors/index.ts +21 -0
- package/src/list/paginator/adaptors/pagination.ts +42 -0
- package/src/list/paginator/adaptors/read-more.ts +56 -0
- package/src/list/paginator/holder.ts +56 -0
- package/src/list/paginator/index.ts +13 -0
- package/src/service.ts +118 -0
- package/src/style/{_color.less → _color.scss} +3 -3
- package/src/style/{_extend.less → _extend.scss} +1 -1
- package/src/style/_variables.scss +3 -0
- package/src/style/{comment.less → comment.scss} +36 -12
- package/src/style/{editor.less → editor.scss} +11 -11
- package/src/style/{list.less → list.scss} +12 -10
- package/src/style/{main.less → main.scss} +24 -23
- package/src/style/{sidebar-layer.less → sidebar-layer.scss} +8 -8
- package/types/artalk-config.d.ts +5 -2
- package/types/artalk-data.d.ts +3 -3
- package/types/context.d.ts +13 -20
- package/types/global.d.ts +1 -0
- package/CNAME +0 -1
- package/LICENSE +0 -165
- package/index.html +0 -157
- package/postcss.config.js +0 -5
- package/scripts/build-i18n.mjs +0 -46
- package/src/lib/checker/admin-checker.ts +0 -36
- package/src/lib/checker/captcha-checker.ts +0 -84
- package/src/style/_variables.less +0 -3
- package/test/ArtalkCount.html +0 -16
- package/test/ArtalkI18n.html +0 -16
- package/test/ArtalkLite.html +0 -15
- package/test/CSRF.html +0 -26
- package/tsconfig.json +0 -12
- package/vite-base.config.ts +0 -21
- package/vite-demo.config.ts +0 -12
- package/vite-lite.config.ts +0 -22
- package/vite.config.ts +0 -27
- /package/dist/src/lib/checker/{admin-checker.d.ts → admin.d.ts} +0 -0
- /package/dist/src/lib/checker/{captcha-checker.d.ts → captcha.d.ts} +0 -0
- /package/src/style/{pagination.less → pagination.scss} +0 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import ListLite from './list-lite'
|
|
2
|
+
import * as Utils from '../lib/utils'
|
|
3
|
+
import * as Ui from '../lib/ui'
|
|
4
|
+
import User from '../lib/user'
|
|
5
|
+
import $t from '../i18n'
|
|
6
|
+
|
|
7
|
+
/** 界面刷新 */
|
|
8
|
+
export function refreshUI(list: ListLite) {
|
|
9
|
+
// 无评论
|
|
10
|
+
const isNoComment = list.ctx.getCommentList().length <= 0
|
|
11
|
+
let $noComment = list.getCommentsWrapEl().querySelector<HTMLElement>('.atk-list-no-comment')
|
|
12
|
+
|
|
13
|
+
if (isNoComment) {
|
|
14
|
+
if (!$noComment) {
|
|
15
|
+
$noComment = Utils.createElement('<div class="atk-list-no-comment"></div>')
|
|
16
|
+
$noComment.innerHTML = list.noCommentText || list.ctx.conf.noComment || list.ctx.$t('noComment')
|
|
17
|
+
list.getCommentsWrapEl().appendChild($noComment)
|
|
18
|
+
}
|
|
19
|
+
} else {
|
|
20
|
+
$noComment?.remove()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// 仅管理员显示控制
|
|
24
|
+
list.ctx.checkAdminShowEl()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** 构建错误提示界面 */
|
|
28
|
+
export function renderErrorDialog(list: ListLite, errMsg: string, errData?: any): HTMLElement {
|
|
29
|
+
const errEl = Utils.createElement(`<span>${errMsg},${$t('listLoadFailMsg')}<br/></span>`)
|
|
30
|
+
|
|
31
|
+
const $retryBtn = Utils.createElement(`<span style="cursor:pointer;">${$t('listRetry')}</span>`)
|
|
32
|
+
$retryBtn.onclick = () => (list.fetchComments(0))
|
|
33
|
+
errEl.appendChild($retryBtn)
|
|
34
|
+
|
|
35
|
+
const adminBtn = Utils.createElement('<span atk-only-admin-show> | <span style="cursor:pointer;">打开控制台</span></span>')
|
|
36
|
+
errEl.appendChild(adminBtn)
|
|
37
|
+
if (!User.data.isAdmin) adminBtn.classList.add('atk-hide')
|
|
38
|
+
|
|
39
|
+
let sidebarView = ''
|
|
40
|
+
|
|
41
|
+
// 找不到站点错误,打开侧边栏并填入创建站点表单
|
|
42
|
+
if (errData?.err_no_site) {
|
|
43
|
+
const viewLoadParam = {
|
|
44
|
+
create_name: list.ctx.conf.site,
|
|
45
|
+
create_urls: `${window.location.protocol}//${window.location.host}`
|
|
46
|
+
}
|
|
47
|
+
sidebarView = `sites|${JSON.stringify(viewLoadParam)}`
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
adminBtn.onclick = () => list.ctx.showSidebar({
|
|
51
|
+
view: sidebarView as any
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
return errEl
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** 版本检测弹窗 */
|
|
58
|
+
export function versionCheckDialog(list: ListLite, feVer: string, beVer: string): boolean {
|
|
59
|
+
const comp = Utils.versionCompare(feVer, beVer)
|
|
60
|
+
const notSameVer = (comp !== 0)
|
|
61
|
+
if (notSameVer) {
|
|
62
|
+
const errEl = Utils.createElement(
|
|
63
|
+
`<div>请更新 Artalk ${comp < 0 ? $t('frontend') : $t('backend')}以获得完整体验 ` +
|
|
64
|
+
`(<a href="https://artalk.js.org/" target="_blank">帮助文档</a>)` +
|
|
65
|
+
`<br/><br/><span style="color: var(--at-color-meta);">` +
|
|
66
|
+
`当前版本:${$t('frontend')} ${feVer} / ${$t('backend')} ${beVer}` +
|
|
67
|
+
`</span><br/><br/></div>`)
|
|
68
|
+
const ignoreBtn = Utils.createElement('<span style="cursor:pointer">忽略</span>')
|
|
69
|
+
ignoreBtn.onclick = () => {
|
|
70
|
+
Ui.setError(list.$el.parentElement!, null)
|
|
71
|
+
list.ctx.conf.versionCheck = false
|
|
72
|
+
list.fetchComments(0)
|
|
73
|
+
}
|
|
74
|
+
errEl.append(ignoreBtn)
|
|
75
|
+
Ui.setError(list.$el.parentElement!, errEl, '<span class="atk-warn-title">Artalk Warn</span>')
|
|
76
|
+
return true
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return false
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** 评论排序方式选择下拉菜单 */
|
|
83
|
+
export function renderDropdown(conf: {
|
|
84
|
+
$dropdownWrap: HTMLElement,
|
|
85
|
+
dropdownList: [string, () => void][]
|
|
86
|
+
}) {
|
|
87
|
+
const { $dropdownWrap, dropdownList } = conf
|
|
88
|
+
if ($dropdownWrap.querySelector('.atk-dropdown')) return
|
|
89
|
+
|
|
90
|
+
// 修改 class
|
|
91
|
+
$dropdownWrap.classList.add('atk-dropdown-wrap')
|
|
92
|
+
|
|
93
|
+
// 插入图标
|
|
94
|
+
$dropdownWrap.append(Utils.createElement(`<span class="atk-arrow-down-icon"></span>`))
|
|
95
|
+
|
|
96
|
+
// 列表项点击事件
|
|
97
|
+
let curtActive = 0 // 当前选中
|
|
98
|
+
const onItemClick = (i: number, $item: HTMLElement, name: string, action: Function) => {
|
|
99
|
+
action()
|
|
100
|
+
|
|
101
|
+
// set active
|
|
102
|
+
curtActive = i
|
|
103
|
+
$dropdown.querySelectorAll('.active').forEach((e) => { e.classList.remove('active') })
|
|
104
|
+
$item.classList.add('active')
|
|
105
|
+
|
|
106
|
+
// 关闭层 (临时消失,取消 :hover)
|
|
107
|
+
$dropdown.style.display = 'none'
|
|
108
|
+
setTimeout(() => { $dropdown.style.display = '' }, 80)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 生成列表元素
|
|
112
|
+
const $dropdown = Utils.createElement(`<ul class="atk-dropdown atk-fade-in"></ul>`)
|
|
113
|
+
dropdownList.forEach((item, i) => {
|
|
114
|
+
const name = item[0] as string
|
|
115
|
+
const action = item[1] as Function
|
|
116
|
+
|
|
117
|
+
const $item = Utils.createElement(`<li class="atk-dropdown-item"><span></span></li>`)
|
|
118
|
+
const $link = $item.querySelector<HTMLElement>('span')!
|
|
119
|
+
$link.innerText = name
|
|
120
|
+
$link.onclick = () => { onItemClick(i, $item, name, action) }
|
|
121
|
+
$dropdown.append($item)
|
|
122
|
+
|
|
123
|
+
if (i === curtActive) $item.classList.add('active') // 默认选中项
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
$dropdownWrap.append($dropdown)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** 删除评论排序方式选择下拉菜单 */
|
|
130
|
+
export function removeDropdown(conf: {
|
|
131
|
+
$dropdownWrap: HTMLElement
|
|
132
|
+
}) {
|
|
133
|
+
const { $dropdownWrap } = conf
|
|
134
|
+
$dropdownWrap.classList.remove('atk-dropdown-wrap')
|
|
135
|
+
$dropdownWrap.querySelector('.atk-arrow-down-icon')?.remove()
|
|
136
|
+
$dropdownWrap.querySelector('.atk-dropdown')?.remove()
|
|
137
|
+
}
|
package/src/list/list.ts
CHANGED
|
@@ -3,8 +3,10 @@ import Context from '~/types/context'
|
|
|
3
3
|
import { version as ARTALK_VERSION } from '../../package.json'
|
|
4
4
|
import * as Utils from '../lib/utils'
|
|
5
5
|
import * as Ui from '../lib/ui'
|
|
6
|
+
import User from '../lib/user'
|
|
6
7
|
import ListHTML from './list.html?raw'
|
|
7
8
|
import ListLite from './list-lite'
|
|
9
|
+
import * as ListUi from './list-ui'
|
|
8
10
|
|
|
9
11
|
export default class List extends ListLite {
|
|
10
12
|
private $closeCommentBtn!: HTMLElement
|
|
@@ -29,16 +31,26 @@ export default class List extends ListLite {
|
|
|
29
31
|
// 操作按钮
|
|
30
32
|
this.initListActionBtn()
|
|
31
33
|
|
|
34
|
+
const countNumHTML = '<span class="atk-comment-count-num">0</span>'
|
|
32
35
|
this.$commentCount = this.$el.querySelector('.atk-comment-count')!
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
|
|
37
|
+
const refreshCountNumEl = () => {
|
|
38
|
+
this.$commentCount.innerHTML = this.$t('counter', {
|
|
39
|
+
count: countNumHTML,
|
|
40
|
+
})
|
|
41
|
+
this.$commentCountNum = this.$commentCount.querySelector('.atk-comment-count-num')!
|
|
42
|
+
}
|
|
43
|
+
refreshCountNumEl()
|
|
37
44
|
|
|
38
45
|
// copyright
|
|
39
46
|
this.$el.querySelector<HTMLElement>('.atk-copyright')!.innerHTML = `Powered By <a href="https://artalk.js.org" target="_blank" title="Artalk v${ARTALK_VERSION}">Artalk</a>`
|
|
40
47
|
|
|
41
48
|
// event listen
|
|
49
|
+
this.ctx.on('conf-loaded', () => {
|
|
50
|
+
// i18n support when locale changed
|
|
51
|
+
refreshCountNumEl()
|
|
52
|
+
this.refreshUI()
|
|
53
|
+
})
|
|
42
54
|
}
|
|
43
55
|
|
|
44
56
|
private initListActionBtn() {
|
|
@@ -67,16 +79,14 @@ export default class List extends ListLite {
|
|
|
67
79
|
this.$commentCountNum.innerText = String(Number(this.data?.total) || 0)
|
|
68
80
|
|
|
69
81
|
// 已输入个人信息
|
|
70
|
-
if (!!
|
|
82
|
+
if (!!User.data.nick && !!User.data.email) {
|
|
71
83
|
this.$openSidebarBtn.classList.remove('atk-hide')
|
|
72
84
|
} else {
|
|
73
85
|
this.$openSidebarBtn.classList.add('atk-hide')
|
|
74
86
|
}
|
|
75
87
|
|
|
76
|
-
// 仅管理员显示控制
|
|
77
|
-
this.ctx.checkAdminShowEl()
|
|
78
88
|
this.$openSidebarBtn.querySelector<HTMLElement>('.atk-text')!
|
|
79
|
-
.innerText = (!
|
|
89
|
+
.innerText = (!User.data.isAdmin) ? this.$t('msgCenter') : this.$t('ctrlCenter')
|
|
80
90
|
|
|
81
91
|
// 关闭评论
|
|
82
92
|
if (!!this.data && !!this.data.page && this.data.page.admin_only === true) {
|
|
@@ -90,6 +100,10 @@ export default class List extends ListLite {
|
|
|
90
100
|
// 评论列表排序 Dropdown 下拉选择层
|
|
91
101
|
if (this.ctx.conf.listSort) {
|
|
92
102
|
this.initDropdown()
|
|
103
|
+
} else {
|
|
104
|
+
ListUi.removeDropdown({
|
|
105
|
+
$dropdownWrap: this.$commentCount
|
|
106
|
+
})
|
|
93
107
|
}
|
|
94
108
|
}
|
|
95
109
|
|
|
@@ -121,8 +135,7 @@ export default class List extends ListLite {
|
|
|
121
135
|
const comment = this.ctx.findComment(commentId)
|
|
122
136
|
if (!comment) { // 若找不到评论
|
|
123
137
|
// 自动翻页
|
|
124
|
-
|
|
125
|
-
else if (this.pagination) this.pagination.next()
|
|
138
|
+
this.pgHolder?.next()
|
|
126
139
|
return
|
|
127
140
|
}
|
|
128
141
|
|
|
@@ -154,6 +167,7 @@ export default class List extends ListLite {
|
|
|
154
167
|
else window.setTimeout(() => goTo(), 350)
|
|
155
168
|
|
|
156
169
|
this.goToCommentFounded = true
|
|
170
|
+
this.goToCommentDelay = true // reset
|
|
157
171
|
}
|
|
158
172
|
|
|
159
173
|
/** 管理员设置页面信息 */
|
|
@@ -184,63 +198,22 @@ export default class List extends ListLite {
|
|
|
184
198
|
}
|
|
185
199
|
}
|
|
186
200
|
|
|
187
|
-
private dropdownLoaded = false
|
|
188
|
-
|
|
189
201
|
/** 初始化选择下拉层 */
|
|
190
202
|
protected initDropdown() {
|
|
191
|
-
if (this.dropdownLoaded) return
|
|
192
|
-
this.dropdownLoaded = true
|
|
193
|
-
|
|
194
|
-
this.$dropdownWrap = this.$commentCount
|
|
195
|
-
this.$commentCount.classList.add('atk-dropdown-wrap')
|
|
196
|
-
|
|
197
|
-
// 插入图标
|
|
198
|
-
this.$dropdownWrap.append(Utils.createElement(`<span class="atk-arrow-down-icon"></span>`))
|
|
199
|
-
|
|
200
203
|
const reloadUseParamsEditor = (func: (p: any) => void) => {
|
|
201
|
-
this.paramsEditor =
|
|
204
|
+
this.paramsEditor = func
|
|
202
205
|
this.fetchComments(0)
|
|
203
206
|
}
|
|
204
207
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
[
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
// 列表项点击事件
|
|
214
|
-
let curtActive = 0 // 当前选中
|
|
215
|
-
const onItemClick = (i: number, $item: HTMLElement, name: string, action: Function) => {
|
|
216
|
-
action()
|
|
217
|
-
|
|
218
|
-
// set active
|
|
219
|
-
curtActive = i
|
|
220
|
-
$dropdown.querySelectorAll('.active').forEach((e) => { e.classList.remove('active') })
|
|
221
|
-
$item.classList.add('active')
|
|
222
|
-
|
|
223
|
-
// 关闭层 (临时消失,取消 :hover)
|
|
224
|
-
$dropdown.style.display = 'none'
|
|
225
|
-
setTimeout(() => { $dropdown.style.display = '' }, 80)
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// 生成列表元素
|
|
229
|
-
const $dropdown = Utils.createElement(`<ul class="atk-dropdown atk-fade-in"></ul>`)
|
|
230
|
-
dropdownList.forEach((item, i) => {
|
|
231
|
-
const name = item[0] as string
|
|
232
|
-
const action = item[1] as Function
|
|
233
|
-
|
|
234
|
-
const $item = Utils.createElement(`<li class="atk-dropdown-item"><span></span></li>`)
|
|
235
|
-
const $link = $item.querySelector<HTMLElement>('span')!
|
|
236
|
-
$link.innerText = name
|
|
237
|
-
$link.onclick = () => { onItemClick(i, $item, name, action) }
|
|
238
|
-
$dropdown.append($item)
|
|
239
|
-
|
|
240
|
-
if (i === curtActive) $item.classList.add('active') // 默认选中项
|
|
208
|
+
ListUi.renderDropdown({
|
|
209
|
+
$dropdownWrap: this.$commentCount,
|
|
210
|
+
dropdownList: [
|
|
211
|
+
[this.$t('sortLatest'), () => { reloadUseParamsEditor(p => { p.sort_by = 'date_desc' }) }],
|
|
212
|
+
[this.$t('sortBest'), () => { reloadUseParamsEditor(p => { p.sort_by = 'vote' }) }],
|
|
213
|
+
[this.$t('sortOldest'), () => { reloadUseParamsEditor(p => { p.sort_by = 'date_asc' }) }],
|
|
214
|
+
[this.$t('sortAuthor'), () => { reloadUseParamsEditor(p => { p.view_only_admin = true }) }],
|
|
215
|
+
]
|
|
241
216
|
})
|
|
242
|
-
|
|
243
|
-
this.$dropdownWrap.append($dropdown)
|
|
244
217
|
}
|
|
245
218
|
|
|
246
219
|
public updateUnread(notifies: NotifyData[]): void {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { IPgHolderConf, TPgMode } from '../index'
|
|
2
|
+
import PaginationAdaptor from './pagination'
|
|
3
|
+
import ReadMoreAdaptor from './read-more'
|
|
4
|
+
|
|
5
|
+
// 分页方式适配器
|
|
6
|
+
const Adaptors: Record<TPgMode, IPgAdaptor<any>> = {
|
|
7
|
+
'pagination': PaginationAdaptor,
|
|
8
|
+
'read-more': ReadMoreAdaptor
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default Adaptors
|
|
12
|
+
|
|
13
|
+
export interface IPgAdaptor<T> {
|
|
14
|
+
instance: T
|
|
15
|
+
el: HTMLElement
|
|
16
|
+
createInstance(conf: IPgHolderConf): [T, HTMLElement]
|
|
17
|
+
setLoading(val: boolean): void
|
|
18
|
+
update(offset: number, total: number): void
|
|
19
|
+
next(): void
|
|
20
|
+
showErr?(msg: string): void
|
|
21
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import Pagination from '@/components/pagination'
|
|
2
|
+
import * as Utils from '@/lib/utils'
|
|
3
|
+
import { IPgAdaptor } from '.'
|
|
4
|
+
|
|
5
|
+
interface IPaginationAdaptor extends IPgAdaptor<Pagination> {}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 翻页形式的分页
|
|
9
|
+
*/
|
|
10
|
+
export default <IPaginationAdaptor>{
|
|
11
|
+
createInstance(conf) {
|
|
12
|
+
const instance = new Pagination(conf.total, {
|
|
13
|
+
pageSize: conf.pageSize,
|
|
14
|
+
onChange: async (o) => {
|
|
15
|
+
if (conf.list.conf.editorTravel === true)
|
|
16
|
+
conf.list.ctx.editorTravelBack() // 防止评论框被吞
|
|
17
|
+
|
|
18
|
+
await conf.list.fetchComments(o)
|
|
19
|
+
|
|
20
|
+
// 滚动到第一个评论的位置
|
|
21
|
+
if (conf.list.repositionAt) {
|
|
22
|
+
const at = conf.list.scrollListenerAt || window
|
|
23
|
+
at.scroll({
|
|
24
|
+
top: conf.list.repositionAt ? Utils.getOffset(conf.list.repositionAt).top : 0,
|
|
25
|
+
left: 0,
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
return [instance, instance.$el]
|
|
32
|
+
},
|
|
33
|
+
setLoading(val) {
|
|
34
|
+
this.instance.setLoading(val)
|
|
35
|
+
},
|
|
36
|
+
update(offset, total) {
|
|
37
|
+
this.instance.update(offset, total)
|
|
38
|
+
},
|
|
39
|
+
next() {
|
|
40
|
+
this.instance.next()
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import ReadMoreBtn from '@/components/read-more-btn'
|
|
2
|
+
import $t from '@/i18n'
|
|
3
|
+
import * as Ui from '@/lib/ui'
|
|
4
|
+
import { IPgAdaptor } from '.'
|
|
5
|
+
|
|
6
|
+
interface IReadMoreAdaptor extends IPgAdaptor<ReadMoreBtn> {
|
|
7
|
+
autoLoadScrollEvent?: () => void
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 阅读更多形式的分页
|
|
12
|
+
*/
|
|
13
|
+
export default <IReadMoreAdaptor>{
|
|
14
|
+
createInstance(conf) {
|
|
15
|
+
const readMoreBtn = new ReadMoreBtn({
|
|
16
|
+
pageSize: conf.pageSize,
|
|
17
|
+
onClick: async (o) => {
|
|
18
|
+
await conf.list.fetchComments(o)
|
|
19
|
+
},
|
|
20
|
+
text: $t('loadMore'),
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
// 滚动到底部自动加载
|
|
24
|
+
if (conf.list.conf.pagination.autoLoad) {
|
|
25
|
+
// 添加滚动事件监听
|
|
26
|
+
const at = conf.list.scrollListenerAt || document
|
|
27
|
+
if (this.autoLoadScrollEvent) at.removeEventListener('scroll', this.autoLoadScrollEvent) // 解除原有
|
|
28
|
+
this.autoLoadScrollEvent = () => {
|
|
29
|
+
if (conf.mode !== 'read-more'
|
|
30
|
+
|| !readMoreBtn?.hasMore
|
|
31
|
+
|| conf.list.getLoading()
|
|
32
|
+
) return
|
|
33
|
+
|
|
34
|
+
const $target = conf.list.$el.querySelector<HTMLElement>('.atk-list-comments-wrap > .atk-comment-wrap:nth-last-child(3)') // 获取倒数第3个评论元素
|
|
35
|
+
if ($target && Ui.isVisible($target, conf.list.scrollListenerAt)) {
|
|
36
|
+
readMoreBtn.click() // 自动点击加载更多按钮
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
at.addEventListener('scroll', this.autoLoadScrollEvent)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return [readMoreBtn, readMoreBtn.$el]
|
|
43
|
+
},
|
|
44
|
+
setLoading(val) {
|
|
45
|
+
this.instance.setLoading(val)
|
|
46
|
+
},
|
|
47
|
+
update(offset, total) {
|
|
48
|
+
this.instance.update(offset, total)
|
|
49
|
+
},
|
|
50
|
+
showErr(msg) {
|
|
51
|
+
this.instance.showErr(msg)
|
|
52
|
+
},
|
|
53
|
+
next() {
|
|
54
|
+
this.instance.click()
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { IPgHolderConf, TPgMode } from '.'
|
|
2
|
+
import Adaptors from './adaptors'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 分页方式持有者(调度器)
|
|
6
|
+
*/
|
|
7
|
+
export default class PgHolder {
|
|
8
|
+
private conf: IPgHolderConf
|
|
9
|
+
|
|
10
|
+
constructor(conf: IPgHolderConf) {
|
|
11
|
+
this.conf = conf
|
|
12
|
+
this.init()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public getAdaptor() {
|
|
16
|
+
return Adaptors[this.conf.mode]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public init() {
|
|
20
|
+
const adaptor = this.getAdaptor()
|
|
21
|
+
const [instance, el] = adaptor.createInstance(this.conf)
|
|
22
|
+
adaptor.instance = instance
|
|
23
|
+
adaptor.el = el
|
|
24
|
+
this.conf.list.$el.append(adaptor.el)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public setLoading(val: boolean) {
|
|
28
|
+
this.getAdaptor().setLoading(val)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public update(offset: number, total: number) {
|
|
32
|
+
this.getAdaptor().update(offset, total)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public getEl() {
|
|
36
|
+
return this.getAdaptor().el
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public showErr(msg: string) {
|
|
40
|
+
const that = this.getAdaptor()
|
|
41
|
+
const func = that.showErr
|
|
42
|
+
if (func) func.bind(that)(msg)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public setMode(val: TPgMode) {
|
|
46
|
+
if (val !== this.conf.mode) {
|
|
47
|
+
this.getEl().remove()
|
|
48
|
+
this.conf.mode = val
|
|
49
|
+
this.init()
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public next() {
|
|
54
|
+
this.getAdaptor().next()
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import ListLite from '../list-lite'
|
|
2
|
+
import PgHolder from './holder'
|
|
3
|
+
|
|
4
|
+
export type TPgMode = 'pagination'|'read-more'
|
|
5
|
+
|
|
6
|
+
export interface IPgHolderConf {
|
|
7
|
+
list: ListLite
|
|
8
|
+
mode: TPgMode
|
|
9
|
+
total: number
|
|
10
|
+
pageSize: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default PgHolder
|
package/src/service.ts
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import Context from '~/types/context'
|
|
2
|
+
import CheckerLauncher from './lib/checker'
|
|
3
|
+
import Api from './api'
|
|
4
|
+
import Editor from './editor'
|
|
5
|
+
import List from './list'
|
|
6
|
+
import Layer from './layer'
|
|
7
|
+
import SidebarLayer from './layer/sidebar-layer'
|
|
8
|
+
import { initMarked } from './lib/marked'
|
|
9
|
+
import User from './lib/user'
|
|
10
|
+
import ListLite from './list/list-lite'
|
|
11
|
+
import * as DarkMode from './lib/dark-mode'
|
|
12
|
+
import * as I18n from './i18n'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Services
|
|
16
|
+
*
|
|
17
|
+
* @description
|
|
18
|
+
* 当函数有返回值时,该值将自动添加到 Context 类作为一个 private 成员的值,
|
|
19
|
+
* 这个成员的名称与函数名对应。可在 Context 类中访问该对象(同事类)。
|
|
20
|
+
*/
|
|
21
|
+
const services = {
|
|
22
|
+
// I18n
|
|
23
|
+
i18n(ctx: Context) {
|
|
24
|
+
I18n.setLocale(ctx.conf.locale)
|
|
25
|
+
|
|
26
|
+
ctx.on('conf-loaded', () => {
|
|
27
|
+
I18n.setLocale(ctx.conf.locale)
|
|
28
|
+
})
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
// Markdown 组件
|
|
32
|
+
markdown() {
|
|
33
|
+
initMarked()
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
// User Store
|
|
37
|
+
user(ctx: Context) {
|
|
38
|
+
User.setContext(ctx)
|
|
39
|
+
return User
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
// HTTP API client
|
|
43
|
+
api(ctx: Context) {
|
|
44
|
+
const api = new Api(ctx)
|
|
45
|
+
return api
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
// CheckerLauncher
|
|
49
|
+
checkerLauncher(ctx: Context) {
|
|
50
|
+
const checkerLauncher = new CheckerLauncher(ctx)
|
|
51
|
+
return checkerLauncher
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
// 编辑器
|
|
55
|
+
editor(ctx: Context) {
|
|
56
|
+
const editor = new Editor(ctx)
|
|
57
|
+
ctx.$root.appendChild(editor.$el)
|
|
58
|
+
return editor
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
// 评论列表
|
|
62
|
+
list(ctx: Context): ListLite|undefined {
|
|
63
|
+
// 评论列表
|
|
64
|
+
const list = new List(ctx)
|
|
65
|
+
ctx.$root.appendChild(list.$el)
|
|
66
|
+
|
|
67
|
+
// 评论获取
|
|
68
|
+
list.fetchComments(0)
|
|
69
|
+
|
|
70
|
+
return list
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
// 弹出层
|
|
74
|
+
layer(ctx: Context) {
|
|
75
|
+
// 记录页面原始 CSS 属性
|
|
76
|
+
Layer.BodyOrgOverflow = document.body.style.overflow
|
|
77
|
+
Layer.BodyOrgPaddingRight = document.body.style.paddingRight
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
// 侧边栏 Layer
|
|
81
|
+
sidebarLayer(ctx: Context) {
|
|
82
|
+
const sidebarLayer = new SidebarLayer(ctx)
|
|
83
|
+
return sidebarLayer
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
// 默认事件绑定
|
|
87
|
+
eventsDefault(ctx: Context) {
|
|
88
|
+
// 锚点快速跳转评论
|
|
89
|
+
window.addEventListener('hashchange', () => {
|
|
90
|
+
ctx.listHashGotoCheck()
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
// 本地用户数据变更
|
|
94
|
+
ctx.on('user-changed', () => {
|
|
95
|
+
ctx.checkAdminShowEl()
|
|
96
|
+
ctx.listRefreshUI()
|
|
97
|
+
})
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
// 夜间模式
|
|
101
|
+
darkMode(ctx: Context) {
|
|
102
|
+
DarkMode.syncDarkModeConf(ctx)
|
|
103
|
+
|
|
104
|
+
ctx.on('conf-loaded', () => {
|
|
105
|
+
DarkMode.syncDarkModeConf(ctx)
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export default services
|
|
111
|
+
|
|
112
|
+
// type tricks for dependency injection
|
|
113
|
+
type TServiceImps = typeof services
|
|
114
|
+
type TObjectWithFuncs = {[k: string]: (...args: any) => any}
|
|
115
|
+
type TKeysOnlyReturn<T extends TObjectWithFuncs, V> = {[K in keyof T]: ReturnType<T[K]> extends V ? K : never}[keyof T]
|
|
116
|
+
type TOmitConditions = TKeysOnlyReturn<TServiceImps, void>
|
|
117
|
+
type TServiceInjectors = Omit<TServiceImps, TOmitConditions>
|
|
118
|
+
export type TInjectedServices = {[K in keyof TServiceInjectors]: ReturnType<TServiceInjectors[K]>}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
--at-color-light: #4285f4;
|
|
9
9
|
--at-color-bg: #FFF;
|
|
10
10
|
--at-color-bg-transl: rgba(255, 255, 255, 0.94);
|
|
11
|
-
--at-color-bg-grey: #
|
|
11
|
+
--at-color-bg-grey: #f3f4f5;
|
|
12
12
|
--at-color-bg-grey-transl: rgba(244, 244, 244, 0.75);
|
|
13
13
|
--at-color-bg-light: rgba(29, 161, 242, 0.1);
|
|
14
14
|
--at-color-main: #0083ff;
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
--at-color-pink: #fa5a57;
|
|
17
17
|
--at-color-yellow: #ff7c37;
|
|
18
18
|
--at-color-green: #4caf50;
|
|
19
|
-
--at-color-gradient: linear-gradient(180deg,
|
|
19
|
+
--at-color-gradient: linear-gradient(180deg, transparent, rgba(255, 255, 255));
|
|
20
20
|
|
|
21
21
|
&.atk-dark-mode {
|
|
22
22
|
--at-color-font: #fff;
|
|
@@ -36,6 +36,6 @@
|
|
|
36
36
|
--at-color-pink: #fa5a57;
|
|
37
37
|
--at-color-yellow: #ff7c37;
|
|
38
38
|
--at-color-green: #4caf50;
|
|
39
|
-
--at-color-gradient: linear-gradient(180deg,
|
|
39
|
+
--at-color-gradient: linear-gradient(180deg, transparent, rgba(30, 34, 36, 1));
|
|
40
40
|
}
|
|
41
41
|
}
|