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.
Files changed (91) hide show
  1. package/README.md +7 -60
  2. package/dist/Artalk.css +1 -1
  3. package/dist/Artalk.es.js +5996 -1
  4. package/dist/Artalk.iife.js +266 -1
  5. package/dist/Artalk.js +266 -1
  6. package/package.json +3 -4
  7. package/src/api/index.ts +269 -0
  8. package/src/api/request.ts +125 -0
  9. package/src/artalk.ts +153 -0
  10. package/src/components/_component.ts.txt +10 -0
  11. package/src/components/action-btn.ts +169 -0
  12. package/src/components/admin/page-list.ts +194 -0
  13. package/src/components/admin/site-list-floater.ts +98 -0
  14. package/src/components/admin/site-list.ts +289 -0
  15. package/src/components/comment.ts +478 -0
  16. package/src/components/dialog.ts +63 -0
  17. package/src/components/editor-plugs/{_ExamplePlug.ts.txt → _example-plug.ts.txt} +0 -0
  18. package/src/components/editor-plugs/{EditorPlug.ts → editor-plug.ts} +3 -3
  19. package/src/components/editor-plugs/{EmoticonsPlug.less → emoticons-plug.less} +0 -0
  20. package/src/components/editor-plugs/{EmoticonsPlug.ts → emoticons-plug.ts} +9 -9
  21. package/src/components/editor-plugs/{PreviewPlug.less → preview-plug.less} +0 -0
  22. package/src/components/editor-plugs/{PreviewPlug.ts → preview-plug.ts} +10 -10
  23. package/src/components/{Editor.ts → editor.ts} +104 -102
  24. package/src/components/html/comment.html +2 -6
  25. package/src/components/html/list.html +0 -1
  26. package/src/components/html/sidebar.html +18 -4
  27. package/src/components/item-text-editor.ts +136 -0
  28. package/src/components/layer.ts +135 -0
  29. package/src/components/list-lite.ts +507 -0
  30. package/src/components/list.ts +172 -0
  31. package/src/components/pagination.ts +150 -0
  32. package/src/components/read-more-btn.ts +71 -0
  33. package/src/components/sidebar-view.ts +26 -0
  34. package/src/components/sidebar-views/comments-view.ts +66 -0
  35. package/src/components/sidebar-views/pages-view.ts +61 -0
  36. package/src/components/sidebar-views/setting-view.ts +21 -0
  37. package/src/components/sidebar-views/sites-view.ts +42 -0
  38. package/src/components/sidebar-views/transfer-view.ts +218 -0
  39. package/src/components/sidebar.ts +288 -0
  40. package/src/context.ts +42 -0
  41. package/src/default-conf.ts +42 -0
  42. package/src/lib/checker/admin-checker.ts +33 -0
  43. package/src/lib/checker/captcha-checker.ts +38 -0
  44. package/src/lib/checker/index.ts +134 -0
  45. package/src/lib/component.ts +3 -3
  46. package/src/lib/md5.js +6 -0
  47. package/src/lib/ui.ts +64 -74
  48. package/src/lib/user.ts +2 -8
  49. package/src/lib/utils.ts +30 -11
  50. package/src/main.ts +1 -1
  51. package/src/style/_color.less +8 -2
  52. package/src/style/_extend.less +81 -0
  53. package/src/style/_variables.less +3 -0
  54. package/src/style/comment.less +60 -24
  55. package/src/style/editor.less +3 -0
  56. package/src/style/item-text-editor.less +35 -0
  57. package/src/style/list.less +51 -32
  58. package/src/style/main.less +75 -1
  59. package/src/style/page-list.less +50 -0
  60. package/src/style/pagination.less +58 -0
  61. package/src/style/sidebar.less +274 -199
  62. package/src/style/site-list.less +208 -0
  63. package/tsconfig.json +27 -0
  64. package/types/artalk-config.d.ts +68 -51
  65. package/types/artalk-data.d.ts +38 -1
  66. package/types/event.d.ts +73 -0
  67. package/types/index.d.ts +1 -6
  68. package/.github/workflows/npm.yml +0 -31
  69. package/.github/workflows/pages.yml +0 -30
  70. package/dist/src/Event.d.ts +0 -59
  71. package/dist/types/artalk-config.d.ts +0 -79
  72. package/dist/types/artalk-data.d.ts +0 -153
  73. package/dist/types/global.d.ts +0 -1
  74. package/dist/types/index.d.ts +0 -8
  75. package/dist/types/vite-env.d.ts +0 -1
  76. package/example/pjax/res1.html +0 -41
  77. package/example/pjax/res2.html +0 -41
  78. package/src/Artalk.ts +0 -160
  79. package/src/Constant.ts +0 -6
  80. package/src/Context.ts +0 -34
  81. package/src/Event.d.ts +0 -59
  82. package/src/components/Comment.ts +0 -402
  83. package/src/components/Layer.ts +0 -138
  84. package/src/components/List.ts +0 -162
  85. package/src/components/ListLite.ts +0 -407
  86. package/src/components/Sidebar.ts +0 -141
  87. package/src/components/sidebar-views/AdminView.ts +0 -523
  88. package/src/components/sidebar-views/MessageView.ts +0 -48
  89. package/src/components/sidebar-views/SidebarView.ts +0 -24
  90. package/src/lib/api.ts +0 -380
  91. package/src/lib/checker.ts +0 -164
@@ -0,0 +1,169 @@
1
+ import * as Utils from '../lib/utils'
2
+
3
+ interface ActionBtnConf {
4
+ /** 按钮文字 (动态/静态) */
5
+ text: (() => string) | string
6
+
7
+ /** 仅管理员可用 */
8
+ adminOnly?: boolean
9
+
10
+ /** 确认操作 */
11
+ confirm?: boolean
12
+
13
+ /** 确认时提示文字 */
14
+ confirmText?: string
15
+ }
16
+
17
+ /**
18
+ * 通用操作按钮
19
+ */
20
+ export default class ActionBtn {
21
+ private conf: ActionBtnConf
22
+ public $el: HTMLElement
23
+
24
+ public isLoading = false // 正在加载
25
+
26
+ public msgRecTimer?: number // 消息显示复原定时器
27
+ public msgRecTimerFunc?: Function // 消息显示复原操作
28
+ public get isMessaging() { return !!this.msgRecTimer } // 消息正在显示
29
+
30
+ public isConfirming = false // 正在确认
31
+ public confirmRecTimer?: number // 确认消息复原定时器
32
+
33
+ /** 构造函数 */
34
+ constructor(conf: ActionBtnConf|string|(() => string)) {
35
+ this.$el = Utils.createElement(`<span class="atk-common-action-btn"></span>`)
36
+
37
+ this.conf = (typeof conf !== 'object') ? ({ text: conf }) : conf
38
+ this.$el.innerText = this.getText()
39
+
40
+ // 仅管理员可操作
41
+ if (this.conf.adminOnly) this.$el.setAttribute('atk-only-admin-show', '')
42
+ }
43
+
44
+ /** 将按钮装载到指定元素 */
45
+ public appendTo(dom: HTMLElement) {
46
+ dom.append(this.$el)
47
+ return this
48
+ }
49
+
50
+ /** 获取按钮文字(动态/静态) */
51
+ private getText() {
52
+ return (typeof this.conf.text === 'string') ? this.conf.text : this.conf.text()
53
+ }
54
+
55
+ /** 设置点击事件 */
56
+ public setClick(func: Function) {
57
+ this.$el.onclick = (e) => {
58
+ e.stopPropagation() // 防止穿透
59
+
60
+ // 按钮处于加载状态,禁止点击
61
+ if (this.isLoading) {
62
+ return
63
+ }
64
+
65
+ // 操作确认
66
+ if (this.conf.confirm && !this.isMessaging) {
67
+ const confirmRestore = () => {
68
+ this.isConfirming = false
69
+ this.$el.classList.remove('atk-btn-confirm')
70
+ this.$el.innerText = this.getText()
71
+ }
72
+
73
+ if (!this.isConfirming) {
74
+ this.isConfirming = true
75
+ this.$el.classList.add('atk-btn-confirm')
76
+ this.$el.innerText = this.conf.confirmText || '确认操作'
77
+ this.confirmRecTimer = window.setTimeout(() => confirmRestore(), 5000)
78
+ return
79
+ }
80
+
81
+ if (this.confirmRecTimer) window.clearTimeout(this.confirmRecTimer)
82
+ confirmRestore()
83
+ }
84
+
85
+ // 立刻释放掉原有的定时器(当显示消息后,用户立刻点击时)
86
+ if (this.msgRecTimer) {
87
+ this.fireMsgRecTimer()
88
+ this.clearMsgRecTimer()
89
+ return
90
+ }
91
+
92
+ // 触发设定的点击事件
93
+ func()
94
+ }
95
+ }
96
+
97
+ /** 文字刷新(动态/静态) */
98
+ public updateText(text?: (() => string) | string) {
99
+ if (text) this.conf.text = text
100
+ this.setLoading(false)
101
+ this.$el.innerText = this.getText()
102
+ }
103
+
104
+ /** 设置加载状态 */
105
+ public setLoading(value = true, loadingText?: string) {
106
+ if (this.isLoading === value) return
107
+ this.isLoading = value
108
+ if (value) {
109
+ this.$el.classList.add('atk-btn-loading')
110
+ this.$el.innerText = loadingText || '加载中...'
111
+ } else {
112
+ this.$el.classList.remove('atk-btn-loading')
113
+ this.$el.innerText = this.getText()
114
+ }
115
+ }
116
+
117
+ /** 错误消息 */
118
+ public setError(text: string) {
119
+ this.setMsg(text, 'atk-btn-error')
120
+ }
121
+
122
+ /** 警告消息 */
123
+ public setWarn(text: string) {
124
+ this.setMsg(text, 'atk-btn-warn')
125
+ }
126
+
127
+ /** 成功消息 */
128
+ public setSuccess(text: string) {
129
+ this.setMsg(text, 'atk-btn-success')
130
+ }
131
+
132
+ /** 设置消息 */
133
+ public setMsg(text: string, className?: string, duringTime?: number, after?: Function) {
134
+ this.setLoading(false)
135
+ if (className) this.$el.classList.add(className)
136
+ this.$el.innerText = text
137
+
138
+ // 设定复原 timer
139
+ this.setMsgRecTimer(() => {
140
+ this.$el.innerText = this.getText()
141
+ if (className) this.$el.classList.remove(className)
142
+ if (after) after()
143
+ }, duringTime || 2500) // 消息默认显示持续 2500s
144
+ }
145
+
146
+ /** 设置消息复原操作定时器 */
147
+ private setMsgRecTimer(func: Function, duringTime: number) {
148
+ this.fireMsgRecTimer()
149
+ this.clearMsgRecTimer()
150
+
151
+ this.msgRecTimerFunc = func
152
+ this.msgRecTimer = window.setTimeout(() => {
153
+ func()
154
+ this.clearMsgRecTimer()
155
+ }, duringTime)
156
+ }
157
+
158
+ /** 立刻触发器复原定时器 */
159
+ private fireMsgRecTimer() {
160
+ if (this.msgRecTimerFunc) this.msgRecTimerFunc()
161
+ }
162
+
163
+ /** 仅清除 timer */
164
+ private clearMsgRecTimer() {
165
+ if (this.msgRecTimer) window.clearTimeout(this.msgRecTimer)
166
+ this.msgRecTimer = undefined
167
+ this.msgRecTimerFunc = undefined
168
+ }
169
+ }
@@ -0,0 +1,194 @@
1
+ import '@/style/page-list.less'
2
+
3
+ import Context from '@/context'
4
+ import Component from '@/lib/component'
5
+ import * as Utils from '@/lib/utils'
6
+ import * as Ui from '@/lib/ui'
7
+ import { PageData } from '~/types/artalk-data'
8
+ import ItemTextEditor from '../item-text-editor'
9
+ import Api from '~/src/api'
10
+ import ActionBtn from '../action-btn'
11
+
12
+ export default class PageList extends Component {
13
+ $editor?: HTMLElement
14
+ $inputer?: HTMLElement
15
+
16
+ pages: PageData[] = []
17
+
18
+ constructor(ctx: Context) {
19
+ super(ctx)
20
+
21
+ this.$el = Utils.createElement(`<div class="atk-page-list"></div>`)
22
+ }
23
+
24
+ /** 清空所有 Pages */
25
+ clearAll() {
26
+ this.pages = []
27
+ this.$el.innerHTML = ''
28
+ }
29
+
30
+ /** 导入 Page 数据 */
31
+ importPages(pages: PageData[]) {
32
+ this.pages.push(...pages)
33
+
34
+ pages.forEach((page) => {
35
+ const $page = this.renderPage(page)
36
+ this.$el.append($page)
37
+ })
38
+ }
39
+
40
+ /** Page Element */
41
+ renderPage(page: PageData) {
42
+ const $page = Utils.createElement(
43
+ `<div class="atk-page-item">
44
+ <div class="atk-page-main">
45
+ <div class="atk-title"></div>
46
+ <div class="atk-sub"></div>
47
+ </div>
48
+ <div class="atk-page-actions">
49
+ <div class="atk-item atk-edit-btn">
50
+ <i class="atk-icon atk-icon-edit"></i>
51
+ </div>
52
+ </div>
53
+ </div>`)
54
+
55
+ const $main = $page.querySelector<HTMLElement>('.atk-page-main')!
56
+ const $title = $main.querySelector<HTMLElement>('.atk-title')!
57
+ const $sub = $main.querySelector<HTMLElement>('.atk-sub')!
58
+ const $editBtn = $page.querySelector<HTMLElement>('.atk-edit-btn')!
59
+
60
+ $title.innerText = page.title
61
+ $sub.innerText = page.url || page.key
62
+ $editBtn.onclick = () => this.showEditor(page, $page)
63
+
64
+ return $page
65
+ }
66
+
67
+ /** 显示编辑器 */
68
+ showEditor(page: PageData, $page: HTMLElement) {
69
+ this.closeEditor()
70
+
71
+ this.$editor = Utils.createElement(
72
+ `<div class="atk-page-edit-layer">
73
+ <div class="atk-page-main-actions">
74
+ <div class="atk-item atk-title-edit-btn">标题修改</div>
75
+ <div class="atk-item atk-key-edit-btn">KEY 变更</div>
76
+ <div class="atk-item atk-admin-only-btn"></div>
77
+ </div>
78
+ <div class="atk-page-actions">
79
+ <div class="atk-item atk-sync-btn">
80
+ <i class="atk-icon atk-icon-sync"></i>
81
+ </div>
82
+ <div class="atk-item atk-del-btn">
83
+ <i class="atk-icon atk-icon-del"></i>
84
+ </div>
85
+ <div class="atk-item atk-close-btn">
86
+ <i class="atk-icon atk-icon-close"></i>
87
+ </div>
88
+ </div>
89
+ </div>`)
90
+ $page.prepend(this.$editor)
91
+
92
+ const $titleEditBtn = this.$editor.querySelector<HTMLElement>('.atk-title-edit-btn')!
93
+ const $keyEditBtn = this.$editor.querySelector<HTMLElement>('.atk-key-edit-btn')!
94
+ const $adminOnlyBtn = this.$editor.querySelector<HTMLElement>('.atk-admin-only-btn')!
95
+ const $syncBtn = this.$editor.querySelector<HTMLElement>('.atk-sync-btn')!
96
+ const $delBtn = this.$editor.querySelector<HTMLElement>('.atk-del-btn')!
97
+ const $closeBtn = this.$editor.querySelector<HTMLElement>('.atk-close-btn')!
98
+ const showLoading = () => { Ui.showLoading(this.$editor!) }
99
+ const hideLoading = () => { Ui.hideLoading(this.$editor!) }
100
+ const showError = (msg: string) => { window.alert(msg) }
101
+
102
+ // 关闭编辑器
103
+ $closeBtn.onclick = () => this.closeEditor()
104
+
105
+ // 文本编辑
106
+ const openTextEditor = (key: string) => {
107
+ const textEditor = new ItemTextEditor({
108
+ initValue: page[key] || '',
109
+ onYes: async (val: string) => {
110
+ Ui.showLoading(textEditor.$el)
111
+ let p: PageData
112
+ try {
113
+ p = await new Api(this.ctx).pageEdit({ ...page, [key]: val })
114
+ } catch (err: any) {
115
+ showError(`修改失败:${err.msg || '未知错误'}`)
116
+ console.error(err)
117
+ return false
118
+ } finally { Ui.hideLoading(textEditor.$el) }
119
+ $page.replaceWith(this.renderPage(p))
120
+ return true
121
+ }
122
+ })
123
+ textEditor.appendTo(this.$editor!)
124
+ }
125
+
126
+ // 标题修改
127
+ $titleEditBtn.onclick = () => openTextEditor('title')
128
+
129
+ // Key 修改
130
+ $keyEditBtn.onclick = () => openTextEditor('key')
131
+
132
+ // 仅管理员可评
133
+ const adminOnlyActionBtn = new ActionBtn({
134
+ text: () => {
135
+ $adminOnlyBtn.classList.remove('atk-green', 'atk-yellow')
136
+ $adminOnlyBtn.classList.add(!page.admin_only ? 'atk-green' : 'atk-yellow')
137
+ return (!page.admin_only) ? '所有人可评' : '管理员可评'
138
+ }
139
+ }).appendTo($adminOnlyBtn)
140
+
141
+ $adminOnlyBtn.onclick = async () => {
142
+ showLoading()
143
+ let p: PageData
144
+ try {
145
+ p = await new Api(this.ctx).pageEdit({ ...page, admin_only: !page.admin_only })
146
+ } catch (err: any) {
147
+ showError(`修改失败:${err.msg || '未知错误'}`)
148
+ console.log(err)
149
+ return
150
+ } finally { hideLoading() }
151
+ page.admin_only = p.admin_only
152
+ adminOnlyActionBtn.updateText()
153
+ }
154
+
155
+ // 同步操作
156
+ $syncBtn.onclick = async () => {
157
+ showLoading()
158
+ let p: PageData
159
+ try {
160
+ p = await new Api(this.ctx).pageFetch(page.id)
161
+ } catch (err: any) {
162
+ showError(`同步失败:${err.msg || '未知错误'}`)
163
+ console.log(err)
164
+ return
165
+ } finally { hideLoading() }
166
+ $page.replaceWith(this.renderPage(p))
167
+ }
168
+
169
+ // 删除
170
+ $delBtn.onclick = () => {
171
+ const del = async () => {
172
+ showLoading()
173
+ try {
174
+ await new Api(this.ctx).pageDel(page.key, page.site_name)
175
+ } catch (err: any) {
176
+ console.log(err)
177
+ showError(`删除失败 ${String(err)}`)
178
+ return
179
+ } finally { hideLoading() }
180
+ $page.remove()
181
+ }
182
+ if (window.confirm(
183
+ `确认删除页面 "${page.title || page.key}"?将会删除所有相关数据`
184
+ )) del()
185
+ }
186
+ }
187
+
188
+ /** 关闭编辑器 */
189
+ public closeEditor() {
190
+ if (!this.$editor) return
191
+
192
+ this.$editor.remove()
193
+ }
194
+ }
@@ -0,0 +1,98 @@
1
+ import '@/style/site-list.less'
2
+
3
+ import Context from '@/context'
4
+ import * as Utils from '@/lib/utils'
5
+ import * as Ui from '@/lib/ui'
6
+ import { SiteData } from '~/types/artalk-data'
7
+ import Api from '~/src/api'
8
+
9
+ interface SiteListFloaterConf {
10
+ onSwitchSite: (siteName: string) => boolean|void
11
+ onClickSitesViewBtn: () => void
12
+ }
13
+
14
+ export default class SiteListFloater {
15
+ ctx: Context
16
+ conf: SiteListFloaterConf
17
+ $el: HTMLElement
18
+ sites: SiteData[] = []
19
+ $sites: HTMLElement
20
+
21
+ constructor(ctx: Context, conf: SiteListFloaterConf) {
22
+ this.ctx = ctx
23
+ this.conf = conf
24
+ this.$el = Utils.createElement(
25
+ `<div class="atk-site-list-floater" style="display: none;">
26
+ <div class="atk-sites"></div>
27
+ </div>`)
28
+ this.$sites = this.$el.querySelector('.atk-sites')!
29
+ }
30
+
31
+ /** 装载 */
32
+ public async load(selectedSite?: string) {
33
+ this.$sites.innerHTML = ''
34
+
35
+ const renderSiteItem = (siteName: string, siteLogo: string, siteTarget?: string, onclick?: Function) => {
36
+ const $site = Utils.createElement(
37
+ `<div class="atk-site-item">
38
+ <div class="atk-site-logo"></div>
39
+ <div class="atk-site-name"></div>
40
+ </div>`)
41
+ $site.onclick = !onclick ? () => this.switch(siteTarget || siteName) : () => onclick()
42
+ $site.setAttribute('data-name', siteTarget || siteName)
43
+ const $siteLogo = $site.querySelector<HTMLElement>('.atk-site-logo')!
44
+ const $siteName = $site.querySelector<HTMLElement>('.atk-site-name')!
45
+ $siteLogo.innerText = siteLogo
46
+ $siteName.innerText = siteName
47
+ if (selectedSite === (siteTarget || siteName)) $site.classList.add('atk-active')
48
+ this.$sites.append($site)
49
+ }
50
+
51
+ renderSiteItem('所有站点', '_', '__ATK_SITE_ALL')
52
+
53
+ const sites = await new Api(this.ctx).siteGet()
54
+ sites.forEach((site) => {
55
+ renderSiteItem(site.name, site.name.substr(0, 1))
56
+ })
57
+
58
+ renderSiteItem('站点管理', '+', '', () => { this.conf.onClickSitesViewBtn();this.hide() })
59
+ }
60
+
61
+ /** 切换站点 */
62
+ private switch(siteName: string) {
63
+ if (this.conf.onSwitchSite(siteName) === false) { return }
64
+
65
+ // set active
66
+ this.$sites.querySelectorAll('.atk-site-item').forEach(e => {
67
+ if (e.getAttribute('data-name') !== siteName) {
68
+ e.classList.remove('atk-active')
69
+ } else {
70
+ e.classList.add('atk-active')
71
+ }
72
+ })
73
+
74
+ this.hide()
75
+ }
76
+
77
+ private outsideChecker?: (evt: MouseEvent) => void // outside checker
78
+
79
+ /** 显示 */
80
+ public show($trigger?: HTMLElement) {
81
+ this.$el.style.display = ''
82
+
83
+ // 点击外部隐藏
84
+ if ($trigger) {
85
+ this.outsideChecker = (evt: MouseEvent) => {
86
+ const isClickInside = $trigger.contains(evt.target as any)||this.$el.contains(evt.target as any)
87
+ if (!isClickInside) { this.hide() }
88
+ }
89
+ document.addEventListener('click', this.outsideChecker)
90
+ }
91
+ }
92
+
93
+ /** 隐藏 */
94
+ public hide() {
95
+ this.$el.style.display = 'none'
96
+ if (this.outsideChecker) document.removeEventListener('click', this.outsideChecker)
97
+ }
98
+ }