artalk 2.1.3 → 2.1.6

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 (62) hide show
  1. package/CNAME +1 -0
  2. package/dist/Artalk.css +1 -1
  3. package/dist/Artalk.es.js +509 -1571
  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 -32
  8. package/postcss.config.js +5 -0
  9. package/src/api/index.ts +16 -3
  10. package/src/api/request.ts +4 -2
  11. package/src/artalk.ts +30 -8
  12. package/src/components/action-btn.ts +1 -1
  13. package/src/components/comment.ts +88 -3
  14. package/src/components/editor-plugs/emoticons-plug.less +21 -11
  15. package/src/components/editor-plugs/emoticons-plug.ts +18 -18
  16. package/src/components/editor-plugs/preview-plug.less +1 -1
  17. package/src/components/editor.ts +15 -20
  18. package/src/components/html/comment.html +6 -13
  19. package/src/components/html/editor.html +8 -9
  20. package/src/components/html/list.html +1 -1
  21. package/src/components/html/sidebar-layer.html +8 -0
  22. package/src/components/list-lite.ts +203 -299
  23. package/src/components/list.ts +11 -9
  24. package/src/components/pagination.ts +2 -2
  25. package/src/components/read-more-btn.ts +27 -4
  26. package/src/components/sidebar-layer.ts +143 -0
  27. package/src/defaults.ts +2 -1
  28. package/src/lib/detect.js +3 -1
  29. package/src/lib/utils.ts +27 -1
  30. package/src/style/_color.less +19 -23
  31. package/src/style/_extend.less +0 -80
  32. package/src/style/_variables.less +0 -24
  33. package/src/style/comment.less +191 -197
  34. package/src/style/editor.less +69 -94
  35. package/src/style/list.less +13 -13
  36. package/src/style/main.less +32 -32
  37. package/src/style/pagination.less +6 -6
  38. package/src/style/sidebar-layer.less +92 -0
  39. package/types/artalk-config.d.ts +4 -1
  40. package/types/artalk-data.d.ts +2 -2
  41. package/vite-common.config.ts +15 -0
  42. package/vite-demo.config.ts +14 -0
  43. package/vite.config.ts +36 -0
  44. package/LICENSE +0 -674
  45. package/README.md +0 -84
  46. package/src/components/admin/page-list.ts +0 -194
  47. package/src/components/admin/site-list-floater.ts +0 -98
  48. package/src/components/admin/site-list.ts +0 -289
  49. package/src/components/html/sidebar.html +0 -22
  50. package/src/components/item-text-editor.ts +0 -136
  51. package/src/components/sidebar-view.ts +0 -26
  52. package/src/components/sidebar-views/comments-view.ts +0 -74
  53. package/src/components/sidebar-views/pages-view.ts +0 -61
  54. package/src/components/sidebar-views/setting-view.ts +0 -21
  55. package/src/components/sidebar-views/sites-view.ts +0 -36
  56. package/src/components/sidebar-views/transfer-view.ts +0 -218
  57. package/src/components/sidebar.ts +0 -293
  58. package/src/lib/md5.js +0 -6
  59. package/src/style/item-text-editor.less +0 -35
  60. package/src/style/page-list.less +0 -50
  61. package/src/style/sidebar.less +0 -420
  62. package/src/style/site-list.less +0 -208
package/README.md DELETED
@@ -1,84 +0,0 @@
1
- <p align="center">
2
- <img src="https://user-images.githubusercontent.com/22412567/137740516-d9e97af0-fb3b-4dab-b331-671a9a2a3a63.png" alt="Artalk" width="100%">
3
- </p>
4
-
5
- # Artalk
6
-
7
- [![](https://img.shields.io/npm/v/artalk.svg?style=flat-square)](https://www.npmjs.com/package/artalk)
8
- [![](https://img.shields.io/npm/dt/artalk.svg?style=flat-square)](https://www.npmjs.com/package/artalk)
9
- [![](https://img.shields.io/github/last-commit/ArtalkJS/Artalk/master.svg?style=flat-square)](https://github.com/ArtalkJS/Artalk/commits/master)
10
- [![](https://img.shields.io/github/issues-raw/ArtalkJS/Artalk.svg?style=flat-square)](https://github.com/ArtalkJS/Artalk/issues)
11
- [![](https://img.shields.io/github/issues-pr-raw/ArtalkJS/Artalk.svg?style=flat-square)](https://github.com/ArtalkJS/Artalk/pulls)
12
- [![](https://img.shields.io/travis/com/ArtalkJS/Artalk?style=flat-square)](https://travis-ci.com/ArtalkJS/Artalk)
13
- [![](https://img.shields.io/github/license/ArtalkJS/Artalk.svg?style=flat-square)](./LICENSE)
14
-
15
- > 🌌 一款简洁的自托管评论系统 | A Selfhosted Comment System.
16
-
17
- 前往:[“**官方文档**”](https://artalk.js.org)
18
-
19
- ---
20
-
21
- - 轻量 (~30kB gzipped)
22
- - 自托管 (隐私安全)
23
- - 简单易上手 (防秃顶)
24
- - Markdown 语法 (通用性)
25
- - Golang 后端 (易部署 / 跨平台)
26
-
27
- ## 特性
28
-
29
- - 表 情 包 :统一管理 / 链接替换
30
- - 通知中心:红点标记 / 已读记录
31
- - 身份验证:徽标自定义 / 密码验证
32
- - 站点隔离:多站点管理 / 站点管理员用户分配
33
- - 评论审核:反垃圾检测 / 验证码 / 提交频率限制
34
- - 邮件提醒:模版自定义 / 多管理员通知
35
- - 树洞模式:仅自己可见 / 说说功能
36
- - 页面管理:标题可显示 / 快速跳转
37
- - 无限层级:可切换为 “平铺模式”
38
- - 评论投票:赞同还是反对评论
39
- - 异步处理:邮件发送无需等待
40
- - 滚动加载:评论内容分页处置
41
- - 自动保存:用户输入防丢功能
42
- - 自动填充:用户链接自动填充
43
- - 暗黑模式:防止眼部疾病伤害
44
- - 评论折叠:这个不打算给你康
45
- - 数据备份:防止评论数据丢失
46
- - 数据迁移:在不同评论系统之间来回切换
47
- - 一页多评:一页多个评论区(似乎没啥用
48
- - TypeScript & Vanilla:纯天然无添加 / 无需依赖
49
- - [Vite](https://github.com/vitejs/vite):开发者的极致体验
50
-
51
- ## 食用方针
52
-
53
- 前往:[“**文档 · 部署**”](https://artalk.js.org/guide/deploy.html)
54
-
55
- ```sh
56
- $ yarn add artalk
57
- ```
58
-
59
- ```ts
60
- import Artalk from 'artalk'
61
-
62
- new Artalk({
63
- el: '#Comments',
64
- pageKey: '<页面链接>',
65
- pageTitle: '<页面标题>',
66
- server: '<后端地址>',
67
- site: '<站点名称>',
68
- })
69
- ```
70
-
71
- ## 特别致谢
72
-
73
- 感谢社区提供的帮助与反馈,若有好的建议与意见欢迎前往 [ISSUES](https://github.com/ArtalkJS/Artalk/issues) 随时告知。
74
-
75
- ## Stargazers over time
76
-
77
- [![Stargazers over time](https://starchart.cc/ArtalkJS/Artalk.svg)](https://starchart.cc/ArtalkJS/Artalk)
78
-
79
- ## License
80
- [GPL-3.0](./LICENSE)
81
-
82
- [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FArtalkJS%2FArtalk.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2FArtalkJS%2FArtalk?ref=badge_shield)
83
-
84
- [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FArtalkJS%2FArtalk.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2FArtalkJS%2FArtalk?ref=badge_large)
@@ -1,194 +0,0 @@
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
- }
@@ -1,98 +0,0 @@
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
- }
@@ -1,289 +0,0 @@
1
- import '@/style/site-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 { SiteData } from '~/types/artalk-data'
8
- import ItemTextEditor from '../item-text-editor'
9
- import Api from '~/src/api'
10
-
11
- export default class SiteList extends Component {
12
- sites: SiteData[] = []
13
-
14
- $header: HTMLElement
15
- $headerTitle: HTMLElement
16
- $headerActions: HTMLElement
17
- $rowsWrap: HTMLElement
18
-
19
- $editor?: HTMLElement
20
- activeSite: string = ''
21
-
22
- $add?: HTMLElement
23
-
24
- constructor(ctx: Context) {
25
- super(ctx)
26
-
27
- this.$el = Utils.createElement(
28
- `<div class="atk-site-list">
29
- <div class="atk-header">
30
- <div class="atk-title"></div>
31
- <div class="atk-actions">
32
- <div class="atk-item atk-site-add-btn"><i class="atk-icon atk-icon-plus"></i></div>
33
- </div>
34
- </div>
35
- <div class="atk-site-rows-wrap"></div>
36
- </div>`)
37
-
38
- this.$header = this.$el.querySelector('.atk-header')!
39
- this.$headerTitle = this.$header.querySelector('.atk-title')!
40
- this.$headerActions = this.$header.querySelector('.atk-actions')!
41
- this.$rowsWrap = this.$el.querySelector('.atk-site-rows-wrap')!
42
-
43
- // 标题
44
- this.$headerTitle.innerText = `共 0 个站点`
45
-
46
- // 新建站点按钮
47
- const $addBtn = this.$headerActions.querySelector<HTMLElement>('.atk-site-add-btn')!
48
- $addBtn.onclick = () => {
49
- this.closeEditor()
50
- this.showAdd()
51
- }
52
- }
53
-
54
- public loadSites(sites: SiteData[]) {
55
- this.sites = sites
56
- this.activeSite = ''
57
- this.$rowsWrap.innerHTML = ''
58
- this.$headerTitle.innerText = `共 0 个站点`
59
-
60
- let $row: HTMLElement
61
-
62
- for (let i = 0; i < sites.length; i++) {
63
- const site = sites[i]
64
-
65
- if (i % 4 === 0) { // 每 4 个 item 为一行
66
- $row = Utils.createElement('<div class="atk-site-row">')
67
- this.$rowsWrap.append($row)
68
- }
69
-
70
- // 创建 site item
71
- const $site = this.renderSite(site, $row!)
72
- $row!.append($site)
73
- }
74
-
75
- this.$headerTitle.innerText = `共 ${sites.length} 个站点`
76
- }
77
-
78
- public renderSite(site: SiteData, $row: HTMLElement) {
79
- const $site = Utils.createElement(
80
- `<div class="atk-site-item">
81
- <div class="atk-site-logo"></div>
82
- <div class="atk-site-name"></div>
83
- </div>`)
84
-
85
- const $siteLogo = $site.querySelector<HTMLElement>('.atk-site-logo')!
86
- const $siteName = $site.querySelector<HTMLElement>('.atk-site-name')!
87
- const setActive = () => { $site.classList.add('atk-active') }
88
-
89
- $siteLogo.innerText = site.name.substr(0, 1)
90
- $siteName.innerText = site.name
91
-
92
- // click
93
- $site.onclick = () => {
94
- this.closeEditor()
95
- this.closeAdd()
96
- setActive()
97
- this.showEditor(site, $site, $row)
98
- }
99
-
100
- if (this.activeSite === site.name) { setActive() }
101
-
102
- return $site
103
- }
104
-
105
- public showEditor(site: SiteData, $site: HTMLElement, $row: HTMLElement) {
106
- this.activeSite = site.name
107
- this.$editor = Utils.createElement(`
108
- <div class="atk-site-edit">
109
- <div class="atk-header">
110
- <div class="atk-site-info">
111
- <span class="atk-site-name"></span>
112
- <span class="atk-site-urls"></span>
113
- </div>
114
- <div class="atk-close-btn">
115
- <i class="atk-icon atk-icon-close"></i>
116
- </div>
117
- </div>
118
- <div class="atk-main">
119
- <div class="atk-site-text-actions">
120
- <div class="atk-item atk-rename-btn">重命名</div>
121
- <div class="atk-item atk-edit-url-btn">修改 URL</div>
122
- <!--<div class="atk-item atk-export-btn">导出</div>
123
- <div class="atk-item atk-import-btn">导入</div>-->
124
- </div>
125
- <div class="atk-site-btn-actions">
126
- <div class="atk-item atk-del-btn">
127
- <i class="atk-icon atk-icon-del"></i>
128
- </div>
129
- </div>
130
- </div>
131
- </div>`)
132
-
133
- // 插入
134
- $row.before(this.$editor)
135
-
136
- // header
137
- const $siteName = this.$editor.querySelector<HTMLElement>('.atk-site-name')!
138
- const $siteUrls = this.$editor.querySelector<HTMLElement>('.atk-site-urls')!
139
- const $closeBtn = this.$editor.querySelector<HTMLElement>('.atk-close-btn')!
140
- $closeBtn.onclick = () => this.closeEditor()
141
-
142
- const update = (s: SiteData) => {
143
- site = s
144
- $siteName.innerText = site.name
145
- $siteName.onclick = () => { if (site.first_url) window.open(site.first_url) }
146
- $siteUrls.innerHTML = ''
147
- site.urls?.forEach(u => {
148
- const $item = Utils.createElement('<span class="atk-url-item"></span>')
149
- $siteUrls.append($item)
150
- $item.innerText = (u || '').replace(/\/$/, '')
151
- $item.onclick = () => { window.open(u) }
152
- })
153
- }
154
- update(site)
155
-
156
- // actions
157
- const $main = this.$editor.querySelector<HTMLElement>('.atk-main')!
158
- const $actions = this.$editor.querySelector<HTMLElement>('.atk-site-text-actions')!
159
- const $renameBtn = $actions.querySelector<HTMLElement>('.atk-rename-btn')!
160
- const $editUrlBtn = $actions.querySelector<HTMLElement>('.atk-edit-url-btn')!
161
- // const $exportBtn = $actions.querySelector<HTMLElement>('.atk-export-btn')!
162
- // const $importBtn = $actions.querySelector<HTMLElement>(`.atk-import-btn`)!
163
- const $delBtn = this.$editor.querySelector<HTMLElement>('.atk-del-btn')!
164
- const showLoading = () => { Ui.showLoading(this.$editor!) }
165
- const hideLoading = () => { Ui.hideLoading(this.$editor!) }
166
- const showError = (msg: string) => { window.alert(msg) }
167
-
168
- // 文本编辑
169
- const openTextEditor = (key: string) => {
170
- let initValue = site[key] || ''
171
- if (key === 'urls') initValue = site.urls_raw || ''
172
- const textEditor = new ItemTextEditor({
173
- initValue,
174
- onYes: async (val: string) => {
175
- Ui.showLoading(textEditor.$el)
176
- let s: SiteData
177
- try {
178
- s = await new Api(this.ctx).siteEdit({ ...site, [key]: val })
179
- } catch (err: any) {
180
- showError(`修改失败:${err.msg || '未知错误'}`)
181
- console.error(err)
182
- return false
183
- } finally { Ui.hideLoading(textEditor.$el) }
184
- $site.replaceWith(this.renderSite(s, $row))
185
- update(s)
186
- return true
187
- }
188
- })
189
- textEditor.appendTo($main)
190
- }
191
-
192
- // 重命名
193
- $renameBtn.onclick = () => openTextEditor('name')
194
-
195
- // 修改 URL
196
- $editUrlBtn.onclick = () => openTextEditor('urls')
197
-
198
- // 导出
199
- // $exportBtn.onclick = () => {}
200
-
201
- // // 导入
202
- // $importBtn.onclick = () => {}
203
-
204
- // 删除
205
- $delBtn.onclick = () => {
206
- const del = async () => {
207
- showLoading()
208
- try {
209
- await new Api(this.ctx).siteDel(site.id, true)
210
- } catch (err: any) {
211
- console.log(err)
212
- showError(`删除失败 ${String(err)}`)
213
- return
214
- } finally { hideLoading() }
215
- this.closeEditor()
216
- $site.remove()
217
- this.sites = this.sites.filter(s => s.name !== site.name)
218
- }
219
- if (window.confirm(
220
- `确认删除站点 "${site.name}"?将会删除所有相关数据`
221
- )) del()
222
- }
223
- }
224
-
225
- public closeEditor() {
226
- if (!this.$editor) return
227
-
228
- this.$editor.remove()
229
- this.$rowsWrap.querySelectorAll('.atk-site-item').forEach((e) => e.classList.remove('atk-active'))
230
- this.activeSite = ''
231
- }
232
-
233
- public showAdd() {
234
- this.closeAdd()
235
-
236
- this.$add = Utils.createElement(`
237
- <div class="atk-site-add">
238
- <div class="atk-header">
239
- <div class="atk-title">新增站点</div>
240
- <div class="atk-close-btn">
241
- <i class="atk-icon atk-icon-close"></i>
242
- </div>
243
- </div>
244
- <div class="atk-form">
245
- <input type="text" name="AtkSiteName" placeholder="站点名称" autocomplete="off">
246
- <input type="text" name="AtkSiteUrls" placeholder="站点 URL(多个用逗号隔开)" autocomplete="off">
247
- <button class="atk-btn" name="AtkSubmit">创建</button>
248
- </div>
249
- </div>`)
250
- this.$header.after(this.$add)
251
-
252
- const $closeBtn = this.$add.querySelector<HTMLElement>('.atk-close-btn')!
253
- $closeBtn.onclick = () => this.closeAdd()
254
-
255
- const $siteName = this.$add.querySelector<HTMLInputElement>('[name="AtkSiteName"]')!
256
- const $siteUrls = this.$add.querySelector<HTMLInputElement>('[name="AtkSiteUrls"]')!
257
- const $submitBtn = this.$add.querySelector<HTMLButtonElement>('[name="AtkSubmit"]')!
258
-
259
- $submitBtn.onclick = async () => {
260
- const siteName = $siteName.value.trim()
261
- const siteUrls = $siteUrls.value.trim()
262
-
263
- if (siteName === '') { $siteName.focus(); return }
264
-
265
- Ui.showLoading(this.$add!)
266
- let s: SiteData
267
- try {
268
- s = await new Api(this.ctx).siteAdd(siteName, siteUrls)
269
- } catch (err: any) {
270
- window.alert(`创建失败:${err.msg || ''}`)
271
- console.error(err)
272
- return
273
- } finally { Ui.hideLoading(this.$add!) }
274
-
275
- this.sites.push(s)
276
- this.loadSites(this.sites)
277
- this.closeAdd()
278
- }
279
-
280
- // 回车键提交
281
- const keyDown = (evt: KeyboardEvent) => { if (evt.key === 'Enter') { $submitBtn.click() } }
282
- $siteName.onkeyup = (evt) => keyDown(evt)
283
- $siteUrls.onkeyup = (evt) => keyDown(evt)
284
- }
285
-
286
- public closeAdd() {
287
- this.$add?.remove()
288
- }
289
- }
@@ -1,22 +0,0 @@
1
- <div class="atk-sidebar">
2
- <div class="atk-sidebar-inner">
3
- <div class="atk-sidebar-header">
4
- <span class="atk-avatar">
5
- <span class="atk-site-logo"></span>
6
- </span>
7
- <span class="atk-menu">
8
- <span class="atk-item atk-active atk-sidebar-title">控制中心</span>
9
- </span>
10
- <div class="atk-sidebar-close"><i class="atk-icon atk-icon-close"></i></div>
11
- </div>
12
- <div class="atk-sidebar-nav">
13
- <div class="akt-curt-view-btn">
14
- <div class="atk-icon"><span></span><span></span><span></span></div>
15
- <div class="atk-text"></div>
16
- </div>
17
- <div class="atk-tabs"></div>
18
- <div class="atk-tabs atk-views" style="display: none;"></div>
19
- </div>
20
- <div class="atk-sidebar-view-wrap"></div>
21
- </div>
22
- </div>