artalk 2.2.4 → 2.2.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.
package/index.html CHANGED
@@ -64,7 +64,7 @@
64
64
  el: '#comments',
65
65
  pageKey: 'https://artalk.js.org/',
66
66
  pageTitle: 'Artalk Home',
67
- server: (import.meta.env.MODE === 'development') ? 'http://localhost:23366/api' : 'https://artalk.qwqaq.com/api',
67
+ server: (import.meta.env.MODE === 'development') ? 'http://localhost:23366' : 'https://artalk.qwqaq.com',
68
68
  site: 'ArtalkDemo',
69
69
  });
70
70
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "artalk",
3
- "version": "2.2.4",
3
+ "version": "2.2.7",
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
@@ -1,5 +1,5 @@
1
- import Context from '../context'
2
1
  import { CommentData, ListData, UserData, PageData, SiteData, NotifyData } from '~/types/artalk-data'
2
+ import Context from '../context'
3
3
  import { Fetch, ToFormData, POST, GET } from './request'
4
4
  import * as Utils from '../lib/utils'
5
5
 
@@ -9,7 +9,7 @@ export default class Api {
9
9
 
10
10
  constructor (ctx: Context) {
11
11
  this.ctx = ctx
12
- this.baseURL = ctx.conf.server
12
+ this.baseURL = `${ctx.conf.server}/api`
13
13
  }
14
14
 
15
15
  // ============================
@@ -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
@@ -318,4 +318,20 @@ export default class Api {
318
318
  const data = await POST<any>(this.ctx, `${this.baseURL}/captcha/status`)
319
319
  return (data || { is_pass: false }) as { is_pass: boolean }
320
320
  }
321
+
322
+ // ============================
323
+ // 管理员缓存操作
324
+ // ============================
325
+
326
+ /** 缓存清除 */
327
+ public cacheFlushAll() {
328
+ const params: any = { flush_all: true }
329
+ return POST(this.ctx, `${this.baseURL}/admin/cache-flush`, params)
330
+ }
331
+
332
+ /** 缓存预热 */
333
+ public cacheWarmUp() {
334
+ const params: any = {}
335
+ return POST(this.ctx, `${this.baseURL}/admin/cache-warm`, params)
336
+ }
321
337
  }
package/src/artalk.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import './style/main.less'
2
2
 
3
- import Context from './context'
4
3
  import ArtalkConfig from '~/types/artalk-config'
4
+ import { EventPayloadMap, Handler } from '~/types/event'
5
+ import Context from './context'
5
6
  import defaults from './defaults'
6
7
 
7
8
  import CheckerLauncher from './lib/checker'
@@ -10,7 +11,6 @@ import List from './components/list'
10
11
  import SidebarLayer from './components/sidebar-layer'
11
12
 
12
13
  import Layer, { GetLayerWrap } from './components/layer'
13
- import { EventPayloadMap, Handler } from '~/types/event'
14
14
  import Api from './api'
15
15
  import * as Utils from './lib/utils'
16
16
 
@@ -33,22 +33,31 @@ export default class Artalk {
33
33
  constructor (customConf: ArtalkConfig) {
34
34
  // 配置
35
35
  this.conf = { ...Artalk.defaults, ...customConf }
36
- this.conf.server = this.conf.server.replace(/\/$/, '')
36
+ this.conf.server = this.conf.server.replace(/\/$/, '').replace(/\/api\/?$/, '')
37
37
 
38
38
  // 默认 pageKey
39
39
  if (!this.conf.pageKey) {
40
40
  // @link http://bl.ocks.org/abernier/3070589
41
- this.conf.pageKey = `${window.location.protocol}//${window.location.host}${window.location.pathname}`
41
+ this.conf.pageKey = `${window.location.pathname}`
42
+ }
43
+
44
+ // 默认 pageTitle
45
+ if (!this.conf.pageTitle) {
46
+ this.conf.pageTitle = `${document.title}`
42
47
  }
43
48
 
44
49
  // 装载元素
45
- try {
46
- const $root = document.querySelector<HTMLElement>(this.conf.el)
47
- if (!$root) throw Error(`Sorry, target element "${this.conf.el}" was not found.`)
48
- this.$root = $root
49
- } catch (e) {
50
- console.error(e)
51
- throw new Error('Please check your Artalk `el` config.')
50
+ if (!!this.conf.el && this.conf.el instanceof HTMLElement) {
51
+ this.$root = this.conf.el
52
+ } else {
53
+ try {
54
+ const $root = document.querySelector<HTMLElement>(this.conf.el)
55
+ if (!$root) throw Error(`Sorry, target element "${this.conf.el}" was not found.`)
56
+ this.$root = $root
57
+ } catch (e) {
58
+ console.error(e)
59
+ throw new Error('Please check your Artalk `el` config.')
60
+ }
52
61
  }
53
62
 
54
63
  // Context 初始化
@@ -1,11 +1,11 @@
1
1
  import '../style/comment.less'
2
2
 
3
+ import { CommentData } from '~/types/artalk-data'
3
4
  import Context from '../context'
4
5
  import Component from '../lib/component'
5
6
  import * as Utils from '../lib/utils'
6
7
  import * as Ui from '../lib/ui'
7
8
  import UADetect from '../lib/detect'
8
- import { CommentData } from '~/types/artalk-data'
9
9
  import CommentHTML from './html/comment.html?raw'
10
10
  import Api from '../api'
11
11
  import ActionBtn from './action-btn'
@@ -1,10 +1,10 @@
1
1
  import './emoticons-plug.less'
2
2
 
3
- import Editor from '../editor'
4
- import EditorPlug from './editor-plug'
5
3
  import * as Utils from '~/src/lib/utils'
6
4
  import * as Ui from '~/src/lib/ui'
7
5
  import { EmoticonListData, EmoticonGrpData } from '~/types/artalk-data'
6
+ import EditorPlug from './editor-plug'
7
+ import Editor from '../editor'
8
8
 
9
9
  type OwOFormatType = {
10
10
  [key: string] : {
@@ -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
36
33
 
37
- // 初始化元素
38
- this.initEmoticonsList()
34
+ async loadEmoticonsData() {
35
+ if (this.isListLoaded) return
36
+ if (this.loadingTask !== null) {
37
+ await this.loadingTask
38
+ return
39
+ }
40
+
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 () {
@@ -1,8 +1,8 @@
1
1
  import './preview-plug.less'
2
2
 
3
+ import * as Utils from '~/src/lib/utils'
3
4
  import Editor from '../editor'
4
5
  import EditorPlug from './editor-plug'
5
- import * as Utils from '~/src/lib/utils'
6
6
 
7
7
  export default class PreviewPlug extends EditorPlug {
8
8
  $el!: HTMLElement
@@ -1,5 +1,6 @@
1
1
  import '../style/editor.less'
2
2
 
3
+ import { CommentData } from '~/types/artalk-data'
3
4
  import Context from '../context'
4
5
  import Component from '../lib/component'
5
6
  import * as Utils from '../lib/utils'
@@ -8,7 +9,6 @@ import EditorHTML from './html/editor.html?raw'
8
9
 
9
10
  import EmoticonsPlug from './editor-plugs/emoticons-plug'
10
11
  import PreviewPlug from './editor-plugs/preview-plug'
11
- import { CommentData } from '~/types/artalk-data'
12
12
  import Api from '../api'
13
13
 
14
14
  export default class Editor extends Component {
@@ -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
 
@@ -1,10 +1,10 @@
1
+ import { ListData, CommentData, NotifyData, ApiVersionData } from '~/types/artalk-data'
1
2
  import Context from '../context'
2
3
  import Component from '../lib/component'
3
4
  import * as Utils from '../lib/utils'
4
5
  import * as Ui from '../lib/ui'
5
6
  import Api from '../api'
6
7
  import Comment from './comment'
7
- import { ListData, CommentData, NotifyData, ApiVersionData } from '~/types/artalk-data'
8
8
  import Pagination from './pagination'
9
9
  import ReadMoreBtn from './read-more-btn'
10
10
  import { backendMinVersion } from '../../package.json'
@@ -49,6 +49,9 @@ export default class ListLite extends Component {
49
49
  </div>`)
50
50
  this.$commentsWrap = this.$el.querySelector('.atk-list-comments-wrap')!
51
51
 
52
+ // 评论为空时显示字符
53
+ if (ctx.conf.noComment) this.noCommentText = ctx.conf.noComment
54
+
52
55
  // 评论时间自动更新
53
56
  window.setInterval(() => {
54
57
  this.$el.querySelectorAll<HTMLElement>('[data-atk-comment-date]').forEach(el => {
@@ -1,12 +1,12 @@
1
1
  import '../style/list.less'
2
2
 
3
+ import { ListData } from '~/types/artalk-data'
3
4
  import Context from '../context'
4
5
  import * as Utils from '../lib/utils'
5
6
  import * as Ui from '../lib/ui'
6
7
  import Api from '../api'
7
8
  import ListHTML from './html/list.html?raw'
8
9
  import ListLite from './list-lite'
9
- import { ListData } from '~/types/artalk-data'
10
10
 
11
11
  export default class List extends ListLite {
12
12
  private $closeCommentBtn!: HTMLElement
@@ -3,11 +3,11 @@ import '../style/sidebar-layer.less'
3
3
  import Context from '@/context'
4
4
  import Component from '@/lib/component'
5
5
  import * as Utils from '@/lib/utils'
6
- import SidebarHTML from './html/sidebar-layer.html?raw'
7
6
  import * as Ui from '@/lib/ui'
7
+ import { SidebarShowPayload } from '~/types/event'
8
+ import SidebarHTML from './html/sidebar-layer.html?raw'
8
9
  import Layer from './layer'
9
10
  import Api from '../api'
10
- import { SidebarShowPayload } from '~/types/event'
11
11
 
12
12
  export default class SidebarLayer extends Component {
13
13
  public layer?: Layer
@@ -88,7 +88,7 @@ export default class SidebarLayer extends Component {
88
88
 
89
89
  // 准备 Iframe 参数
90
90
  const baseURL = (import.meta.env.MODE === 'development') ? 'http://localhost:23367/'
91
- : Utils.getURLBasedOnApi(this.ctx, 'sidebar/')
91
+ : Utils.getURLBasedOnApi(this.ctx, '/sidebar/')
92
92
 
93
93
  const query: any = {
94
94
  pageKey: this.conf.pageKey,
package/src/context.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { marked as libMarked } from 'marked'
2
- import User from './lib/user'
3
2
  import ArtalkConfig from '~/types/artalk-config'
4
3
  import { EventPayloadMap, Event, EventScopeType, Handler } from '~/types/event'
4
+ import User from './lib/user'
5
5
 
6
6
  /**
7
7
  * Artalk Context
@@ -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
  }
@@ -15,7 +15,7 @@ const CaptchaChecker: Checker = {
15
15
  const $iframe = Utils.createElement<HTMLIFrameElement>(`<iframe class="atk-fade-in"></iframe>`)
16
16
  $iframe.style.display = 'none'
17
17
  Ui.showLoading($iframeWrap, { transparentBg: true })
18
- $iframe.src = `${that.ctx.conf.server}/captcha/get?t=${+new Date()}`
18
+ $iframe.src = `${that.ctx.conf.server}/api/captcha/get?t=${+new Date()}`
19
19
  $iframe.onload = () => {
20
20
  $iframe.style.display = ''
21
21
  Ui.hideLoading($iframeWrap)
@@ -1,9 +1,9 @@
1
1
  import Context from '@/context'
2
2
  import Layer from '@/components/layer'
3
3
  import Dialog from '@/components/dialog'
4
+ import { CheckerPayload } from '~/types/event'
4
5
  import * as Utils from '../utils'
5
6
  import * as Ui from '../ui'
6
- import { CheckerPayload } from '~/types/event'
7
7
  import CaptchaChecker from './captcha-checker'
8
8
  import AdminChecker from './admin-checker'
9
9
 
@@ -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