artalk 2.2.4 → 2.2.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "artalk",
3
- "version": "2.2.4",
3
+ "version": "2.2.5",
4
4
  "backendMinVersion": "2.1.2",
5
5
  "license": "LGPL-3.0",
6
6
  "description": "A Selfhosted Comment System",
package/src/api/index.ts CHANGED
@@ -45,7 +45,7 @@ export default class Api {
45
45
  content: comment.content,
46
46
  rid: comment.rid,
47
47
  page_key: comment.page_key,
48
- ua: await Utils.getCorrectUserAgent(), // 需要后端支持,获取修正后的 UA (针对 Win11)
48
+ ua: await Utils.getCorrectUserAgent(), // 需要后端支持,获取修正后的 UA
49
49
  }
50
50
 
51
51
  if (comment.page_title) params.page_title = comment.page_title
@@ -20,22 +20,33 @@ export default class EmoticonsPlug extends EditorPlug {
20
20
  public $grpWrap!: HTMLElement
21
21
  public $grpSwitcher!: HTMLElement
22
22
 
23
+ public loadingTask: Promise<void>|null = null
24
+
23
25
  constructor (public editor: Editor) {
24
26
  super(editor)
25
27
 
26
28
  this.$el = Utils.createElement(`<div class="atk-editor-plug-emoticons"></div>`)
27
-
28
- this.init()
29
29
  }
30
30
 
31
- async init() {
32
- // 数据处理
33
- Ui.showLoading(this.$el)
34
- this.emoticons = await this.handleData(this.ctx.conf.emoticons)
35
- Ui.hideLoading(this.$el)
31
+ isListLoaded = false
32
+ isImgLoaded = false
33
+
34
+ async loadEmoticonsData() {
35
+ if (this.isListLoaded) return
36
+ if (this.loadingTask !== null) {
37
+ await this.loadingTask
38
+ return
39
+ }
36
40
 
37
- // 初始化元素
38
- this.initEmoticonsList()
41
+ // 数据处理
42
+ this.loadingTask = (async () => {
43
+ Ui.showLoading(this.$el)
44
+ this.emoticons = await this.handleData(this.ctx.conf.emoticons)
45
+ Ui.hideLoading(this.$el)
46
+ this.loadingTask = null
47
+ this.isListLoaded = true
48
+ })()
49
+ await this.loadingTask
39
50
  }
40
51
 
41
52
  async handleData(data: any): Promise<any[]> {
@@ -85,7 +96,7 @@ export default class EmoticonsPlug extends EditorPlug {
85
96
  // 剔除非法数据
86
97
  data = data.filter((item: any) => (typeof item === 'object' && !Array.isArray(item) && !!item && !!item.name))
87
98
 
88
- console.log(data)
99
+ // console.log(data)
89
100
 
90
101
  this.solveNullKey(data)
91
102
  this.solveSameKey(data)
@@ -243,10 +254,20 @@ export default class EmoticonsPlug extends EditorPlug {
243
254
  }
244
255
 
245
256
  onShow () {
246
- // 延迟执行,防止无法读取高度
247
- setTimeout(() => {
248
- this.changeListHeight()
249
- }, 30)
257
+ ;(async () => {
258
+ await this.loadEmoticonsData()
259
+
260
+ // 初始化元素
261
+ if (!this.isImgLoaded) {
262
+ this.initEmoticonsList()
263
+ this.isImgLoaded = true
264
+ }
265
+
266
+ // 延迟执行,防止无法读取高度
267
+ setTimeout(() => {
268
+ this.changeListHeight()
269
+ }, 30)
270
+ })()
250
271
  }
251
272
 
252
273
  onHide () {
@@ -205,6 +205,17 @@ export default class Editor extends Component {
205
205
  // 切换按钮
206
206
  const btnElem = Utils.createElement(`<span class="atk-plug-btn" data-plug-name="${PlugObj.Name}">${PlugObj.BtnHTML}</span>`)
207
207
  this.$plugBtnWrap.appendChild(btnElem)
208
+
209
+ // 表情包插件预加载
210
+ if (PlugObj.Name === 'emoticons') {
211
+ const emoPlug = new PlugObj(this) as EmoticonsPlug
212
+ this.plugList[PlugObj.Name] = emoPlug
213
+
214
+ window.setTimeout(() => {
215
+ emoPlug.loadEmoticonsData()
216
+ }, 1000) // 延迟 1s 加载
217
+ }
218
+
208
219
  btnElem.addEventListener('click', () => {
209
220
  let plug = this.plugList[PlugObj.Name]
210
221
  if (!plug) {
@@ -398,7 +409,11 @@ export default class Editor extends Component {
398
409
  if (!!this.plugList && !!this.plugList.preview) {
399
410
  this.plugList.preview.updateContent()
400
411
  }
401
- this.adjustTextareaHeight()
412
+
413
+ // 延迟执行防止无效
414
+ window.setTimeout(() => {
415
+ this.adjustTextareaHeight()
416
+ }, 80)
402
417
  }
403
418
 
404
419
  clearEditor () {
@@ -568,5 +583,9 @@ export default class Editor extends Component {
568
583
  // 取消回复
569
584
  if (this.replyComment !== null) this.cancelReply()
570
585
  }
586
+
587
+ initRemoteEmoticons () {
588
+
589
+ }
571
590
  }
572
591
 
package/src/context.ts CHANGED
@@ -42,4 +42,5 @@ export default class Context {
42
42
  }
43
43
 
44
44
  public markedInstance!: typeof libMarked
45
+ public markedReplacers: ((raw: string) => string)[] = []
45
46
  }
@@ -0,0 +1,222 @@
1
+ const win = (window as any) || {}
2
+ const nav = navigator || {}
3
+
4
+ function Detect (userAgent?: string) {
5
+ const u = String(userAgent || nav.userAgent)
6
+ const dest = {
7
+ os: '',
8
+ osVersion: '',
9
+ engine: '',
10
+ browser: '',
11
+ device: '',
12
+ language: '',
13
+ version: ''
14
+ }
15
+
16
+ // 内核
17
+ const engineMatch = {
18
+ Trident: u.includes('Trident') || u.includes('NET CLR'),
19
+ Presto: u.includes('Presto'),
20
+ WebKit: u.includes('AppleWebKit'),
21
+ Gecko: u.includes('Gecko/')
22
+ }
23
+
24
+ // 浏览器
25
+ const browserMatch = {
26
+ Safari: u.includes('Safari'),
27
+ Chrome: u.includes('Chrome') || u.includes('CriOS'),
28
+ IE: u.includes('MSIE') || u.includes('Trident'),
29
+ Edge: u.includes('Edge') || u.includes('Edg'),
30
+ Firefox: u.includes('Firefox') || u.includes('FxiOS'),
31
+ 'Firefox Focus': u.includes('Focus'),
32
+ Chromium: u.includes('Chromium'),
33
+ Opera: u.includes('Opera') || u.includes('OPR'),
34
+ Vivaldi: u.includes('Vivaldi'),
35
+ Yandex: u.includes('YaBrowser'),
36
+ Kindle: u.includes('Kindle') || u.includes('Silk/'),
37
+ 360: u.includes('360EE') || u.includes('360SE'),
38
+ UC: u.includes('UC') || u.includes(' UBrowser'),
39
+ QQBrowser: u.includes('QQBrowser'),
40
+ QQ: u.includes('QQ/'),
41
+ Baidu: u.includes('Baidu') || u.includes('BIDUBrowser'),
42
+ Maxthon: u.includes('Maxthon'),
43
+ Sogou: u.includes('MetaSr') || u.includes('Sogou'),
44
+ LBBROWSER: u.includes('LBBROWSER'),
45
+ '2345Explorer': u.includes('2345Explorer'),
46
+ TheWorld: u.includes('TheWorld'),
47
+ MIUI: u.includes('MiuiBrowser'),
48
+ Quark: u.includes('Quark'),
49
+ Qiyu: u.includes('Qiyu'),
50
+ Wechat: u.includes('MicroMessenger'),
51
+ Taobao: u.includes('AliApp(TB'),
52
+ Alipay: u.includes('AliApp(AP'),
53
+ Weibo: u.includes('Weibo'),
54
+ Douban: u.includes('com.douban.frodo'),
55
+ Suning: u.includes('SNEBUY-APP'),
56
+ iQiYi: u.includes('IqiyiApp'),
57
+ }
58
+
59
+ // 系统或平台
60
+ const osMatch = {
61
+ Windows: u.includes('Windows'),
62
+ Linux: u.includes('Linux') || u.includes('X11'),
63
+ 'macOS': u.includes('Macintosh'),
64
+ Android: u.includes('Android') || u.includes('Adr'),
65
+ Ubuntu: u.includes('Ubuntu'),
66
+ FreeBSD: u.includes('FreeBSD'),
67
+ Debian: u.includes('Debian'),
68
+ 'Windows Phone': u.includes('IEMobile') || u.includes('Windows Phone'),
69
+ BlackBerry: u.includes('BlackBerry') || u.includes('RIM'),
70
+ MeeGo: u.includes('MeeGo'),
71
+ Symbian: u.includes('Symbian'),
72
+ iOS: u.includes('like Mac OS X'),
73
+ 'Chrome OS': u.includes('CrOS'),
74
+ WebOS: u.includes('hpwOS'),
75
+ }
76
+
77
+ // 设备
78
+ const deviceMatch = {
79
+ Mobile: u.includes('Mobi') || u.includes('iPh') || u.includes('480'),
80
+ Tablet: u.includes('Tablet') || u.includes('Pad') || u.includes('Nexus 7')
81
+ }
82
+
83
+ // 修正
84
+ if (deviceMatch.Mobile) {
85
+ deviceMatch.Mobile = !(u.includes('iPad'))
86
+ } else if (browserMatch.Chrome && u.includes('Edg')) {
87
+ // Chrome 内核的 Edge
88
+ browserMatch.Chrome = false
89
+ browserMatch.Edge = true
90
+ } else if (win.showModalDialog && win.chrome) {
91
+ browserMatch.Chrome = false
92
+ browserMatch['360'] = true
93
+ }
94
+
95
+ // 默认设备
96
+ dest.device = 'PC'
97
+
98
+ // 语言
99
+ dest.language = (() => {
100
+ const g = ((nav as any).browserLanguage || nav.language)
101
+ const arr = g.split('-')
102
+ if (arr[1]) arr[1] = arr[1].toUpperCase()
103
+ return arr.join('_')
104
+ })()
105
+
106
+ // 应用判断数据
107
+ const hash = {
108
+ engine: engineMatch,
109
+ browser: browserMatch,
110
+ os: osMatch,
111
+ device: deviceMatch,
112
+ }
113
+ Object.entries(hash).forEach(([type, match]) => {
114
+ Object.entries(match).forEach(([name, result]) => {
115
+ if (result === true) dest[type] = name
116
+ })
117
+ })
118
+
119
+ // 系统版本信息
120
+ const osVersion = {
121
+ Windows: () => {
122
+ const v = u.replace(/^.*Windows NT ([\d.]+);.*$/, '$1')
123
+ const wvHash = {
124
+ '6.4': '10',
125
+ '6.3': '8.1',
126
+ '6.2': '8',
127
+ '6.1': '7',
128
+ '6.0': 'Vista',
129
+ '5.2': 'XP',
130
+ '5.1': 'XP',
131
+ '5.0': '2000',
132
+ '10.0': '10',
133
+ '11.0': '11' // 自定的,不是微软官方的判断方法
134
+ }
135
+ return wvHash[v] || v
136
+ },
137
+ Android: () => u.replace(/^.*Android ([\d.]+);.*$/, '$1'),
138
+ iOS: () => u.replace(/^.*OS ([\d_]+) like.*$/, '$1').replace(/_/g, '.'),
139
+ Debian: () => u.replace(/^.*Debian\/([\d.]+).*$/, '$1'),
140
+ 'Windows Phone': () => u.replace(/^.*Windows Phone( OS)? ([\d.]+);.*$/, '$2'),
141
+ 'macOS': () => u.replace(/^.*Mac OS X ([\d_]+).*$/, '$1').replace(/_/g, '.'),
142
+ WebOS: () => u.replace(/^.*hpwOS\/([\d.]+);.*$/, '$1')
143
+ }
144
+
145
+ dest.osVersion = ''
146
+ if (osVersion[dest.os]) {
147
+ dest.osVersion = osVersion[dest.os]()
148
+ if (dest.osVersion === u) {
149
+ dest.osVersion = ''
150
+ }
151
+ }
152
+
153
+ // 浏览器版本信息
154
+ const version = {
155
+ Safari: () => u.replace(/^.*Version\/([\d.]+).*$/, '$1'),
156
+ Chrome: () => u.replace(/^.*Chrome\/([\d.]+).*$/, '$1').replace(/^.*CriOS\/([\d.]+).*$/, '$1'),
157
+ IE: () => u.replace(/^.*MSIE ([\d.]+).*$/, '$1').replace(/^.*rv:([\d.]+).*$/, '$1'),
158
+ Edge: () => u.replace(/^.*(Edge|Edg|Edg[A-Z]{1})\/([\d.]+).*$/, '$2'),
159
+ Firefox: () => u.replace(/^.*Firefox\/([\d.]+).*$/, '$1').replace(/^.*FxiOS\/([\d.]+).*$/, '$1'),
160
+ 'Firefox Focus': () => u.replace(/^.*Focus\/([\d.]+).*$/, '$1'),
161
+ Chromium: () => u.replace(/^.*Chromium\/([\d.]+).*$/, '$1'),
162
+ Opera: () => u.replace(/^.*Opera\/([\d.]+).*$/, '$1').replace(/^.*OPR\/([\d.]+).*$/, '$1'),
163
+ Vivaldi: () => u.replace(/^.*Vivaldi\/([\d.]+).*$/, '$1'),
164
+ Yandex: () => u.replace(/^.*YaBrowser\/([\d.]+).*$/, '$1'),
165
+ Kindle: () => u.replace(/^.*Version\/([\d.]+).*$/, '$1'),
166
+ Maxthon: () => u.replace(/^.*Maxthon\/([\d.]+).*$/, '$1'),
167
+ QQBrowser: () => u.replace(/^.*QQBrowser\/([\d.]+).*$/, '$1'),
168
+ QQ: () => u.replace(/^.*QQ\/([\d.]+).*$/, '$1'),
169
+ Baidu: () => u.replace(/^.*BIDUBrowser[\s/]([\d.]+).*$/, '$1'),
170
+ UC: () => u.replace(/^.*UC?Browser\/([\d.]+).*$/, '$1'),
171
+ Sogou: () => u.replace(/^.*SE ([\d.X]+).*$/, '$1').replace(/^.*SogouMobileBrowser\/([\d.]+).*$/, '$1'),
172
+ '2345Explorer': () => u.replace(/^.*2345Explorer\/([\d.]+).*$/, '$1'),
173
+ TheWorld: () => u.replace(/^.*TheWorld ([\d.]+).*$/, '$1'),
174
+ MIUI: () => u.replace(/^.*MiuiBrowser\/([\d.]+).*$/, '$1'),
175
+ Quark: () => u.replace(/^.*Quark\/([\d.]+).*$/, '$1'),
176
+ Qiyu: () => u.replace(/^.*Qiyu\/([\d.]+).*$/, '$1'),
177
+ Wechat: () => u.replace(/^.*MicroMessenger\/([\d.]+).*$/, '$1'),
178
+ Taobao: () => u.replace(/^.*AliApp\(TB\/([\d.]+).*$/, '$1'),
179
+ Alipay: () => u.replace(/^.*AliApp\(AP\/([\d.]+).*$/, '$1'),
180
+ Weibo: () => u.replace(/^.*weibo__([\d.]+).*$/, '$1'),
181
+ Douban: () => u.replace(/^.*com.douban.frodo\/([\d.]+).*$/, '$1'),
182
+ Suning: () => u.replace(/^.*SNEBUY-APP([\d.]+).*$/, '$1'),
183
+ iQiYi: () => u.replace(/^.*IqiyiVersion\/([\d.]+).*$/, '$1'),
184
+ }
185
+
186
+ dest.version = ''
187
+ if (version[dest.browser]) {
188
+ dest.version = version[dest.browser]()
189
+ if (dest.version === u) {
190
+ dest.version = ''
191
+ }
192
+ }
193
+
194
+ // 简化版本号
195
+ /* if (_this.osVersion.indexOf('.')) {
196
+ _this.osVersion = _this.osVersion.substring(0, _this.osVersion.indexOf('.'))
197
+ } */
198
+ if (dest.version.indexOf('.')) {
199
+ dest.version = dest.version.substring(0, dest.version.indexOf('.'))
200
+ }
201
+
202
+ // 修正
203
+ if (dest.os === 'iOS' && u.includes('iPad')) {
204
+ dest.os = 'iPadOS'
205
+ } else if (dest.browser === 'Edge' && !u.includes('Edg')) {
206
+ dest.engine = 'EdgeHTML'
207
+ } else if (dest.browser === 'MIUI') {
208
+ dest.os = 'Android'
209
+ } else if (dest.browser === 'Chrome' && Number(dest.version) > 27) {
210
+ dest.engine = 'Blink'
211
+ } else if (dest.browser === 'Opera' && Number(dest.version) > 12) {
212
+ dest.engine = 'Blink'
213
+ } else if (dest.browser === 'Yandex') {
214
+ dest.engine = 'Blink'
215
+ } else if (dest.browser === undefined) {
216
+ dest.browser = 'Unknow App'
217
+ }
218
+
219
+ return dest
220
+ }
221
+
222
+ export default Detect
package/src/lib/utils.ts CHANGED
@@ -143,10 +143,10 @@ export function versionCompare(a: string, b: string) {
143
143
  /** 初始化 marked */
144
144
  export function initMarked(ctx: Context) {
145
145
  const renderer = new libMarked.Renderer()
146
- const linkRenderer = renderer.link
146
+ const orgLinkRenderer = renderer.link
147
147
  renderer.link = (href, title, text) => {
148
148
  const localLink = href?.startsWith(`${window.location.protocol}//${window.location.hostname}`);
149
- const html = linkRenderer.call(renderer as any, href, title, text);
149
+ const html = orgLinkRenderer.call(renderer as any, href, title, text);
150
150
  return html.replace(/^<a /, `<a target="_blank" ${!localLink ? `rel="noreferrer noopener nofollow"` : ''} `);
151
151
  }
152
152
 
@@ -186,8 +186,9 @@ export function initMarked(ctx: Context) {
186
186
 
187
187
  /** 解析 markdown */
188
188
  export function marked(ctx: Context, src: string): string {
189
+ // @link https://github.com/markedjs/marked/discussions/1232
189
190
  // @link https://gist.github.com/lionel-rowe/bb384465ba4e4c81a9c8dada84167225
190
- return insane(ctx.markedInstance.parse(src), {
191
+ let dest = insane(ctx.markedInstance.parse(src), {
191
192
  allowedClasses: {},
192
193
  allowedSchemes: ['http', 'https', 'mailto'],
193
194
  allowedTags: [
@@ -198,7 +199,7 @@ export function marked(ctx: Context, src: string): string {
198
199
  ],
199
200
  allowedAttributes: {
200
201
  '*': ['title', 'accesskey'],
201
- a: ['href', 'name', 'target', 'aria-label'],
202
+ a: ['href', 'name', 'target', 'aria-label', 'rel'],
202
203
  img: ['src', 'alt', 'title', 'atk-emoticon', 'aria-label'],
203
204
  // for code highlight
204
205
  code: ['class'],
@@ -229,28 +230,45 @@ export function marked(ctx: Context, src: string): string {
229
230
  return true
230
231
  }
231
232
  })
233
+
234
+ // 内容替换器
235
+ ctx.markedReplacers.forEach((replacer) => {
236
+ if (typeof replacer === 'function') dest = replacer(dest)
237
+ })
238
+
239
+ return dest
232
240
  }
233
241
 
234
242
  /** 获取修正后的 UserAgent */
235
243
  export async function getCorrectUserAgent() {
236
244
  const uaRaw = navigator.userAgent
237
- const uaData = (navigator as any).userAgentData // @link https://web.dev/migrate-to-ua-ch/
238
- if (!uaData || !uaData.getHighEntropyValues) {
245
+ if (!(navigator as any).userAgentData || !(navigator as any).userAgentData.getHighEntropyValues) {
239
246
  return uaRaw
240
247
  }
241
248
 
242
- // microsoft f******k you!!!!!
243
- // @link https://docs.microsoft.com/en-us/microsoft-edge/web-platform/how-to-detect-win11
249
+ // @link https://web.dev/migrate-to-ua-ch/
250
+ // @link https://web.dev/user-agent-client-hints/
251
+ const uaData = (navigator as any).userAgentData
244
252
  let uaGot: any = null
245
253
  try {
246
254
  uaGot = await uaData.getHighEntropyValues(["platformVersion"])
247
255
  } catch (err) { console.error(err); return uaRaw }
248
-
249
- if (uaData.platform !== "Windows") { return uaRaw }
250
256
  const majorPlatformVersion = Number(uaGot.platformVersion.split('.')[0])
251
- if (majorPlatformVersion >= 13) {
252
- // Win11 样本:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36"
253
- return uaRaw.replace(/Windows\W+NT\W+10.0/, 'Windows NT 11.0')
257
+
258
+ if (uaData.platform === "Windows") {
259
+ if (majorPlatformVersion >= 13) { // @link https://docs.microsoft.com/en-us/microsoft-edge/web-platform/how-to-detect-win11
260
+ // @date 2022-4-29
261
+ // Win 11 样本:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36"
262
+ return uaRaw.replace(/Windows NT 10.0/, 'Windows NT 11.0')
263
+ }
264
+ }
265
+ if (uaData.platform === "macOS") {
266
+ if (majorPlatformVersion >= 11) { // 11 => BigSur
267
+ // @date 2022-4-29
268
+ // (Intel Chip) macOS 12.3 样本:"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36"
269
+ // (Arm Chip) macOS 样本:"Mozilla/5.0 (Macintosh; ARM Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.2 Safari/605.1.15"
270
+ return uaRaw.replace(/(Mac OS X \d+_\d+_\d+|Mac OS X)/, `Mac OS X ${uaGot.platformVersion.replace(/\./g, '_')}`)
271
+ }
254
272
  }
255
273
 
256
274
  return uaRaw