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
package/index.html ADDED
@@ -0,0 +1,143 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Artalk DEMO</title>
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, target-densitydpi=device-dpi" />
7
+ <style>
8
+ * { box-sizing: border-box; }
9
+ body { padding:45px 10px;margin:0;background:#f4f4f4 }
10
+ body.dark-mode { background:#000; }
11
+ .card { margin:0 auto;max-width:700px;position:relative;overflow:hidden;background:#FFF;border-radius:4px;padding:20px;border: 1px solid transparent; }
12
+ body.dark-mode .card { background: #1e2223;border: 1px solid #2d3235; }
13
+ .card:not(:last-child) { margin-bottom:10px; }
14
+ .card.billboard { text-align: center; }
15
+ .title > a { display:block;text-decoration:none;color:#0083ff;font-size:35px;margin-top:0;margin-bottom:10px; }
16
+ .title > a:hover { margin-left:0 }
17
+ .title > a > span { opacity:0 }
18
+ .title > a:hover>span { opacity:1 }
19
+ .desc { font-size:12px;color:#757e91 }
20
+ .social { margin-top:20px }
21
+ .social > a:not(:last-child) { margin-right:2px }
22
+ @media only screen and (max-width: 768px) {
23
+ body { padding: 0;background: #FFF; }
24
+ .card.billboard { padding-top: 20px; }
25
+ .card { width: 100%;padding: 5px; }
26
+ }
27
+ .float-btn { user-select: none;bottom: 25px;display: block;cursor: pointer;text-align: center;width: 60px;height: 37px;line-height: 37px;font-size: 13px;color: #757e91;background: #fff;opacity: 0.8;border-radius: 4px;border: 1px solid #eceff2;position: fixed; }
28
+ .to-night { z-index: 99999;left: 25px;background: #1e2223;color: #fff; }
29
+ body.dark-mode .to-night { background: #fff;border: 1px solid #2d3235;color: #0083ff; }
30
+ .to-top { right: 25px; }
31
+ </style>
32
+ </head>
33
+ <body>
34
+ <div class="card billboard atk-fade-in">
35
+ <div class="title"><a href="https://github.com/ArtalkJS/Artalk" target="_blank"><span>『 </span>Artalk<span> 』</span></a></div>
36
+ <div class="desc">一款简洁的自托管评论系统 <span id="ArtalkPV"></span></div>
37
+ <div class="social">
38
+ <a href="https://www.npmjs.com/package/artalk" target="_blank"><img src="https://img.shields.io/npm/v/artalk.svg?style=flat-square"></a>
39
+ <a href="https://www.npmjs.com/package/artalk" target="_blank"><img src="https://img.shields.io/npm/dt/artalk.svg?style=flat-square"></a>
40
+ <a href="https://github.com/ArtalkJS/Artalk/commits/master" target="_blank"><img src="https://img.shields.io/github/last-commit/ArtalkJS/Artalk?style=flat-square"></a>
41
+ <a href="https://github.com/ArtalkJS/Artalk/blob/master/LICENSE" target="_blank"><img src="https://img.shields.io/github/license/ArtalkJS/Artalk.svg?style=flat-square"></a>
42
+ <a href="https://github.com/ArtalkJS/Artalk" target="_blank"><img src="https://img.shields.io/github/stars/ArtalkJS/Artalk?style=flat-square"></a>
43
+ </div>
44
+ </div>
45
+ <div class="card atk-fade-in">
46
+ <div id="comments"></div>
47
+ </div>
48
+ <div class="float-btn to-night atk-fade-in">Blink</div>
49
+ <div class="float-btn to-top atk-fade-in" style="display: none;">TOP</div>
50
+
51
+ <script type="module">
52
+ import Artalk from './src/main'
53
+ var artalk = new Artalk({
54
+ el: '#comments',
55
+ pageKey: 'https://artalk.js.org/',
56
+ pageTitle: 'Artalk Home',
57
+ server: (import.meta.env.MODE === 'development') ? 'http://localhost:23366/api' : 'https://artalk.qwqaq.com/api',
58
+ site: 'ArtalkDemo',
59
+ });
60
+
61
+ window.artalk = artalk;
62
+
63
+ ;(function () {
64
+ const toTopElem = document.querySelector('.to-top')
65
+ toTopElem.addEventListener('click', () => {
66
+ window.scrollTo(0, 0)
67
+ })
68
+
69
+ checkShowToTop()
70
+ document.addEventListener('scroll', () => {
71
+ checkShowToTop()
72
+ })
73
+
74
+ function checkShowToTop() {
75
+ const scrollTop = window.scrollY
76
+ const whereShow = window.screen.height
77
+ const isNeedShow = (scrollTop > 0) && (scrollTop > whereShow)
78
+
79
+ if (isNeedShow) {
80
+ toTopElem.style.display = ''
81
+ } else {
82
+ toTopElem.style.display = 'none'
83
+ }
84
+ }
85
+ })();
86
+
87
+ ;(function () {
88
+ // @Refer: https://stackoverflow.com/questions/50840168/how-to-detect-if-the-os-is-in-dark-mode-in-browsers
89
+ let darkMode = false
90
+
91
+ if (getLS() == "true") {
92
+ darkMode = true;
93
+ } else {
94
+ try {
95
+ if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
96
+ darkMode = true;
97
+ }
98
+ } catch {}
99
+ }
100
+
101
+ function getLS() {
102
+ return localStorage.getItem("dark_mode_on") || null
103
+ }
104
+
105
+ function setLS(val) {
106
+ return localStorage.setItem("dark_mode_on", String(val))
107
+ }
108
+
109
+ function checkAndApplyDarkMode () {
110
+ artalk.setDarkMode(darkMode)
111
+ if (darkMode) {
112
+ document.body.classList.add('dark-mode')
113
+ } else {
114
+ document.body.classList.remove('dark-mode')
115
+ }
116
+ }
117
+
118
+ checkAndApplyDarkMode()
119
+
120
+ const toNightElem = document.querySelector('.to-night')
121
+ toNightElem.addEventListener('click', () => {
122
+ darkMode = !darkMode
123
+ setLS(darkMode)
124
+ checkAndApplyDarkMode()
125
+ })
126
+
127
+ // Listen
128
+ try {
129
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
130
+ const newColorScheme = e.matches ? "dark" : "light";
131
+ if (newColorScheme == "dark") {
132
+ darkMode = true
133
+ } else {
134
+ darkMode = false
135
+ }
136
+
137
+ checkAndApplyDarkMode()
138
+ });
139
+ } catch {}
140
+ })()
141
+ </script>
142
+ </body>
143
+ </html>
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "artalk",
3
- "version": "2.1.4",
3
+ "version": "2.1.7",
4
+ "license": "GPL-3.0",
4
5
  "description": "A Selfhosted Comment System",
5
6
  "keywords": [
6
7
  "comment-system",
@@ -9,24 +10,18 @@
9
10
  "comments",
10
11
  "blog"
11
12
  ],
12
- "homepage": "https://artalk.js.org",
13
- "bugs": "https://github.com/ArtalkJS/Artalk/issue",
14
- "license": "GPL-3.0",
15
13
  "repository": {
16
14
  "type": "git",
17
- "url": "https://github.com/ArtalkJS/Artalk.git"
15
+ "url": "https://github.com/ArtalkJS/Artalk.git",
16
+ "directory": "packages/artalk"
18
17
  },
18
+ "homepage": "https://artalk.js.org",
19
+ "bugs": "https://github.com/ArtalkJS/Artalk/issue",
19
20
  "main": "./dist/Artalk.js",
20
21
  "unpkg": "./dist/Artalk.iife.js",
21
22
  "jsdelivr": "./dist/Artalk.iife.js",
22
23
  "module": "./dist/Artalk.es.js",
23
24
  "types": "./types/index.d.ts",
24
- "exports": {
25
- ".": {
26
- "import": "./dist/Artalk.es.js",
27
- "require": "./dist/Artalk.js"
28
- }
29
- },
30
25
  "scripts": {
31
26
  "dev": "vite",
32
27
  "build": "yarn lint && vite build",
@@ -38,27 +33,11 @@
38
33
  "lint": "eslint --ext .js,.ts src"
39
34
  },
40
35
  "devDependencies": {
41
- "@rollup/plugin-dynamic-import-vars": "^1.4.1",
42
- "@types/marked": "^4.0.1",
43
- "@types/node": "^16.11.7",
44
- "@typescript-eslint/eslint-plugin": "^5.4.0",
45
- "@typescript-eslint/parser": "^5.4.0",
46
- "autoprefixer": "^10.4.0",
47
- "eslint": "^8.2.0",
48
- "eslint-config-airbnb-base": "15.0.0",
49
- "eslint-config-airbnb-typescript": "^16.0.0",
50
- "eslint-config-prettier": "^8.3.0",
51
- "eslint-import-resolver-typescript": "^2.5.0",
52
- "eslint-plugin-import": "^2.25.2",
53
- "less": "^4.1.2",
54
- "prettier": "^2.4.1",
55
- "typescript": "^4.5.2",
56
- "vite": "^2.6.14",
57
- "vite-tsconfig-paths": "^3.3.17"
36
+ "@types/marked": "^4.0.3"
58
37
  },
59
38
  "dependencies": {
60
39
  "hanabi": "^0.4.0",
61
40
  "insane": "^2.6.2",
62
- "marked": "^4.0.3"
41
+ "marked": "^4.0.14"
63
42
  }
64
43
  }
@@ -0,0 +1,5 @@
1
+ module.exports = {
2
+ plugins: [
3
+ require('autoprefixer')
4
+ ]
5
+ }
package/src/api/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import Context from '../context'
2
2
  import { CommentData, ListData, UserData, PageData, SiteData, NotifyData } from '~/types/artalk-data'
3
3
  import { Fetch, ToFormData, POST, GET } from './request'
4
+ import * as Utils from '../lib/utils'
4
5
 
5
6
  export default class Api {
6
7
  private ctx: Context
@@ -44,6 +45,7 @@ export default class Api {
44
45
  content: comment.content,
45
46
  rid: comment.rid,
46
47
  page_key: comment.page_key,
48
+ ua: await Utils.getCorrectUserAgent(), // 需要后端支持,获取修正后的 UA (针对 Win11)
47
49
  }
48
50
 
49
51
  if (comment.page_title) params.page_title = comment.page_title
@@ -250,6 +252,18 @@ export default class Api {
250
252
  return POST(this.ctx, `${this.baseURL}/mark-read`, params)
251
253
  }
252
254
 
255
+ /** PV */
256
+ public async pv() {
257
+ const params: any = {
258
+ site_name: this.ctx.conf.site || '',
259
+ page_key: this.ctx.conf.pageKey || '',
260
+ page_title: this.ctx.conf.pageTitle || ''
261
+ }
262
+
263
+ const p = await POST<any>(this.ctx, `${this.baseURL}/pv`, params)
264
+ return p.pv as number
265
+ }
266
+
253
267
  // ============================
254
268
  // 验证码
255
269
  // ============================
@@ -21,7 +21,9 @@ export async function Fetch(ctx: Context, input: RequestInfo, init: RequestInit,
21
21
  resp = await timeoutPromise(timeout || ctx.conf.reqTimeout || 15000, fetch(input, init))
22
22
  }
23
23
 
24
- if (!resp.ok && resp.status !== 401) throw new Error(`请求响应 ${resp.status}`)
24
+ const noAccessCodes = [401, 400]
25
+
26
+ if (!resp.ok && !noAccessCodes.includes(resp.status)) throw new Error(`请求响应 ${resp.status}`)
25
27
 
26
28
  // 解析获取响应的 json
27
29
  let json: any = await resp.json()
@@ -49,7 +51,7 @@ export async function Fetch(ctx: Context, input: RequestInfo, init: RequestInit,
49
51
  }
50
52
  })
51
53
  }))
52
- } else if ((json.data && json.data.need_login) || resp.status === 401) {
54
+ } else if ((json.data && json.data.need_login) || noAccessCodes.includes(resp.status)) {
53
55
  // 请求需要管理员权限
54
56
  json = await (new Promise<any>((resolve, reject) => {
55
57
  ctx.trigger('checker-admin', {
package/src/artalk.ts CHANGED
@@ -7,10 +7,11 @@ import defaults from './defaults'
7
7
  import CheckerLauncher from './lib/checker'
8
8
  import Editor from './components/editor'
9
9
  import List from './components/list'
10
- import Sidebar from './components/sidebar'
10
+ import SidebarLayer from './components/sidebar-layer'
11
11
 
12
12
  import { GetLayerWrap } from './components/layer'
13
13
  import { EventPayloadMap, Handler } from '~/types/event'
14
+ import Api from './api'
14
15
 
15
16
  /**
16
17
  * Artalk
@@ -26,7 +27,7 @@ export default class Artalk {
26
27
  public checkerLauncher: CheckerLauncher
27
28
  public editor: Editor
28
29
  public list: List
29
- public sidebar: Sidebar
30
+ public sidebarLayer: SidebarLayer
30
31
 
31
32
  constructor (customConf: ArtalkConfig) {
32
33
  // 配置
@@ -68,14 +69,17 @@ export default class Artalk {
68
69
  this.$root.appendChild(this.list.$el)
69
70
 
70
71
  // 侧边栏
71
- this.sidebar = new Sidebar(this.ctx)
72
- this.$root.appendChild(this.sidebar.$el)
72
+ this.sidebarLayer = new SidebarLayer(this.ctx)
73
+ this.$root.appendChild(this.sidebarLayer.$el)
73
74
 
74
75
  // 评论获取
75
76
  this.list.fetchComments(0)
76
77
 
77
78
  // 事件绑定初始化
78
79
  this.initEventBind()
80
+
81
+ // 其他
82
+ this.initPV()
79
83
  }
80
84
 
81
85
  /** 事件绑定 · 初始化 */
@@ -93,8 +97,12 @@ export default class Artalk {
93
97
 
94
98
  // for layer
95
99
  const { $wrap: $layerWrap } = GetLayerWrap(this.ctx)
96
- if ($layerWrap)
97
- $layerWrap.querySelectorAll<HTMLElement>(`[atk-only-admin-show]`).forEach(item => items.push(item))
100
+ if ($layerWrap) $layerWrap.querySelectorAll<HTMLElement>(`[atk-only-admin-show]`).forEach(item => items.push(item))
101
+
102
+ // for sidebar
103
+ // TODO: 这个其实应该写在 packages/artalk-sidebar 里面的
104
+ const $sidebarEl = document.querySelector<HTMLElement>('.atk-sidebar')
105
+ if ($sidebarEl) $sidebarEl.querySelectorAll<HTMLElement>(`[atk-only-admin-show]`).forEach(item => items.push(item))
98
106
 
99
107
  items.forEach(($item: HTMLElement) => {
100
108
  if (this.ctx.user.data.isAdmin)
@@ -143,6 +151,20 @@ export default class Artalk {
143
151
  this.initDarkMode()
144
152
  }
145
153
 
154
+ /** PV */
155
+ public async initPV() {
156
+ if (!this.conf.pvEl || !document.querySelector(this.conf.pvEl))
157
+ return
158
+
159
+ const $pv = document.querySelector<HTMLElement>(this.conf.pvEl)!
160
+ // $pv.innerText = '-' // 默认占位符
161
+ const pvNum = await new Api(this.ctx).pv()
162
+ if (Number.isNaN(Number(pvNum)))
163
+ return
164
+
165
+ $pv.innerText = String(pvNum)
166
+ }
167
+
146
168
  /** 监听事件 */
147
169
  public on<K extends keyof EventPayloadMap>(name: K, handler: Handler<EventPayloadMap[K]>) {
148
170
  this.ctx.on(name, handler, 'external')
@@ -136,6 +136,11 @@ export default class Comment extends Component {
136
136
  $badge.remove()
137
137
  }
138
138
 
139
+ if (this.data.is_pinned) {
140
+ const $pinnedBadge = Utils.createElement(`<span class="atk-item atk-pinned-badge">置顶</span>`) // 置顶徽章
141
+ $nick.insertAdjacentElement('afterend', $pinnedBadge)
142
+ }
143
+
139
144
  const $date = this.$el.querySelector<HTMLElement>('.atk-date')!
140
145
  $date.innerText = this.getDateFormatted()
141
146
  $date.setAttribute('data-atk-comment-date', String(+new Date(this.data.date)))
@@ -272,6 +277,16 @@ export default class Comment extends Component {
272
277
  delBtn.setClick(() => {
273
278
  this.adminDelete(delBtn)
274
279
  })
280
+
281
+ // 绑定置顶按钮事件
282
+ const pinnedBtn = new ActionBtn({
283
+ text: () => (this.data.is_pinned ? '取消置顶' : '置顶'),
284
+ adminOnly: true
285
+ })
286
+ pinnedBtn.appendTo(this.$actions)
287
+ pinnedBtn.setClick(() => {
288
+ this.adminEdit('pinned', pendingBtn)
289
+ })
275
290
  }
276
291
  //#endregion
277
292
 
@@ -404,9 +419,9 @@ export default class Comment extends Component {
404
419
  })
405
420
  }
406
421
 
407
- /** 管理员 - 评论折叠 */
408
- adminEdit(type: 'collapsed'|'pending', btnElem: ActionBtn) {
409
- if (btnElem.isLoading) return // 若正在折叠中
422
+ /** 管理员 - 评论状态修改 */
423
+ adminEdit(type: 'collapsed'|'pending'|'pinned', btnElem: ActionBtn) {
424
+ if (btnElem.isLoading) return // 若正在修改中
410
425
 
411
426
  btnElem.setLoading(true, '修改中...')
412
427
 
@@ -416,6 +431,8 @@ export default class Comment extends Component {
416
431
  modify.is_collapsed = !modify.is_collapsed
417
432
  } else if (type === 'pending') {
418
433
  modify.is_pending = !modify.is_pending
434
+ } else if (type === 'pinned') {
435
+ modify.is_pinned = !modify.is_pinned
419
436
  }
420
437
 
421
438
  new Api(this.ctx).commentEdit(modify).then((comment) => {
@@ -1,8 +1,10 @@
1
1
  <div class="atk-list">
2
2
  <div class="atk-list-header">
3
3
  <div class="atk-comment-count">
4
- <span class="atk-comment-count-num">0</span>
5
- 条评论
4
+ <div class="atk-text">
5
+ <span class="atk-comment-count-num">0</span>
6
+ 条评论
7
+ </div>
6
8
  </div>
7
9
  <div class="atk-right-action">
8
10
  <span data-action="admin-close-comment" class="atk-hide" atk-only-admin-show>关闭评论</span>
@@ -0,0 +1,8 @@
1
+ <div class="atk-sidebar-layer">
2
+ <div class="atk-sidebar-inner">
3
+ <div class="atk-sidebar-header">
4
+ <div class="atk-sidebar-close"><i class="atk-icon atk-icon-close"></i></div>
5
+ </div>
6
+ <div class="atk-sidebar-iframe-wrap"></div>
7
+ </div>
8
+ </div>
@@ -12,6 +12,8 @@ export default class List extends ListLite {
12
12
  private $closeCommentBtn!: HTMLElement
13
13
  private $openSidebarBtn!: HTMLElement
14
14
  private $unreadBadge!: HTMLElement
15
+ private $commentCount!: HTMLElement
16
+ private $dropdownWrap?: HTMLElement
15
17
 
16
18
  constructor (ctx: Context) {
17
19
  const el = Utils.createElement(ListHTML)
@@ -40,6 +42,10 @@ export default class List extends ListLite {
40
42
  // 操作按钮
41
43
  this.initListActionBtn()
42
44
 
45
+ // 下拉选择层
46
+ this.$commentCount = this.$el.querySelector('.atk-comment-count')!
47
+ this.initDropdown()
48
+
43
49
  // copyright
44
50
  this.$el.querySelector<HTMLElement>('.atk-copyright')!.innerHTML = `Powered By <a href="https://artalk.js.org" target="_blank" title="Artalk v${ARTALK_VERSION}">Artalk</a>`
45
51
 
@@ -171,4 +177,58 @@ export default class List extends ListLite {
171
177
  this.$unreadBadge.style.display = 'none'
172
178
  }
173
179
  }
180
+
181
+ /** 初始化选择下拉层 */
182
+ protected initDropdown() {
183
+ this.$dropdownWrap = this.$commentCount
184
+ this.$commentCount.classList.add('atk-dropdown-wrap')
185
+
186
+ // 插入图标
187
+ this.$commentsWrap.append(Utils.createElement(`<span class="atk-arrow-down-icon"></span>`))
188
+
189
+ const reloadUseParamsEditor = (func: (p: any) => void) => {
190
+ this.paramsEditor = (p) => { func(p) }
191
+ this.fetchComments(0)
192
+ }
193
+
194
+ // 下拉列表
195
+ const dropdownList = [
196
+ ['最新', () => { reloadUseParamsEditor(p => { p.sort_by = 'date_desc' }) }],
197
+ ['最热', () => { reloadUseParamsEditor(p => { p.sort_by = 'vote' }) }],
198
+ ['最早', () => { reloadUseParamsEditor(p => { p.sort_by = 'date_asc' }) }],
199
+ ['作者', () => { reloadUseParamsEditor(p => { p.view_only_admin = true }) }],
200
+ ]
201
+
202
+ // 列表项点击事件
203
+ let curtActive = 0 // 当前选中
204
+ const onItemClick = (i: number, $item: HTMLElement, name: string, action: Function) => {
205
+ action()
206
+
207
+ // set active
208
+ curtActive = i
209
+ $dropdown.querySelectorAll('.active').forEach((e) => { e.classList.remove('active') })
210
+ $item.classList.add('active')
211
+
212
+ // 关闭层 (临时消失,取消 :hover)
213
+ $dropdown.style.display = 'none'
214
+ setTimeout(() => { $dropdown.style.display = '' }, 80)
215
+ }
216
+
217
+ // 生成列表元素
218
+ const $dropdown = Utils.createElement(`<ul class="atk-dropdown atk-fade-in"></ul>`)
219
+ dropdownList.forEach((item, i) => {
220
+ const name = item[0] as string
221
+ const action = item[1] as Function
222
+
223
+ const $item = Utils.createElement(`<li class="atk-dropdown-item"><span></span></li>`)
224
+ const $link = $item.querySelector<HTMLElement>('span')!
225
+ $link.innerText = name
226
+ $link.onclick = () => { onItemClick(i, $item, name, action) }
227
+ $dropdown.append($item)
228
+
229
+ if (i === curtActive) $item.classList.add('active') // 默认选中项
230
+ })
231
+
232
+ this.$dropdownWrap.append($dropdown)
233
+ }
174
234
  }
@@ -0,0 +1,143 @@
1
+ import '../style/sidebar-layer.less'
2
+
3
+ import Context from '@/context'
4
+ import Component from '@/lib/component'
5
+ import * as Utils from '@/lib/utils'
6
+ import SidebarHTML from './html/sidebar-layer.html?raw'
7
+ import * as Ui from '@/lib/ui'
8
+ import Layer from './layer'
9
+
10
+ export default class SidebarLayer extends Component {
11
+ public layer?: Layer
12
+ public $header: HTMLElement
13
+ public $closeBtn: HTMLElement
14
+ public $iframeWrap: HTMLElement
15
+ public $iframe?: HTMLIFrameElement
16
+
17
+ constructor(ctx: Context) {
18
+ super(ctx)
19
+
20
+ this.$el = Utils.createElement(SidebarHTML)
21
+ this.$header = this.$el.querySelector('.atk-sidebar-header')!
22
+ this.$closeBtn = this.$header.querySelector('.atk-sidebar-close')!
23
+ this.$iframeWrap = this.$el.querySelector('.atk-sidebar-iframe-wrap')!
24
+
25
+ this.$closeBtn.onclick = () => {
26
+ this.hide()
27
+ }
28
+
29
+ // event
30
+ this.ctx.on('sidebar-show', () => (this.show()))
31
+ this.ctx.on('sidebar-hide', () => (this.hide()))
32
+ this.ctx.on('user-changed', () => { this.firstShow = true })
33
+ }
34
+
35
+ private firstShow = true
36
+
37
+ /** 显示 */
38
+ public async show() {
39
+ this.$el.style.transform = '' // 动画清除,防止二次打开失效
40
+
41
+ // 获取 Layer
42
+ if (this.layer == null) {
43
+ this.layer = new Layer(this.ctx, 'sidebar', this.$el)
44
+ this.layer.afterHide = () => {
45
+ // 防止评论框被吞
46
+ if (this.ctx.conf.editorTravel === true) {
47
+ this.ctx.trigger('editor-travel-back')
48
+ }
49
+ }
50
+ }
51
+ this.layer.show()
52
+
53
+ // viewWrap 滚动条归位
54
+ // this.$viewWrap.scrollTo(0, 0)
55
+
56
+ // 执行动画
57
+ setTimeout(() => {
58
+ this.$el.style.transform = 'translate(0, 0)'
59
+ }, 20)
60
+
61
+ // 第一次加载
62
+ if (this.firstShow) {
63
+ this.$iframeWrap.innerHTML = ''
64
+ this.$iframe = Utils.createElement<HTMLIFrameElement>('<iframe></iframe>')
65
+
66
+ const baseURL = (import.meta.env.MODE === 'development') ? 'http://localhost:23367/'
67
+ : `${this.conf.server.replace(/\/$/, '')}/../sidebar/`
68
+ const userData = encodeURIComponent(JSON.stringify(this.ctx.user.data))
69
+
70
+ // 测试是否能访问
71
+ this.iframeLoad(`${baseURL}`
72
+ + `?pageKey=${encodeURIComponent(this.conf.pageKey)}`
73
+ + `&site=${encodeURIComponent(this.conf.site || '')}`
74
+ + `&user=${userData}`
75
+ + `${((this.conf.darkMode) ? `&darkMode=1` : ``)}`)
76
+
77
+ this.$iframeWrap.append(this.$iframe)
78
+ this.firstShow = false
79
+ } else {
80
+ // 暗黑模式
81
+ if (this.conf.darkMode && !this.$iframe!.src.match(/darkMode=1$/)) {
82
+ this.iframeLoad(`${this.$iframe!.src}&darkMode=1`)
83
+ }
84
+ if (!this.conf.darkMode && this.$iframe!.src.match(/darkMode=1$/)) {
85
+ this.iframeLoad(this.$iframe!.src.replace(/&darkMode=1$/, ''))
86
+ }
87
+ }
88
+ }
89
+
90
+ /** 隐藏 */
91
+ public hide() {
92
+ // 执行动画
93
+ this.$el.style.transform = ''
94
+
95
+ this.layer?.hide()
96
+ }
97
+
98
+ public iframeLoad(src: string) {
99
+ if (!this.$iframe) return
100
+
101
+ this.$iframe.src = src
102
+
103
+ // 加载动画
104
+ Ui.showLoading(this.$iframeWrap)
105
+ this.$iframe.onload = () => {
106
+ Ui.hideLoading(this.$iframeWrap)
107
+ }
108
+
109
+ // this.checkReqStatus(src) // 判不准,删了,没啥用
110
+ }
111
+
112
+ loadingTimer: number|null = null
113
+
114
+ // 测试可访问性 (由于 iframe 测不准,需要额外请求)
115
+ public async checkReqStatus(url: string) {
116
+ if (this.loadingTimer !== null) window.clearTimeout(this.loadingTimer)
117
+
118
+ this.loadingTimer = window.setTimeout(async () => {
119
+ try {
120
+ await fetch(url)
121
+ } catch (err) {
122
+ console.log(err)
123
+ // 请求失败,显示错误提示
124
+ const $errAlert = Utils.createElement(
125
+ `<div class="atk-err-alert">` +
126
+ ` <div class="atk-title">侧边栏似乎打开失败</div>` +
127
+ ` <div class="atk-text"><span id="AtkReload">重新加载</span> / <span id="AtkCancel">取消</span></div>` +
128
+ `</div>`
129
+ )
130
+ const $reloadBtn = $errAlert.querySelector<HTMLElement>('#AtkReload')!
131
+ const $cancelBtn = $errAlert.querySelector<HTMLElement>('#AtkCancel')!
132
+ $reloadBtn.onclick = () => {
133
+ this.iframeLoad(url)
134
+ $errAlert.remove()
135
+ }
136
+ $cancelBtn.onclick = () => { // 提供取消按钮,防止误判
137
+ $errAlert.remove()
138
+ }
139
+ this.$iframeWrap.append($errAlert)
140
+ }
141
+ }, 2000) // 2s 后开始检测
142
+ }
143
+ }
package/src/defaults.ts CHANGED
@@ -17,6 +17,7 @@ const defaults: ArtalkConfig = {
17
17
  vote: true,
18
18
  voteDown: false,
19
19
  uaBadge: true,
20
+ pvEl: '#ArtalkPV',
20
21
  flatMode: 'auto',
21
22
  maxNesting: 3,
22
23
  gravatar: {
package/src/lib/detect.js CHANGED
@@ -104,7 +104,9 @@ function Detect (userAgent) {
104
104
  '6.0': 'Vista',
105
105
  5.2: 'XP',
106
106
  5.1: 'XP',
107
- '5.0': '2000'
107
+ '5.0': '2000',
108
+ '10.0': '10',
109
+ '11.0': '11' // 自定的,不是微软官方的判断方法
108
110
  }
109
111
  return hash[v] || v
110
112
  },