artalk 2.1.4 → 2.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CNAME +1 -0
  2. package/dist/Artalk.css +1 -1
  3. package/dist/Artalk.es.js +303 -1248
  4. package/dist/Artalk.iife.js +1 -1
  5. package/dist/Artalk.js +1 -1
  6. package/index.html +143 -0
  7. package/package.json +8 -29
  8. package/postcss.config.js +5 -0
  9. package/src/api/index.ts +14 -0
  10. package/src/api/request.ts +4 -2
  11. package/src/artalk.ts +28 -6
  12. package/src/components/comment.ts +20 -3
  13. package/src/components/html/list.html +4 -2
  14. package/src/components/html/sidebar-layer.html +8 -0
  15. package/src/components/list.ts +60 -0
  16. package/src/components/sidebar-layer.ts +143 -0
  17. package/src/defaults.ts +1 -0
  18. package/src/lib/detect.js +3 -1
  19. package/src/lib/utils.ts +25 -0
  20. package/src/style/_extend.less +0 -80
  21. package/src/style/comment.less +6 -0
  22. package/src/style/list.less +66 -0
  23. package/src/style/main.less +1 -1
  24. package/src/style/sidebar-layer.less +92 -0
  25. package/types/artalk-config.d.ts +4 -1
  26. package/types/artalk-data.d.ts +3 -0
  27. package/vite-common.config.ts +15 -0
  28. package/vite-demo.config.ts +14 -0
  29. package/vite.config.ts +36 -0
  30. package/LICENSE +0 -674
  31. package/README.md +0 -84
  32. package/src/components/admin/page-list.ts +0 -194
  33. package/src/components/admin/site-list-floater.ts +0 -98
  34. package/src/components/admin/site-list.ts +0 -289
  35. package/src/components/html/sidebar.html +0 -22
  36. package/src/components/item-text-editor.ts +0 -136
  37. package/src/components/sidebar-view.ts +0 -26
  38. package/src/components/sidebar-views/comments-view.ts +0 -73
  39. package/src/components/sidebar-views/pages-view.ts +0 -61
  40. package/src/components/sidebar-views/setting-view.ts +0 -21
  41. package/src/components/sidebar-views/sites-view.ts +0 -36
  42. package/src/components/sidebar-views/transfer-view.ts +0 -218
  43. package/src/components/sidebar.ts +0 -293
  44. package/src/lib/md5.js +0 -6
  45. package/src/style/item-text-editor.less +0 -35
  46. package/src/style/page-list.less +0 -50
  47. package/src/style/sidebar.less +0 -420
  48. package/src/style/site-list.less +0 -208
@@ -1,136 +0,0 @@
1
- import '../style/item-text-editor.less'
2
-
3
- import * as Utils from '../lib/utils'
4
-
5
- interface ItemTextEditorConf {
6
- initValue?: string
7
- validator?: (value: string) => boolean
8
- onYes?: (value: string) => boolean|void|Promise<boolean|void>
9
- onNo?: () => boolean|void|Promise<boolean|void>
10
- placeholder?: string
11
- }
12
-
13
- /**
14
- * 通用列表项目文本编辑器
15
- */
16
- export default class ItemTextEditor {
17
- private conf: ItemTextEditorConf
18
- public $el: HTMLElement
19
-
20
- public $input: HTMLInputElement
21
- public $yesBtn: HTMLElement
22
- public $noBtn: HTMLElement
23
-
24
- public value: string = ''
25
- public allowSubmit = true
26
-
27
- constructor(conf: ItemTextEditorConf) {
28
- this.conf = conf
29
-
30
- this.$el = Utils.createElement(
31
- `<div class="atk-item-text-editor-layer">
32
- <div class="atk-edit-form">
33
- <input class="atk-main-input" type="text" placeholder="输入内容..." autocomplete="off" autofocus>
34
- </div>
35
- <div class="atk-actions">
36
- <div class="atk-item atk-yes-btn">
37
- <i class="atk-icon atk-icon-yes"></i>
38
- </div>
39
- <div class="atk-item atk-no-btn">
40
- <i class="atk-icon atk-icon-no"></i>
41
- </div>
42
- </div>
43
- </div>`)
44
-
45
- this.$input = this.$el.querySelector('.atk-main-input')!
46
- this.$yesBtn = this.$el.querySelector('.atk-yes-btn')!
47
- this.$noBtn = this.$el.querySelector('.atk-no-btn')!
48
-
49
- this.$input.value = conf.initValue || ''
50
- this.value = conf.initValue || ''
51
- if (this.conf.placeholder) this.$input.placeholder = this.conf.placeholder
52
-
53
- this.$input.oninput = () => this.onInput()
54
- this.$input.onkeyup = (evt) => {
55
- if (evt.key === 'Enter' || evt.keyCode === 13) { // 按下回车键
56
- evt.preventDefault()
57
- this.submit()
58
- }
59
- }
60
-
61
- window.setTimeout(() => this.$input.focus(), 80)
62
-
63
- this.$yesBtn.onclick = () => { this.submit() }
64
- this.$noBtn.onclick = () => { this.cancel() }
65
- }
66
-
67
- public appendTo(parentDOM: HTMLElement) {
68
- parentDOM.append(this.$el)
69
- return this
70
- }
71
-
72
- private onInput() {
73
- this.value = this.$input.value
74
-
75
- // 验证器
76
- if (this.conf.validator) {
77
- const ok = this.conf.validator(this.value)
78
- this.setAllowSubmit(ok)
79
- if (!ok) {
80
- this.$input.classList.add('atk-invalid')
81
- } else {
82
- this.$input.classList.remove('atk-invalid')
83
- }
84
- }
85
- }
86
-
87
- public setAllowSubmit(allow: boolean) {
88
- if (this.allowSubmit === allow) return
89
- this.allowSubmit = allow
90
- if (!allow) {
91
- this.$yesBtn.classList.add('.atk-disabled')
92
- } else {
93
- this.$yesBtn.classList.remove('.atk-disabled')
94
- }
95
- }
96
-
97
- public async submit() {
98
- if (!this.allowSubmit) return
99
-
100
- if (this.conf.onYes) {
101
- let isContinue: any
102
- if (this.conf.onYes instanceof (async () => {}).constructor) {
103
- isContinue = await this.conf.onYes(this.value)
104
- } else {
105
- isContinue = this.conf.onYes(this.value)
106
- }
107
-
108
- if (isContinue === undefined || isContinue === true) {
109
- this.closeEditor()
110
- }
111
- } else {
112
- this.closeEditor()
113
- }
114
- }
115
-
116
- public async cancel() {
117
- if (this.conf.onNo) {
118
- let isContinue: any
119
- if (this.conf.onNo instanceof (async () => {}).constructor) {
120
- isContinue = await this.conf.onNo()
121
- } else {
122
- isContinue = this.conf.onNo()
123
- }
124
-
125
- if (isContinue === undefined || isContinue === true) {
126
- this.closeEditor()
127
- }
128
- } else {
129
- this.closeEditor()
130
- }
131
- }
132
-
133
- public closeEditor() {
134
- this.$el.remove()
135
- }
136
- }
@@ -1,26 +0,0 @@
1
- import Context from '../context'
2
- import Component from '../lib/component'
3
- import * as Utils from '../lib/utils'
4
- import Comment from './comment'
5
-
6
- export default class SidebarView extends Component {
7
- static viewName = ''
8
- static viewTitle = ''
9
- static viewAdminOnly = false
10
-
11
- viewTabs = {}
12
- viewActiveTab = ''
13
-
14
- protected $parent: HTMLElement
15
-
16
- constructor(ctx: Context, $parent: HTMLElement) {
17
- super(ctx)
18
-
19
- this.$parent = $parent
20
- this.$el = Utils.createElement(`<div class="atk-sidebar-view"></div>`)
21
- }
22
-
23
- mount(siteName: string) {}
24
-
25
- switchTab(tab: string, siteName: string): boolean|void {}
26
- }
@@ -1,73 +0,0 @@
1
- import Context from '../../context'
2
- import SidebarView from '../sidebar-view'
3
- import * as Utils from '../../lib/utils'
4
- import ListLite from '../list-lite'
5
-
6
- export default class MessageView extends SidebarView {
7
- static viewName = 'comments'
8
- static viewTitle = '评论'
9
-
10
- viewTabs: any = {}
11
- viewActiveTab = ''
12
-
13
- list!: ListLite
14
-
15
- mount(siteName: string) {
16
- // tabs
17
- if (this.ctx.user.data.isAdmin) {
18
- this.viewTabs = {
19
- admin_all: '全部',
20
- admin_pending: '待审',
21
- all: '个人',
22
- }
23
- this.viewActiveTab = 'admin_all'
24
- } else {
25
- this.viewTabs = {
26
- mentions: '提及',
27
- all: '全部',
28
- mine: '我的',
29
- pending: '待审',
30
- }
31
- this.viewActiveTab = 'mentions'
32
- }
33
-
34
- this.list = new ListLite(this.ctx)
35
- this.list.flatMode = true
36
- this.list.unreadHighlight = true
37
- this.list.scrollListenerAt = this.$parent
38
- this.list.pageMode = 'pagination'
39
- this.list.noCommentText = '<div class="atk-sidebar-no-content">无内容</div>'
40
- this.list.renderComment = (comment) => {
41
- comment.setOpenURL(`${comment.data.page_key}#atk-comment-${comment.data.id}`)
42
- comment.onReplyBtnClick = () => {
43
- if (this.ctx.conf.editorTravel === true) {
44
- this.ctx.trigger('editor-reply', {data: comment.data, $el: comment.$el, scroll: false})
45
- } else {
46
- this.ctx.trigger('sidebar-hide')
47
- this.ctx.trigger('editor-reply', {data: comment.data, $el: comment.$el, scroll: true})
48
- }
49
- }
50
- }
51
- this.list.paramsEditor = (params) => {
52
- params.site_name = siteName
53
- }
54
-
55
- this.$el.innerHTML = ''
56
- this.$el.append(this.list.$el)
57
-
58
- this.switchTab(this.viewActiveTab, siteName)
59
- }
60
-
61
- switchTab(tab: string, siteName: string): boolean|void {
62
- //console.log(tab, siteName)
63
- this.viewActiveTab = tab
64
- this.list.paramsEditor = (params) => {
65
- params.type = tab as any
66
- params.site_name = siteName
67
- }
68
- this.list.fetchComments(0)
69
-
70
- return true
71
- }
72
- }
73
-
@@ -1,61 +0,0 @@
1
- import Api from '~/src/api'
2
- import Context from '../../context'
3
- import Component from '../../lib/component'
4
- import * as Utils from '../../lib/utils'
5
- import * as Ui from '../../lib/ui'
6
- import PageList from '../admin/page-list'
7
- import Comment from '../comment'
8
- import Pagination, { PaginationConf } from '../pagination'
9
- import SidebarView from '../sidebar-view'
10
-
11
- const PAGE_SIZE = 20
12
-
13
- export default class PagesView extends SidebarView {
14
- static viewName = 'pages'
15
- static viewTitle = '页面'
16
- static viewAdminOnly = true
17
-
18
- viewTabs = {}
19
- viewActiveTab = ''
20
-
21
- pageList!: PageList
22
- pagination!: Pagination
23
-
24
- mount(siteName: string) {
25
- if (!this.pageList) {
26
- this.pageList = new PageList(this.ctx)
27
- this.$el.append(this.pageList.$el)
28
- }
29
-
30
- this.switchTab(this.viewActiveTab, siteName)
31
- }
32
-
33
- switchTab(tab: string, siteName: string): boolean|void {
34
- this.reqPages(siteName, 0)
35
- }
36
-
37
- async reqPages(siteName: string, offset: number) {
38
- this.pageList.clearAll()
39
- ;(this.$el.parentNode as any)?.scrollTo(0, 0)
40
-
41
- Ui.showLoading(this.$el)
42
-
43
- const data = await new Api(this.ctx).pageGet(siteName, offset, PAGE_SIZE)
44
- this.pageList.importPages(data.pages || [])
45
-
46
- Ui.hideLoading(this.$el)
47
-
48
- if (!this.pagination) {
49
- this.pagination = new Pagination(data.total, {
50
- pageSize: PAGE_SIZE,
51
- onChange: (o: number) => {
52
- this.reqPages(siteName, o)
53
- }
54
- })
55
-
56
- this.$el.append(this.pagination.$el)
57
- }
58
- if (this.pagination && offset === 0)
59
- this.pagination.update(offset, data.total)
60
- }
61
- }
@@ -1,21 +0,0 @@
1
- import Api from '~/src/api'
2
- import Context from '../../context'
3
- import Component from '../../lib/component'
4
- import * as Utils from '../../lib/utils'
5
- import SiteList from '../admin/site-list'
6
- import Comment from '../comment'
7
- import SidebarView from '../sidebar-view'
8
-
9
- export default class SettingView extends SidebarView {
10
- static viewName = 'setting'
11
- static viewTitle = '配置'
12
- static viewAdminOnly = true
13
-
14
- viewTabs = {}
15
- viewActiveTab = ''
16
-
17
- mount(siteName: string) {
18
- }
19
-
20
- switchTab(tab: string, siteName: string) {}
21
- }
@@ -1,36 +0,0 @@
1
- import Api from '~/src/api'
2
- import Context from '../../context'
3
- import Component from '../../lib/component'
4
- import * as Utils from '../../lib/utils'
5
- import SiteList from '../admin/site-list'
6
- import Comment from '../comment'
7
- import SidebarView from '../sidebar-view'
8
-
9
- export default class SitesView extends SidebarView {
10
- static viewName = 'sites'
11
- static viewTitle = '站点'
12
- static viewAdminOnly = true
13
-
14
- viewTabs = {}
15
- viewActiveTab = ''
16
-
17
- siteList!: SiteList
18
-
19
- mount(siteName: string) {
20
- if (!this.siteList) {
21
- this.siteList = new SiteList(this.ctx)
22
- this.$el.append(this.siteList.$el)
23
- }
24
-
25
- this.reqSites()
26
- }
27
-
28
- switchTab(tab: string, siteName: string) {
29
- this.reqSites()
30
- }
31
-
32
- async reqSites() {
33
- const sites = await new Api(this.ctx).siteGet()
34
- this.siteList.loadSites(sites)
35
- }
36
- }
@@ -1,218 +0,0 @@
1
- import Api from '~/src/api'
2
- import Context from '../../context'
3
- import Component from '../../lib/component'
4
- import * as Utils from '../../lib/utils'
5
- import * as Ui from '../../lib/ui'
6
- import SiteList from '../admin/site-list'
7
- import Comment from '../comment'
8
- import SidebarView from '../sidebar-view'
9
-
10
- export default class TransferView extends SidebarView {ß
11
- static viewName = 'transfer'
12
- static viewTitle = '迁移'
13
- static viewAdminOnly = true
14
-
15
- viewTabs = {
16
- 'import': '导入',
17
- 'export': '导出',
18
- }
19
- viewActiveTab = 'import'
20
-
21
- mount(siteName: string) {
22
- this.switchTab('import', siteName)
23
- }
24
-
25
- switchTab(tab: string, siteName: string) {
26
- if (tab === 'import') {
27
- this.initImport()
28
- } else if (tab === 'export') {
29
- // if (window.confirm(`开始导出?`))
30
- this.initExport()
31
- return false
32
- }
33
- return true
34
- }
35
-
36
- initImport() {
37
- this.$el.innerHTML =
38
- `<div class="atk-log-wrap" style="display: none;">
39
- <div class="atk-log-back-btn">返回</div>
40
- <div class="atk-log"></div>
41
- </div>
42
- <div class="atk-form">
43
- <div class="atk-label">数据类型</div>
44
- <select name="AtkDataType">
45
- <option value="artrans">Artrans (数据行囊)</option>
46
- <option value="artalk_v1">Artalk v1 (PHP 旧版)</option>
47
- <option value="typecho">Typecho</option>
48
- <option value="wordpress">WordPress</option>
49
- <option value="disqus">Disqus</option>
50
- <option value="commento">Commento</option>
51
- <option value="valine">Valine</option>
52
- <option value="twikoo">Twikoo</option>
53
- </select>
54
- <div class="atk-label atk-data-file-label">数据文件</div>
55
- <input type="file" name="AtkDataFile" accept="text/plain,.json">
56
- <div class="atk-label">目标站点名</div>
57
- <input type="text" name="AtkSiteName" placeholder="输入内容..." autocomplete="off">
58
- <div class="atk-label">目标站点 URL</div>
59
- <input type="text" name="AtkSiteURL" placeholder="输入内容..." autocomplete="off">
60
- <div class="atk-label">启动参数(可选)</div>
61
- <textarea name="AtkPayload"></textarea>
62
- <span class="atk-desc">启动参数查阅:“<a href="https://artalk.js.org/guide/transfer.html" target="_blank">文档 · 数据搬家</a>”</span>
63
- <button class="atk-btn" name="AtkSubmit">导入</button>
64
- </div>`
65
-
66
- const $form = this.$el.querySelector<HTMLSelectElement>('.atk-form')!
67
- const $dataType = $form.querySelector<HTMLSelectElement>('[name="AtkDataType"]')!
68
- const $dataFile = $form.querySelector<HTMLInputElement>('[name="AtkDataFile"]')!
69
- const $dataFileLabel = $form.querySelector<HTMLInputElement>('.atk-data-file-label')!
70
- const $siteName = $form.querySelector<HTMLInputElement>('[name="AtkSiteName"]')!
71
- const $siteURL = $form.querySelector<HTMLInputElement>('[name="AtkSiteURL"]')!
72
- const $payload = $form.querySelector<HTMLTextAreaElement>('[name="AtkPayload"]')!
73
- const $submitBtn = $form.querySelector<HTMLButtonElement>('[name="AtkSubmit"]')!
74
- const setError = (msg: string) => window.alert(msg)
75
-
76
- $dataType.onchange = () => {
77
- if (['typecho'].includes($dataType.value)) {
78
- $dataFile.style.display = 'none'
79
- $dataFileLabel.style.display = 'none'
80
- } else {
81
- $dataFile.style.display = ''
82
- $dataFileLabel.style.display = ''
83
- }
84
- }
85
-
86
- $submitBtn.onclick = () => {
87
- const dataType = $dataType.value.trim()
88
- const siteName = $siteName.value.trim()
89
- const siteURL = $siteURL.value.trim()
90
- const payload = $payload.value.trim()
91
-
92
- if (dataType === '') {
93
- setError('请选择数据类型')
94
- return
95
- }
96
-
97
- // 请求 payload 参数制备
98
- let rData: any = {}
99
- if (payload) {
100
- // JSON 格式检验
101
- try {
102
- rData = JSON.parse(payload)
103
- } catch (err) {
104
- setError(`Payload JSON 格式有误:${String(err)}`)
105
- return
106
- }
107
-
108
- if (rData !instanceof Object) {
109
- setError(`Payload 需为 JSON 对象`)
110
- return
111
- }
112
- }
113
- if (siteName) rData.t_name = siteName
114
- if (siteURL) rData.t_url = siteURL
115
-
116
- // 创建导入会话
117
- const createSession = (dataStr?: string) => {
118
- const $logWrap = this.$el.querySelector<HTMLElement>('.atk-log-wrap')!
119
- const $log = $logWrap.querySelector<HTMLElement>('.atk-log')!
120
- const $backBtn = this.$el.querySelector<HTMLElement>('.atk-log-back-btn')!
121
-
122
- $logWrap.style.display = ''
123
- $form.style.display = 'none'
124
-
125
- $backBtn.onclick = () => {
126
- $logWrap.style.display = 'none'
127
- $form.style.display = ''
128
- }
129
-
130
- if (dataStr) rData.json_data = dataStr
131
-
132
- // 创建 iframe
133
- const frameName = `f_${+new Date()}`
134
- const $frame = document.createElement('iframe')
135
- $frame.className = 'atk-iframe'
136
- $frame.name = frameName
137
- $log.innerHTML = ''
138
- $log.append($frame)
139
-
140
- const formParams: {[k: string]: string} = {
141
- type: dataType,
142
- payload: JSON.stringify(rData),
143
- token: this.ctx.user.data.token || '',
144
- }
145
-
146
- // 创建临时表单,初始化 iframe
147
- const $formTmp = document.createElement('form')
148
- $formTmp.style.display = 'none'
149
- $formTmp.setAttribute('method', 'post')
150
- $formTmp.setAttribute('action', `${this.ctx.conf.server}/admin/import`)
151
- $formTmp.setAttribute("target", frameName)
152
-
153
- Object.entries(formParams).forEach(([key, val]) => {
154
- const $inputTmp = document.createElement('input')
155
- $inputTmp.setAttribute('type', 'hidden')
156
- $inputTmp.setAttribute('name', key)
157
- $inputTmp.value = val
158
- $formTmp.appendChild($inputTmp)
159
- })
160
-
161
- $logWrap.append($formTmp)
162
- $formTmp.submit()
163
- $formTmp.remove()
164
- }
165
-
166
- const reader = new FileReader()
167
- reader.onload = () => {
168
- const data = String(reader.result)
169
- createSession(data)
170
- }
171
-
172
- // 是否已选择文件
173
- if ($dataFile.files?.length) {
174
- // 先读取文件
175
- reader.readAsText($dataFile.files[0])
176
- } else {
177
- // 直接开始会话
178
- createSession()
179
- }
180
- }
181
- }
182
-
183
- async initExport() {
184
- Ui.showLoading(this.$el)
185
-
186
- try {
187
- const d = await new Api(this.ctx).export()
188
- this.download(`artrans-${this.getYmdHisFilename()}.json`, d)
189
- } catch (err: any) {
190
- console.log(err)
191
- window.alert(`${String(err)}`)
192
- return
193
- } finally { Ui.hideLoading(this.$el) }
194
- }
195
-
196
- download(filename: string, text: string) {
197
- const el = document.createElement('a')
198
- el.setAttribute('href', `data:text/json;charset=utf-8,${encodeURIComponent(text)}`);
199
- el.setAttribute('download', filename)
200
- el.style.display = 'none'
201
- document.body.appendChild(el)
202
- el.click()
203
- document.body.removeChild(el)
204
- }
205
-
206
- getYmdHisFilename() {
207
- const date = new Date()
208
-
209
- const year = date.getFullYear()
210
- const month = date.getMonth() + 1
211
- const day = date.getDate()
212
- const hours = date.getHours()
213
- const minutes = date.getMinutes()
214
- const seconds = date.getSeconds()
215
-
216
- return `${year}${month}${day}-${hours}${Utils.padWithZeros(minutes, 2)}${Utils.padWithZeros(seconds, 2)}`
217
- }
218
- }