artalk 2.1.0 → 2.1.4
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/dist/Artalk.css +1 -1
- package/dist/Artalk.es.js +3895 -3106
- package/dist/Artalk.iife.js +1 -266
- package/dist/Artalk.js +1 -266
- package/package.json +19 -21
- package/src/api/index.ts +6 -7
- package/src/artalk.ts +11 -4
- package/src/components/action-btn.ts +1 -1
- package/src/components/comment.ts +102 -27
- package/src/components/editor-plugs/_example-plug.ts.txt +2 -7
- package/src/components/editor-plugs/editor-plug.ts +3 -3
- package/src/components/editor-plugs/emoticons-plug.less +21 -11
- package/src/components/editor-plugs/emoticons-plug.ts +199 -79
- package/src/components/editor-plugs/preview-plug.less +1 -1
- package/src/components/editor-plugs/preview-plug.ts +2 -7
- package/src/components/editor.ts +75 -34
- package/src/components/html/comment.html +6 -13
- package/src/components/html/editor.html +9 -10
- package/src/components/html/list.html +1 -1
- package/src/components/layer.ts +33 -14
- package/src/components/list-lite.ts +218 -235
- package/src/components/list.ts +11 -9
- package/src/components/pagination.ts +2 -2
- package/src/components/read-more-btn.ts +27 -10
- package/src/components/sidebar-views/comments-view.ts +12 -5
- package/src/components/sidebar-views/sites-view.ts +0 -6
- package/src/components/sidebar.ts +7 -2
- package/src/{default-conf.ts → defaults.ts} +7 -7
- package/src/lib/utils.ts +41 -12
- package/src/style/_color.less +19 -23
- package/src/style/_extend.less +3 -3
- package/src/style/_variables.less +0 -24
- package/src/style/comment.less +191 -197
- package/src/style/editor.less +78 -103
- package/src/style/item-text-editor.less +2 -2
- package/src/style/list.less +14 -13
- package/src/style/main.less +35 -31
- package/src/style/page-list.less +4 -4
- package/src/style/pagination.less +6 -6
- package/src/style/sidebar.less +35 -35
- package/src/style/site-list.less +13 -13
- package/types/artalk-config.d.ts +13 -10
- package/types/artalk-data.d.ts +10 -5
- package/types/event.d.ts +10 -1
- package/src/assets/emoticons.json +0 -100
|
@@ -3,126 +3,242 @@ import './emoticons-plug.less'
|
|
|
3
3
|
import Editor from '../editor'
|
|
4
4
|
import EditorPlug from './editor-plug'
|
|
5
5
|
import * as Utils from '~/src/lib/utils'
|
|
6
|
+
import * as Ui from '~/src/lib/ui'
|
|
7
|
+
import { EmoticonListData, EmoticonGrpData } from '~/types/artalk-data'
|
|
6
8
|
|
|
7
|
-
type
|
|
8
|
-
[
|
|
9
|
-
|
|
10
|
-
container: {
|
|
9
|
+
type OwOFormatType = {
|
|
10
|
+
[key: string] : {
|
|
11
|
+
type: 'emoticon'|'emoji'|'image',
|
|
12
|
+
container: {icon: string, text: string}[]
|
|
11
13
|
}
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
export default class EmoticonsPlug extends EditorPlug {
|
|
15
17
|
public $el!: HTMLElement
|
|
16
|
-
public emoticons:
|
|
18
|
+
public emoticons: EmoticonListData = []
|
|
17
19
|
|
|
18
|
-
public
|
|
19
|
-
public
|
|
20
|
+
public $grpWrap!: HTMLElement
|
|
21
|
+
public $grpSwitcher!: HTMLElement
|
|
20
22
|
|
|
21
23
|
constructor (public editor: Editor) {
|
|
22
24
|
super(editor)
|
|
23
25
|
|
|
24
|
-
this
|
|
25
|
-
|
|
26
|
+
this.$el = Utils.createElement(`<div class="atk-editor-plug-emoticons"></div>`)
|
|
27
|
+
|
|
28
|
+
this.init()
|
|
26
29
|
}
|
|
27
30
|
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
async init() {
|
|
32
|
+
// 数据处理
|
|
33
|
+
Ui.showLoading(this.$el)
|
|
34
|
+
this.emoticons = await this.handleData(this.ctx.conf.emoticons)
|
|
35
|
+
Ui.hideLoading(this.$el)
|
|
30
36
|
|
|
31
|
-
//
|
|
32
|
-
this.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
37
|
+
// 初始化元素
|
|
38
|
+
this.initEmoticonsList()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async handleData(data: any): Promise<any[]> {
|
|
42
|
+
if (!Array.isArray(data) && ['object', 'string'].includes(typeof data)) {
|
|
43
|
+
data = [data]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!Array.isArray(data)) {
|
|
47
|
+
Ui.setError(this.$el, "表情包数据必须为 Array/Object/String 类型")
|
|
48
|
+
Ui.hideLoading(this.$el)
|
|
49
|
+
return []
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const pushGrp = (grp: EmoticonGrpData) => {
|
|
53
|
+
if (typeof grp !== 'object') return
|
|
54
|
+
if (grp.name && data.find(o => o.name === grp.name)) return
|
|
55
|
+
data.push(grp)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 加载子内容
|
|
59
|
+
const remoteLoad = async (d: any[]) => {
|
|
60
|
+
await Promise.all(d.map(async (grp, index) => {
|
|
61
|
+
if (typeof grp === 'object' && !Array.isArray(grp)) {
|
|
62
|
+
pushGrp(grp)
|
|
63
|
+
} else if (Array.isArray(grp)) {
|
|
64
|
+
await remoteLoad(grp)
|
|
65
|
+
} else if (typeof grp === "string") {
|
|
66
|
+
const grpData = await this.remoteLoad(grp)
|
|
67
|
+
|
|
68
|
+
if (Array.isArray(grpData)) await remoteLoad(grpData)
|
|
69
|
+
else if (typeof grpData === 'object') pushGrp(grpData)
|
|
52
70
|
}
|
|
71
|
+
}))
|
|
72
|
+
}
|
|
73
|
+
await remoteLoad(data)
|
|
74
|
+
|
|
75
|
+
// 处理数组数据
|
|
76
|
+
data.forEach((item: any) => {
|
|
77
|
+
if (this.isOwOFormat(item)) {
|
|
78
|
+
const c = this.convertOwO(item)
|
|
79
|
+
c.forEach((grp) => { pushGrp(grp) })
|
|
80
|
+
} else if (Array.isArray(item)) {
|
|
81
|
+
item.forEach((grp) => { pushGrp(grp) })
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
// 剔除非法数据
|
|
86
|
+
data = data.filter((item: any) => (typeof item === 'object' && !Array.isArray(item) && !!item && !!item.name))
|
|
87
|
+
|
|
88
|
+
console.log(data)
|
|
89
|
+
|
|
90
|
+
this.solveNullKey(data)
|
|
91
|
+
this.solveSameKey(data)
|
|
92
|
+
|
|
93
|
+
return data
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** 远程加载 */
|
|
97
|
+
async remoteLoad(url: string): Promise<any> {
|
|
98
|
+
if (!url) return []
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const resp = await fetch(url)
|
|
102
|
+
const json = await resp.json()
|
|
103
|
+
return json
|
|
104
|
+
} catch (err) {
|
|
105
|
+
Ui.hideLoading(this.$el)
|
|
106
|
+
Ui.setError(this.$el, `表情加载失败 ${String(err)}`)
|
|
107
|
+
return []
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** 避免表情 item.key 为 null 的情况 */
|
|
112
|
+
solveNullKey(data: EmoticonGrpData[]) {
|
|
113
|
+
data.forEach((grp) => {
|
|
114
|
+
grp.items.forEach((item, index) => {
|
|
115
|
+
if (!item.key) item.key = `${grp.name} ${index+1}`
|
|
53
116
|
})
|
|
54
117
|
})
|
|
118
|
+
}
|
|
55
119
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
120
|
+
/** 避免相同 item.key */
|
|
121
|
+
solveSameKey(data: EmoticonGrpData[]) {
|
|
122
|
+
const tmp: {[key: string]: number} = {}
|
|
123
|
+
data.forEach((grp) => {
|
|
124
|
+
grp.items.forEach(item => {
|
|
125
|
+
if (!item.key || String(item.key).trim() === "") return
|
|
126
|
+
if (!tmp[item.key]) tmp[item.key] = 1
|
|
127
|
+
else tmp[item.key]++
|
|
128
|
+
|
|
129
|
+
if (tmp[item.key] > 1) item.key = `${item.key} ${tmp[item.key]}`
|
|
130
|
+
})
|
|
64
131
|
})
|
|
132
|
+
}
|
|
65
133
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
134
|
+
/** 判断是否为 OwO 格式 */
|
|
135
|
+
isOwOFormat(data: any) {
|
|
136
|
+
try {
|
|
137
|
+
return (typeof data === 'object') && !!Object.values(data).length
|
|
138
|
+
&& Array.isArray(Object.keys(Object.values<any>(data)[0].container))
|
|
139
|
+
&& Object.keys(Object.values<any>(data)[0].container[0]).includes('icon')
|
|
140
|
+
} catch { return false }
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** 转换 OwO 格式数据 */
|
|
144
|
+
convertOwO(owoData: OwOFormatType): EmoticonListData {
|
|
145
|
+
const dest: EmoticonListData = []
|
|
146
|
+
Object.entries(owoData).forEach(([grpName, grp]) => {
|
|
147
|
+
const nGrp: EmoticonGrpData = { name: grpName, type: grp.type, items: [] }
|
|
148
|
+
grp.container.forEach((item, index) => {
|
|
149
|
+
// 图片标签提取 src 属性值
|
|
150
|
+
const iconStr = item.icon
|
|
151
|
+
if (/<(img|IMG)/.test(iconStr)) {
|
|
152
|
+
const find = /src=["'](.*?)["']/.exec(iconStr)
|
|
153
|
+
if (find && find.length > 1) item.icon = find[1]
|
|
154
|
+
}
|
|
155
|
+
nGrp.items.push({ key: item.text || `${grpName} ${index+1}`, val: item.icon })
|
|
72
156
|
})
|
|
157
|
+
dest.push(nGrp)
|
|
73
158
|
})
|
|
159
|
+
return dest
|
|
160
|
+
}
|
|
74
161
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
162
|
+
/** 初始化表情列表界面 */
|
|
163
|
+
initEmoticonsList () {
|
|
164
|
+
// 表情列表
|
|
165
|
+
this.$grpWrap = Utils.createElement(`<div class="atk-grp-wrap"></div>`)
|
|
166
|
+
this.$el.append(this.$grpWrap)
|
|
167
|
+
|
|
168
|
+
this.emoticons.forEach((grp, index) => {
|
|
169
|
+
const $grp = Utils.createElement(`<div class="atk-grp" style="display: none;"></div>`)
|
|
170
|
+
this.$grpWrap.append($grp)
|
|
171
|
+
$grp.setAttribute('data-index', String(index))
|
|
172
|
+
$grp.setAttribute('data-grp-name', grp.name)
|
|
173
|
+
$grp.setAttribute('data-type', grp.type)
|
|
174
|
+
grp.items.forEach((item) => {
|
|
175
|
+
const $item = Utils.createElement(`<span class="atk-item"></span>`)
|
|
176
|
+
$grp.append($item)
|
|
177
|
+
|
|
178
|
+
if (!!item.key && !(new RegExp(`^(${grp.name})?\\s?[0-9]+$`).test(item.key)))
|
|
179
|
+
$item.setAttribute('title', item.key)
|
|
180
|
+
|
|
181
|
+
if (grp.type === 'image') {
|
|
182
|
+
const imgEl = document.createElement('img')
|
|
183
|
+
imgEl.src = item.val
|
|
184
|
+
imgEl.alt = item.key
|
|
185
|
+
$item.append(imgEl)
|
|
89
186
|
} else {
|
|
90
|
-
|
|
187
|
+
$item.innerText = item.val
|
|
91
188
|
}
|
|
92
|
-
|
|
189
|
+
|
|
190
|
+
$item.onclick = () => {
|
|
191
|
+
if (grp.type === 'image') {
|
|
192
|
+
this.editor.insertContent(`:[${item.key}]`)
|
|
193
|
+
} else {
|
|
194
|
+
this.editor.insertContent(item.val || '')
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
})
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
// 表情分类
|
|
201
|
+
this.$grpSwitcher = Utils.createElement(`<div class="atk-grp-switcher"></div>`)
|
|
202
|
+
this.$el.append(this.$grpSwitcher)
|
|
203
|
+
this.emoticons.forEach((grp, index) => {
|
|
204
|
+
const $item = Utils.createElement('<span />')
|
|
205
|
+
$item.innerText = grp.name
|
|
206
|
+
$item.setAttribute('data-index', String(index))
|
|
207
|
+
$item.onclick = () => (this.openGrp(index))
|
|
208
|
+
this.$grpSwitcher.append($item)
|
|
93
209
|
})
|
|
210
|
+
|
|
211
|
+
// 默认打开第一个分类
|
|
212
|
+
if (this.emoticons.length > 0)
|
|
213
|
+
this.openGrp(0)
|
|
94
214
|
}
|
|
95
215
|
|
|
96
|
-
|
|
97
|
-
|
|
216
|
+
/** 打开一个表情组 */
|
|
217
|
+
openGrp (index: number) {
|
|
218
|
+
Array.from(this.$grpWrap.children).forEach((item) => {
|
|
98
219
|
const el = item as HTMLElement
|
|
99
|
-
if (el.getAttribute('data-
|
|
220
|
+
if (el.getAttribute('data-index') !== String(index)) {
|
|
100
221
|
el.style.display = 'none'
|
|
101
222
|
} else {
|
|
102
223
|
el.style.display = ''
|
|
103
224
|
}
|
|
104
225
|
})
|
|
105
226
|
|
|
106
|
-
this.
|
|
107
|
-
this.
|
|
227
|
+
this.$grpSwitcher.querySelectorAll('span.active').forEach(item => item.classList.remove('active'))
|
|
228
|
+
this.$grpSwitcher.querySelector(`span[data-index="${index}"]`)?.classList.add('active')
|
|
108
229
|
|
|
109
230
|
this.changeListHeight()
|
|
110
231
|
}
|
|
111
232
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
getBtnHtml () {
|
|
117
|
-
return '表情'
|
|
118
|
-
}
|
|
233
|
+
static Name = 'emoticons'
|
|
234
|
+
static BtnHTML = '表情'
|
|
119
235
|
|
|
120
236
|
getEl () {
|
|
121
237
|
return this.$el
|
|
122
238
|
}
|
|
123
239
|
|
|
124
240
|
changeListHeight () {
|
|
125
|
-
/* const listWrapHeight = Utils.getHeight(this
|
|
241
|
+
/* const listWrapHeight = Utils.getHeight(this.$grpWrapem)
|
|
126
242
|
this.editor.plugWrapEl.style.height = `${listWrapHeight > 150 ? listWrapHeight : 150}px` */
|
|
127
243
|
}
|
|
128
244
|
|
|
@@ -137,11 +253,15 @@ export default class EmoticonsPlug extends EditorPlug {
|
|
|
137
253
|
this.$el.parentElement!.style.height = ''
|
|
138
254
|
}
|
|
139
255
|
|
|
256
|
+
/** 处理评论 content 中的表情内容 */
|
|
140
257
|
public transEmoticonImageText (text: string) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
258
|
+
if (!this.emoticons || !Array.isArray(this.emoticons))
|
|
259
|
+
return text
|
|
260
|
+
|
|
261
|
+
this.emoticons.forEach((grp) => {
|
|
262
|
+
if (grp.type !== 'image') return
|
|
263
|
+
Object.entries(grp.items).forEach(([index, item]) => {
|
|
264
|
+
text = text.split(`:[${item.key}]`).join(`<img src="${item.val}" atk-emoticon="${item.key}">`) // replaceAll(...)
|
|
145
265
|
})
|
|
146
266
|
})
|
|
147
267
|
|
|
@@ -19,13 +19,8 @@ export default class PreviewPlug extends EditorPlug {
|
|
|
19
19
|
this.binded = false
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
getBtnHtml () {
|
|
27
|
-
return '预览 <i title="Markdown is supported"><svg class="markdown" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M14.85 3H1.15C.52 3 0 3.52 0 4.15v7.69C0 12.48.52 13 1.15 13h13.69c.64 0 1.15-.52 1.15-1.15v-7.7C16 3.52 15.48 3 14.85 3zM9 11H7V8L5.5 9.92 4 8v3H2V5h2l1.5 2L7 5h2v6zm2.99.5L9.5 8H11V5h2v3h1.5l-2.51 3.5z"></path></svg></i>'
|
|
28
|
-
}
|
|
22
|
+
static Name = 'preview'
|
|
23
|
+
static BtnHTML = '预览 <i title="Markdown is supported"><svg class="markdown" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M14.85 3H1.15C.52 3 0 3.52 0 4.15v7.69C0 12.48.52 13 1.15 13h13.69c.64 0 1.15-.52 1.15-1.15v-7.7C16 3.52 15.48 3 14.85 3zM9 11H7V8L5.5 9.92 4 8v3H2V5h2l1.5 2L7 5h2v6zm2.99.5L9.5 8H11V5h2v3h1.5l-2.51 3.5z"></path></svg></i>'
|
|
29
24
|
|
|
30
25
|
getEl () {
|
|
31
26
|
return this.$el
|
package/src/components/editor.ts
CHANGED
|
@@ -18,18 +18,17 @@ export default class Editor extends Component {
|
|
|
18
18
|
public $header: HTMLElement
|
|
19
19
|
public $textareaWrap: HTMLElement
|
|
20
20
|
public $textarea: HTMLTextAreaElement
|
|
21
|
-
public $closeComment: HTMLTextAreaElement
|
|
22
21
|
public $plugWrap: HTMLElement
|
|
23
22
|
public $bottom: HTMLElement
|
|
24
|
-
public $
|
|
25
|
-
public $plugSwitcherWrap: HTMLElement
|
|
26
|
-
public $bottomPartRight: HTMLElement
|
|
23
|
+
public $plugBtnWrap: HTMLElement
|
|
27
24
|
public $submitBtn: HTMLButtonElement
|
|
28
25
|
public $notifyWrap: HTMLElement
|
|
29
26
|
|
|
30
27
|
private replyComment: CommentData|null = null
|
|
31
28
|
private $sendReply: HTMLElement|null = null
|
|
32
29
|
|
|
30
|
+
private isTraveling = false
|
|
31
|
+
|
|
33
32
|
private get user () {
|
|
34
33
|
return this.ctx.user
|
|
35
34
|
}
|
|
@@ -39,17 +38,14 @@ export default class Editor extends Component {
|
|
|
39
38
|
|
|
40
39
|
this.$el = Utils.createElement(EditorHTML)
|
|
41
40
|
|
|
42
|
-
this.$header = this.$el.querySelector('.atk-
|
|
43
|
-
this.$textareaWrap = this.$el.querySelector('.atk-
|
|
44
|
-
this.$textarea = this.$el.querySelector('.atk-
|
|
45
|
-
this.$
|
|
46
|
-
this.$
|
|
47
|
-
this.$
|
|
48
|
-
this.$bottomPartLeft = this.$el.querySelector('.atk-editor-bottom-part.atk-left')!
|
|
49
|
-
this.$plugSwitcherWrap = this.$el.querySelector('.atk-editor-plug-switcher-wrap')!
|
|
50
|
-
this.$bottomPartRight = this.$el.querySelector('.atk-editor-bottom-part.atk-right')!
|
|
41
|
+
this.$header = this.$el.querySelector('.atk-header')!
|
|
42
|
+
this.$textareaWrap = this.$el.querySelector('.atk-textarea-wrap')!
|
|
43
|
+
this.$textarea = this.$el.querySelector('.atk-textarea')!
|
|
44
|
+
this.$plugWrap = this.$el.querySelector('.atk-plug-wrap')!
|
|
45
|
+
this.$bottom = this.$el.querySelector('.atk-bottom')!
|
|
46
|
+
this.$plugBtnWrap = this.$el.querySelector('.atk-plug-btn-wrap')!
|
|
51
47
|
this.$submitBtn = this.$el.querySelector('.atk-send-btn')!
|
|
52
|
-
this.$notifyWrap = this.$el.querySelector('.atk-
|
|
48
|
+
this.$notifyWrap = this.$el.querySelector('.atk-notify-wrap')!
|
|
53
49
|
|
|
54
50
|
this.initLocalStorage()
|
|
55
51
|
this.initHeader()
|
|
@@ -60,10 +56,13 @@ export default class Editor extends Component {
|
|
|
60
56
|
// 监听事件
|
|
61
57
|
this.ctx.on('editor-open', () => (this.open()))
|
|
62
58
|
this.ctx.on('editor-close', () => (this.close()))
|
|
63
|
-
this.ctx.on('editor-reply', (
|
|
59
|
+
this.ctx.on('editor-reply', (p) => (this.setReply(p.data, p.$el, p.scroll)))
|
|
60
|
+
this.ctx.on('editor-reply-cancel', () => (this.cancelReply()))
|
|
64
61
|
this.ctx.on('editor-show-loading', () => (Ui.showLoading(this.$el)))
|
|
65
62
|
this.ctx.on('editor-hide-loading', () => (Ui.hideLoading(this.$el)))
|
|
66
63
|
this.ctx.on('editor-notify', (f) => (this.showNotify(f.msg, f.type)))
|
|
64
|
+
this.ctx.on('editor-travel', ($el) => (this.travel($el)))
|
|
65
|
+
this.ctx.on('editor-travel-back', () => (this.travelBack()))
|
|
67
66
|
}
|
|
68
67
|
|
|
69
68
|
initLocalStorage () {
|
|
@@ -196,38 +195,41 @@ export default class Editor extends Component {
|
|
|
196
195
|
this.$plugWrap.innerHTML = ''
|
|
197
196
|
this.$plugWrap.style.display = 'none'
|
|
198
197
|
this.openedPlugName = null
|
|
199
|
-
this.$
|
|
198
|
+
this.$plugBtnWrap.innerHTML = ''
|
|
200
199
|
|
|
201
200
|
// 依次实例化 plug
|
|
202
201
|
this.LOADABLE_PLUG_LIST.forEach((PlugObj) => {
|
|
203
|
-
const plug = new PlugObj(this)
|
|
204
|
-
this.plugList[plug.getName()] = plug
|
|
205
|
-
|
|
206
202
|
// 切换按钮
|
|
207
|
-
const btnElem = Utils.createElement(`<span class="atk-
|
|
208
|
-
this.$
|
|
203
|
+
const btnElem = Utils.createElement(`<span class="atk-plug-btn">${PlugObj.BtnHTML}</span>`)
|
|
204
|
+
this.$plugBtnWrap.appendChild(btnElem)
|
|
209
205
|
btnElem.addEventListener('click', () => {
|
|
210
|
-
|
|
206
|
+
let plug = this.plugList[PlugObj.Name]
|
|
207
|
+
if (!plug) {
|
|
208
|
+
plug = new PlugObj(this)
|
|
209
|
+
this.plugList[PlugObj.Name] = plug
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
this.$plugBtnWrap.querySelectorAll('.active').forEach(item => item.classList.remove('active'))
|
|
211
213
|
|
|
212
214
|
// 若点击已打开的,则收起
|
|
213
|
-
if (
|
|
215
|
+
if (PlugObj.Name === this.openedPlugName) {
|
|
214
216
|
plug.onHide()
|
|
215
217
|
this.$plugWrap.style.display = 'none'
|
|
216
218
|
this.openedPlugName = null
|
|
217
219
|
return
|
|
218
220
|
}
|
|
219
221
|
|
|
220
|
-
if (this.$plugWrap.querySelector(`[data-plug-name="${
|
|
222
|
+
if (this.$plugWrap.querySelector(`[data-plug-name="${PlugObj.Name}"]`) === null) {
|
|
221
223
|
// 需要初始化
|
|
222
224
|
const plugEl = plug.getEl()
|
|
223
|
-
plugEl.setAttribute('data-plug-name',
|
|
225
|
+
plugEl.setAttribute('data-plug-name', PlugObj.Name)
|
|
224
226
|
plugEl.style.display = 'none'
|
|
225
227
|
this.$plugWrap.appendChild(plugEl)
|
|
226
228
|
}
|
|
227
229
|
|
|
228
230
|
(Array.from(this.$plugWrap.children) as HTMLElement[]).forEach((plugItemEl: HTMLElement) => {
|
|
229
231
|
const plugItemName = plugItemEl.getAttribute('data-plug-name')!
|
|
230
|
-
if (plugItemName ===
|
|
232
|
+
if (plugItemName === PlugObj.Name) {
|
|
231
233
|
plugItemEl.style.display = ''
|
|
232
234
|
this.plugList[plugItemName].onShow()
|
|
233
235
|
} else {
|
|
@@ -237,7 +239,7 @@ export default class Editor extends Component {
|
|
|
237
239
|
})
|
|
238
240
|
|
|
239
241
|
this.$plugWrap.style.display = ''
|
|
240
|
-
this.openedPlugName =
|
|
242
|
+
this.openedPlugName = PlugObj.Name
|
|
241
243
|
|
|
242
244
|
btnElem.classList.add('active')
|
|
243
245
|
})
|
|
@@ -315,21 +317,27 @@ export default class Editor extends Component {
|
|
|
315
317
|
this.$sendReply = null
|
|
316
318
|
}
|
|
317
319
|
|
|
318
|
-
setReply (commentData: CommentData) {
|
|
320
|
+
setReply (commentData: CommentData, $comment: HTMLElement, scroll = true) {
|
|
319
321
|
if (this.replyComment !== null) {
|
|
320
322
|
this.cancelReply()
|
|
321
323
|
}
|
|
322
324
|
|
|
323
325
|
if (this.$sendReply === null) {
|
|
324
|
-
this.$sendReply = Utils.createElement('<div class="atk-send-reply
|
|
326
|
+
this.$sendReply = Utils.createElement('<div class="atk-send-reply">回复 <span class="atk-text"></span><span class="atk-cancel" title="取消 AT">×</span></div>');
|
|
325
327
|
this.$sendReply.querySelector<HTMLElement>('.atk-text')!.innerText = `@${commentData.nick}`
|
|
326
328
|
this.$sendReply.addEventListener('click', () => {
|
|
327
329
|
this.cancelReply()
|
|
328
330
|
})
|
|
329
|
-
this.$textareaWrap.
|
|
331
|
+
this.$textareaWrap.append(this.$sendReply)
|
|
330
332
|
}
|
|
331
333
|
this.replyComment = commentData
|
|
332
|
-
|
|
334
|
+
|
|
335
|
+
if (this.ctx.conf.editorTravel === true) {
|
|
336
|
+
this.travel($comment)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (scroll) Ui.scrollIntoView(this.$el)
|
|
340
|
+
|
|
333
341
|
this.$textarea.focus()
|
|
334
342
|
}
|
|
335
343
|
|
|
@@ -339,6 +347,10 @@ export default class Editor extends Component {
|
|
|
339
347
|
this.$sendReply = null
|
|
340
348
|
}
|
|
341
349
|
this.replyComment = null
|
|
350
|
+
|
|
351
|
+
if (this.ctx.conf.editorTravel === true) {
|
|
352
|
+
this.travelBack()
|
|
353
|
+
}
|
|
342
354
|
}
|
|
343
355
|
|
|
344
356
|
initSubmit () {
|
|
@@ -366,15 +378,24 @@ export default class Editor extends Component {
|
|
|
366
378
|
nick: this.user.data.nick,
|
|
367
379
|
email: this.user.data.email,
|
|
368
380
|
link: this.user.data.link,
|
|
369
|
-
rid: this.replyComment === null ? 0 : this.replyComment.id
|
|
381
|
+
rid: (this.replyComment === null) ? 0 : this.replyComment.id,
|
|
382
|
+
page_key: (this.replyComment === null) ? this.ctx.conf.pageKey : this.replyComment.page_key,
|
|
383
|
+
page_title: (this.replyComment === null) ? this.ctx.conf.pageTitle : undefined,
|
|
384
|
+
site_name: (this.replyComment === null) ? this.ctx.conf.site : this.replyComment.site_name
|
|
370
385
|
})
|
|
371
386
|
|
|
387
|
+
// 回复不同页面的评论
|
|
388
|
+
if (this.replyComment !== null && this.replyComment.page_key !== this.ctx.conf.pageKey) {
|
|
389
|
+
window.open(`${this.replyComment.page_key}#atk-comment-${nComment.id}`)
|
|
390
|
+
}
|
|
391
|
+
|
|
372
392
|
this.ctx.trigger('list-insert', nComment)
|
|
373
393
|
this.clearEditor() // 清空编辑器
|
|
374
394
|
this.ctx.trigger('editor-submitted')
|
|
375
395
|
} catch (err: any) {
|
|
376
396
|
console.error(err)
|
|
377
397
|
this.showNotify(`评论失败,${err.msg || String(err)}`, 'e')
|
|
398
|
+
return
|
|
378
399
|
} finally {
|
|
379
400
|
Ui.hideLoading(this.$el)
|
|
380
401
|
}
|
|
@@ -386,7 +407,8 @@ export default class Editor extends Component {
|
|
|
386
407
|
|
|
387
408
|
/** 关闭评论 */
|
|
388
409
|
close () {
|
|
389
|
-
this.$
|
|
410
|
+
if (!this.$textareaWrap.querySelector('.atk-comment-closed'))
|
|
411
|
+
this.$textareaWrap.prepend(Utils.createElement('<div class="atk-comment-closed">仅管理员可评论</div>'))
|
|
390
412
|
|
|
391
413
|
if (!this.user.data.isAdmin) {
|
|
392
414
|
this.$textarea.style.display = 'none'
|
|
@@ -401,9 +423,28 @@ export default class Editor extends Component {
|
|
|
401
423
|
|
|
402
424
|
/** 打开评论 */
|
|
403
425
|
open () {
|
|
404
|
-
this.$
|
|
426
|
+
this.$textareaWrap.querySelector('.atk-comment-closed')?.remove()
|
|
405
427
|
this.$textarea.style.display = ''
|
|
406
428
|
this.$bottom.style.display = ''
|
|
407
429
|
}
|
|
430
|
+
|
|
431
|
+
travel ($afterEl: HTMLElement) {
|
|
432
|
+
if (this.isTraveling) return
|
|
433
|
+
this.isTraveling = true
|
|
434
|
+
this.$el.after(Utils.createElement('<div class="atk-editor-travel-placeholder"></div>'))
|
|
435
|
+
|
|
436
|
+
const $travelPlace = Utils.createElement('<div></div>')
|
|
437
|
+
$afterEl.after($travelPlace)
|
|
438
|
+
$travelPlace.replaceWith(this.$el)
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
travelBack () {
|
|
442
|
+
if (!this.isTraveling) return
|
|
443
|
+
this.isTraveling = false
|
|
444
|
+
this.ctx.$root.querySelector('.atk-editor-travel-placeholder')?.replaceWith(this.$el)
|
|
445
|
+
|
|
446
|
+
// 取消回复
|
|
447
|
+
if (this.replyComment !== null) this.cancelReply()
|
|
448
|
+
}
|
|
408
449
|
}
|
|
409
450
|
|
|
@@ -1,25 +1,18 @@
|
|
|
1
|
-
<div class="atk-comment-wrap"
|
|
1
|
+
<div class="atk-comment-wrap">
|
|
2
2
|
<div class="atk-comment">
|
|
3
|
-
|
|
4
3
|
<div class="atk-avatar"></div>
|
|
5
|
-
|
|
6
|
-
<div class="atk-comment-main">
|
|
7
|
-
|
|
4
|
+
<div class="atk-main">
|
|
8
5
|
<div class="atk-header">
|
|
9
|
-
<span class="atk-nick"></span>
|
|
10
|
-
<span class="atk-badge"></span>
|
|
11
|
-
<span class="atk-date"></span>
|
|
6
|
+
<span class="atk-item atk-nick"></span>
|
|
7
|
+
<span class="atk-item atk-badge"></span>
|
|
8
|
+
<span class="atk-item atk-date"></span>
|
|
12
9
|
</div>
|
|
13
|
-
|
|
14
10
|
<div class="atk-body">
|
|
15
11
|
<div class="atk-content"></div>
|
|
16
12
|
</div>
|
|
17
|
-
|
|
18
13
|
<div class="atk-footer">
|
|
19
|
-
<div class="atk-
|
|
14
|
+
<div class="atk-actions"></div>
|
|
20
15
|
</div>
|
|
21
|
-
|
|
22
16
|
</div>
|
|
23
|
-
|
|
24
17
|
</div>
|
|
25
18
|
</div>
|
|
@@ -1,19 +1,18 @@
|
|
|
1
|
-
<div class="atk-editor">
|
|
2
|
-
<div class="atk-
|
|
1
|
+
<div class="atk-main-editor">
|
|
2
|
+
<div class="atk-header">
|
|
3
3
|
<input name="nick" placeholder="昵称" class="atk-nick" type="text" required="required">
|
|
4
4
|
<input name="email" placeholder="邮箱" class="atk-email" type="email" required="required">
|
|
5
5
|
<input name="link" placeholder="网址 (https://)" class="atk-link" type="url">
|
|
6
6
|
</div>
|
|
7
|
-
<div class="atk-
|
|
8
|
-
<
|
|
9
|
-
<textarea id="atk-editor-textarea" class="atk-editor-textarea" placeholder=""></textarea>
|
|
7
|
+
<div class="atk-textarea-wrap">
|
|
8
|
+
<textarea class="atk-textarea" placeholder=""></textarea>
|
|
10
9
|
</div>
|
|
11
|
-
<div class="atk-
|
|
12
|
-
<div class="atk-
|
|
13
|
-
<div class="atk-
|
|
14
|
-
<div class="atk-
|
|
10
|
+
<div class="atk-plug-wrap" style="display: none;"></div>
|
|
11
|
+
<div class="atk-bottom">
|
|
12
|
+
<div class="atk-item atk-plug-btn-wrap"></div>
|
|
13
|
+
<div class="atk-item">
|
|
15
14
|
<button type="button" class="atk-send-btn"></button>
|
|
16
15
|
</div>
|
|
17
16
|
</div>
|
|
18
|
-
<div class="atk-
|
|
17
|
+
<div class="atk-notify-wrap"></div>
|
|
19
18
|
</div>
|