Compare commits

...
17 Commits
Author SHA1 Message Date
qwqcode f053b376ee v2.0.6 2021-11-03 00:15:06 +08:00
qwqcode f41e67748f fix: github actions npm publish error.
Signed-off-by: qwqcode <qwqcode@gmail.com>
2021-11-03 00:14:41 +08:00
qwqcode 5fcc8be5a5 v2.0.5 2021-11-02 23:58:55 +08:00
qwqcode b812924ebb feat: Version Check.
Signed-off-by: qwqcode <qwqcode@gmail.com>
2021-11-02 23:57:47 +08:00
qwqcode 952a888a54 feat: Comments list flatMode & maxNesting config.
Signed-off-by: qwqcode <qwqcode@gmail.com>
2021-11-02 22:44:00 +08:00
qwqcode 6f4bce7327 feat(event): Add descriptions of events & more event.
Signed-off-by: qwqcode <qwqcode@gmail.com>
2021-11-02 20:35:39 +08:00
qwqcode a009a05237 feat(event): comments-loaded & comments-loading.
Signed-off-by: qwqcode <qwqcode@gmail.com>
2021-11-02 19:03:26 +08:00
qwqcode 3d5931dcf9 feat: Event scope. 2021-11-02 17:23:41 +08:00
qwqcode 9dbf03ce60 feat: expose Event on/off/trigger methods.
Signed-off-by: qwqcode <qwqcode@gmail.com>
2021-11-02 17:08:37 +08:00
qwqcode 499df5f8f3 docs: Add some code comments.
Signed-off-by: qwqcode <qwqcode@gmail.com>
2021-11-02 16:28:04 +08:00
qwqcode dae500190e refactor(API): Common Request class.
Signed-off-by: qwqcode <qwqcode@gmail.com>
2021-11-02 16:14:53 +08:00
qwqcode bc40e691af feat: Config set request timeout.
Signed-off-by: qwqcode <qwqcode@gmail.com>
2021-11-02 14:31:55 +08:00
qwqcode e19b18a234 fix: TS declare.
Signed-off-by: qwqcode <qwqcode@gmail.com>
2021-11-02 14:16:49 +08:00
qwqcode f54de59778 fix: Cross-site edit page or comment issue. 2021-10-30 22:06:35 +08:00
qwqcode ffd62f09ac fix: Remove the deprecated pageUrl config.
Signed-off-by: qwqcode <qwqcode@gmail.com>
2021-10-30 19:57:05 +08:00
qwqcode eebe77e3fa feat(GitHub Actions): Auto update Docs CDN link.
Signed-off-by: qwqcode <qwqcode@gmail.com>
2021-10-29 17:16:29 +08:00
qwqcode 9c2cd74793 chore: Add .github to .npmignore
Signed-off-by: qwqcode <qwqcode@gmail.com>
2021-10-29 11:38:43 +08:00
33 changed files with 821 additions and 953 deletions

No files matched your search

+31 -8
View File
@@ -17,15 +17,38 @@ jobs:
node-version: '12.x'
registry-url: 'https://registry.npmjs.org'
- run: npm install
- run: npm build
- run: npm run build
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
# Setup .npmrc file to publish to GitHub Packages
- uses: actions/setup-node@v2
with:
registry-url: 'https://npm.pkg.github.com'
# Publish to GitHub Packages
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# - uses: actions/setup-node@v2
# with:
# registry-url: 'https://npm.pkg.github.com'
# # Publish to GitHub Packages
# - run: npm publish
# env:
# NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Docs Update
docs-update:
runs-on: ubuntu-latest
steps:
- name: Get the version
id: get_version
run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\/v/}
- name: Checkout ArtalkDocs
uses: actions/checkout@v2
with:
repository: ArtalkJS/Docs
token: ${{ secrets.PAT }}
- name: Update CDN links
run: |
sed -i 's#artalk@.[^/]*#artalk@${{ steps.get_version.outputs.VERSION }}#g' docs/code/ArtalkCDN.json
sed -i 's#artalk@.[^/]*#artalk@${{ steps.get_version.outputs.VERSION }}#g' docs/code/quick-start/cdn.html
- uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: Update Artalk CDN link to v${{ steps.get_version.outputs.VERSION }}
+2 -1
View File
@@ -39,7 +39,6 @@ vite*.config*
.eslintrc.js
.eslintignore
postcss.config.js
tsconfig.json
yarn.lock
package-lock.json
@@ -50,3 +49,5 @@ local
CNAME
index.html
#dist
.github
+7 -60
View File
@@ -52,61 +52,6 @@
前往:[“**文档 · 部署**”](https://artalk.js.org/guide/deploy.html)
### 两步抵达
<details>
<summary>点击展开</summary>
1. 后端程序部署,前往:[“**文档 · 后端部署**”](https://artalk.js.org/guide/backend/install.html)
2. 前端页面引入:
```html
<!DOCTYPE html>
<html>
<head>
<!-- 引入 CSS -->
<link href="https://cdn.jsdelivr.net/npm/artalk@2/dist/Artalk.css" rel="stylesheet">
</head>
<body>
<!-- 给定元素 -->
<div id="Comments"></div>
<!-- 引入 JS -->
<script src="https://cdn.jsdelivr.net/npm/artalk@2/dist/Artalk.js"></script>
<!-- 配置 Artalk -->
<script>
new Artalk({
el: '#Comments',
pageKey: '<页面链接>',
pageTitle: '<页面标题>',
server: '<后端地址>',
site: '<站点名称>',
})
</script>
</body>
</html>
```
</details>
### CDN 资源
在页面合适的位置通过 CDN 资源引入 Artalk:
```html
<!-- CSS -->
<link href="https://cdn.jsdelivr.net/npm/artalk@2/dist/Artalk.css" rel="stylesheet">
<!-- JS -->
<script src="https://cdn.jsdelivr.net/npm/artalk@2/dist/Artalk.js"></script>
```
### NPM 引用
```sh
$ yarn add artalk
```
@@ -115,23 +60,25 @@ $ yarn add artalk
import Artalk from 'artalk'
new Artalk({
// ...your config
el: '#Comments',
pageKey: '<页面链接>',
pageTitle: '<页面标题>',
server: '<后端地址>',
site: '<站点名称>',
})
```
Artalk 拥有 TypeScript 声明文件,能够在引用时帮助您进行代码提示。
## 特别致谢
感谢社区提供的帮助与反馈,若有好的建议与意见欢迎前往 [issues](https://github.com/ArtalkJS/Artalk/issues) 随时告知。
感谢社区提供的帮助与反馈,若有好的建议与意见欢迎前往 [ISSUES](https://github.com/ArtalkJS/Artalk/issues) 随时告知。
## Stargazers over time
[![Stargazers over time](https://starchart.cc/ArtalkJS/Artalk.svg)](https://starchart.cc/ArtalkJS/Artalk)
[![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)
## License
[GPL-3.0](./LICENSE)
[![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)
[![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)
-55
View File
@@ -1,55 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Multiple Artalk On One Page Example</title>
<!-- 1. 引入 Artalk.css -->
<link rel="stylesheet" href="/dist/Artalk.css">
</head>
<body>
<h1>Multiple Artalk On One Page Example</h1>
<div class="main">
<h2>Artalk One</h2>
<div id="ArtalkComments-1"></div>
<h2>Artalk Two</h2>
<div id="ArtalkComments-2"></div>
</div>
<!-- 2. 引入 Artalk.js -->
<script src="/dist/Artalk.js"></script>
<!-- 3. 配置 Artalk -->
<script>
(function () {
// 公用配置
const commonConf = {
placeholder: '来啊,快活啊 ( ゜- ゜)',
noComment: '快来成为第一个评论的人吧~',
defaultAvatar: 'mp',
serverUrl: 'http://localhost:23366/',
readMore: {
pageSize: 15,
autoLoad: true
}
}
// 第一个评论
new Artalk({
el: '#ArtalkComments-1',
pageKey: 'multiple-on-one-page-1',
...commonConf
})
// 第二个评论
new Artalk({
el: '#ArtalkComments-2',
pageKey: 'multiple-on-one-page-2',
...commonConf
})
})();
</script>
</body>
</html>
-36
View File
@@ -1,36 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Artalk Normal Example</title>
<!-- 1. 引入 Artalk.css -->
<link rel="stylesheet" href="/dist/Artalk.css">
</head>
<body>
<h1>Artalk Normal Example</h1>
<div class="main">
<div id="ArtalkComments"></div>
</div>
<!-- 2. 引入 Artalk.js -->
<script src="/dist/Artalk.js"></script>
<!-- 3. 配置 Artalk -->
<script>
var artalk = new Artalk({
el: '#ArtalkComments', // 元素选择
placeholder: '来啊,快活啊 ( ゜- ゜)', // 占位符
noComment: '快来成为第一个评论的人吧~', // 无评论时显示
defaultAvatar: 'mp', // 参考 https://cn.gravatar.com/site/implement/images/#default-image
pageKey: 'Artalk Normal Example',
serverUrl: 'http://localhost:23366/',
readMore: { // 阅读更多配置
pageSize: 15, // 每次请求获取评论数
autoLoad: true // 滚动到底部自动加载
}
})
</script>
</body>
</html>
-29
View File
@@ -1,29 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Artalk Pjax Example</title>
<!-- 1. 引入 Artalk.css -->
<link rel="stylesheet" href="/dist/Artalk.css">
</head>
<body>
<h1>Artalk Pjax Example</h1>
<div>
Go to <a href="./res1.html">第一页</a> | <a href="./res2.html">第二页</a>
</div>
<div id="container"></div>
</body>
<!-- 2. 引入 Artalk.js -->
<script src="/dist/Artalk.js"></script>
<!-- Pjax 配置 -->
<script src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery-pjax@2.0.1/jquery.pjax.min.js"></script>
<script type="text/javascript">
$(document).pjax('a', '#container', {
fragment: '.main' // △ 提取 AJAX 响应内容中指定的元素内容片段;参考:http://bsify.admui.com/jquery-pjax/
})
</script>
</html>
-41
View File
@@ -1,41 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Res</title>
<!-- 1. 引入 Artalk.css -->
<link rel="stylesheet" href="/dist/Artalk.css">
</head>
<body>
<div class="main"> <!-- △ fragment: .main -->
<section id="comments">
<div id="ArtalkComments"></div>
</section>
<!-- 2. 引入 Artalk.js -->
<script src="/dist/Artalk.js"></script>
<!-- 3. 配置 Artalk -->
<script>
window.artalk = new Artalk({
el: '#ArtalkComments',
placeholder: '来啊,快活啊 ( ゜- ゜)',
noComment: '快来成为第一个评论的人吧~',
defaultAvatar: 'mp',
pageKey: 'pjax-example-page1',
serverUrl: 'http://localhost:23366/index.php',
readMore: {
pageSize: 15,
autoLoad: true
}
});
</script>
</div>
<!-- Pjax 配置 -->
<script src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery-pjax@2.0.1/jquery.pjax.min.js"></script>
</body>
</html>
-41
View File
@@ -1,41 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Res</title>
<!-- 1. 引入 Artalk.css -->
<link rel="stylesheet" href="/dist/Artalk.css">
</head>
<body>
<div class="main"> <!-- △ fragment: .main -->
<section id="comments">
<div id="ArtalkComments"></div>
</section>
<!-- 2. 引入 Artalk.js -->
<script src="/dist/Artalk.js"></script>
<!-- 3. 配置 Artalk -->
<script>
window.artalk = new Artalk({
el: '#ArtalkComments',
placeholder: '来啊,快活啊 ( ゜- ゜)',
noComment: '快来成为第一个评论的人吧~',
defaultAvatar: 'mp',
pageKey: 'pjax-example-page2',
serverUrl: 'http://localhost:23366/index.php',
readMore: {
pageSize: 15,
autoLoad: true
}
});
</script>
</div>
<!-- Pjax 配置 -->
<script src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery-pjax@2.0.1/jquery.pjax.min.js"></script>
</body>
</html>
+3 -4
View File
@@ -1,7 +1,7 @@
{
"name": "artalk",
"version": "2.0.4",
"description": "A Fast, Slight & Delightful Comment",
"version": "2.0.6",
"description": "A Selfhosted Comment System",
"keywords": [
"comment-system",
"comments-widget",
@@ -20,7 +20,7 @@
"unpkg": "./dist/Artalk.iife.js",
"jsdelivr": "./dist/Artalk.iife.js",
"module": "./dist/Artalk.es.js",
"types": "./dist/types/index.d.ts",
"types": "./types/index.d.ts",
"exports": {
".": {
"import": "./dist/Artalk.es.js",
@@ -57,7 +57,6 @@
"prettier": "^2.3.2",
"typescript": "4.3.2",
"vite": "^2.5.1",
"vite-plugin-dts": "^0.7.0",
"vite-tsconfig-paths": "^3.3.14"
},
"dependencies": {
+31 -14
View File
@@ -10,6 +10,7 @@ import { ArtalkConfig } from '~/types/artalk-config'
import Context from './Context'
import emoticons from './assets/emoticons.json'
import Constant from './Constant'
import { EventPayloadMap, Handler } from '~/types/event'
const defaultOpts: ArtalkConfig = {
el: '',
@@ -25,11 +26,15 @@ const defaultOpts: ArtalkConfig = {
cdn: 'https://sdn.geekzu.org/avatar/'
},
darkMode: false,
reqTimeout: 15000,
flatMode: false,
maxNesting: 3,
versionCheck: true,
}
export default class Artalk {
public conf: ArtalkConfig
public ctx: Context
public conf: ArtalkConfig
public el: HTMLElement
public readonly contextID: number = new Date().getTime() // 实例唯一 ID
public checker: Checker
@@ -40,11 +45,11 @@ export default class Artalk {
constructor (conf: ArtalkConfig) {
// Version Information
console.log(`\n %c `
+ `Artalk v${ARTALK_VERSION} %c 一款简洁有趣的可拓展评论系统 \n\n%c`
+ `> https://artalk.js.org\n`
+ `> https://github.com/ArtalkJS/Artalk\n`,
'color: #FFF; background: #1DAAFF; padding:5px 0;', 'color: #FFF; background: #656565; padding:5px 0;', '')
// console.log(`\n %c `
// + `Artalk v${ARTALK_VERSION} %c 一款简洁有趣的可拓展评论系统 \n\n%c`
// + `> https://artalk.js.org\n`
// + `> https://github.com/ArtalkJS/Artalk\n`,
// 'color: #FFF; background: #1DAAFF; padding:5px 0;', 'color: #FFF; background: #656565; padding:5px 0;', '')
// Options
this.conf = { ...defaultOpts, ...conf }
@@ -100,7 +105,7 @@ export default class Artalk {
})
// 仅管理员显示控制
this.ctx.addEventListener('check-admin-show-el', () => {
this.ctx.on('check-admin-show-el', () => {
const items: HTMLElement[] = []
this.el.querySelectorAll<HTMLElement>(`[atk-only-admin-show]`).forEach(item => items.push(item))
@@ -117,14 +122,14 @@ export default class Artalk {
})
})
this.ctx.addEventListener('user-changed', () => {
this.ctx.dispatchEvent('check-admin-show-el')
this.ctx.dispatchEvent('list-refresh-ui')
this.ctx.on('user-changed', () => {
this.ctx.trigger('check-admin-show-el')
this.ctx.trigger('list-refresh-ui')
})
}
/** 暗黑模式 - 初始化 */
initDarkMode() {
public initDarkMode() {
if (this.conf.darkMode) {
this.el.classList.add(Constant.DARK_MODE_CLASSNAME)
} else {
@@ -143,18 +148,30 @@ export default class Artalk {
}
/** 暗黑模式 - 设定 */
setDarkMode(darkMode: boolean) {
public setDarkMode(darkMode: boolean) {
this.ctx.conf.darkMode = darkMode
this.initDarkMode()
}
/** 暗黑模式 - 开启 */
openDarkMode() {
public openDarkMode() {
this.setDarkMode(true)
}
/** 暗黑模式 - 关闭 */
closeDarkMode() {
public closeDarkMode() {
this.setDarkMode(false)
}
public on<K extends keyof EventPayloadMap>(name: K, handler: Handler<EventPayloadMap[K]>): void {
this.ctx.on(name, handler, 'external')
}
public off<K extends keyof EventPayloadMap>(name: K, handler: Handler<EventPayloadMap[K]>): void {
this.ctx.off(name, handler, 'external')
}
public trigger<K extends keyof EventPayloadMap>(name: K, payload?: EventPayloadMap[K]): void {
this.ctx.trigger(name, payload, 'external')
}
}
+13 -10
View File
@@ -1,6 +1,6 @@
import { ArtalkConfig } from '~/types/artalk-config'
import User from './lib/user'
import { EventPayloadMap, Listener, Event } from './Event'
import { ArtalkConfig } from '~/types/artalk-config'
import { EventPayloadMap, Event, EventScopeType, Handler } from '~/types/event'
export default class Context {
public cid: number // Context 唯一标识
@@ -17,18 +17,21 @@ export default class Context {
this.user = new User(this.conf)
}
public addEventListener<K extends keyof EventPayloadMap>(name: K, listener: Listener<EventPayloadMap[K]>): void {
this.eventList.push({ name, listener: listener as any })
public on<K extends keyof EventPayloadMap>(name: K, handler: Handler<EventPayloadMap[K]>, scope: EventScopeType = 'internal'): void {
this.eventList.push({ name, handler: handler as any, scope })
}
public removeEventListener<K extends keyof EventPayloadMap>(name: K): void {
this.eventList = this.eventList.filter((event) => event.name !== name)
public off<K extends keyof EventPayloadMap>(name: K, handler?: Handler<EventPayloadMap[K]>, scope: EventScopeType = 'internal'): void {
this.eventList = this.eventList.filter((evt) => {
if (handler) return !(evt.name === name && evt.handler === handler && evt.scope === scope)
return !(evt.name === name && evt.scope === scope) // 删除全部相同 name event
})
}
public dispatchEvent<K extends keyof EventPayloadMap>(name: K, payload?: EventPayloadMap[K]): void {
public trigger<K extends keyof EventPayloadMap>(name: K, payload?: EventPayloadMap[K], scope?: EventScopeType): void {
this.eventList
.filter((event) => event.name === name)
.map((event) => event.listener)
.forEach((listener) => listener(payload as any))
.filter((evt) => evt.name === name && (scope ? (evt.scope === scope) : true))
.map((evt) => evt.handler)
.forEach((handler) => handler(payload as any))
}
}
-59
View File
@@ -1,59 +0,0 @@
import { ListData, CommentData, NotifyData } from '~/types/artalk-data'
/** EventName to EventPayload Type */
export interface EventPayloadMap {
'list-loading': undefined
'list-clear': undefined
'list-refresh-ui': undefined
'list-import': CommentData[]
'list-insert': CommentData
'list-comment-del': CommentData
'list-reload': undefined
'list-update-data': (data: ListData | undefined) => void
'sidebar-show'?: SidebarShowPayload
'sidebar-hide': undefined
'check-admin-show-el': undefined
'editor-open-comment': undefined
'editor-close-comment': undefined
'editor-show-loading': undefined
'editor-hide-loading': undefined
'editor-notify': NotifyConf
'editor-reply': CommentData
'user-changed': undefined
'unread-update': UnreadUpdatePayload
'checker-admin': CheckerConf
'checker-captcha': CheckerCaptchaConf
}
export interface CheckerConf {
onSuccess?: (inputVal: string, dialogEl?: HTMLElement) => void
onMount?: (dialogEl: HTMLElement) => void
onCancel?: () => void
}
export interface NotifyConf {
msg: string
type: any
}
export interface CheckerCaptchaConf extends CheckerConf {
imgData?: string
}
export interface SidebarShowPayload {
viewName?: string
}
export interface UnreadUpdatePayload {
notifies: NotifyData[]
}
// ============================================
export interface Listener<P> {
(payload: P): void
}
export interface Event<K extends keyof EventPayloadMap = keyof EventPayloadMap> {
name: keyof EventPayloadMap
listener: Listener<EventPayloadMap[K]>
}
+264
View File
@@ -0,0 +1,264 @@
import Context from '../Context'
import { CommentData, ListData, UserData, PageData, SiteData, NotifyData } from '~/types/artalk-data'
import { Fetch, ToFormData, POST, GET } from './request'
export default class Api {
private ctx: Context
private baseURL: string
constructor (ctx: Context) {
this.ctx = ctx
this.baseURL = ctx.conf.server
}
// ============================
// 评论 Comment
// ============================
/** 评论 · 获取 */
public get(offset: number, type?: string, flatMode?: boolean, paramsEditor?: (params: any) => void) {
const params: any = {
page_key: this.ctx.conf.pageKey,
site_name: this.ctx.conf.site || '',
limit: this.ctx.conf.readMore?.pageSize || 15,
offset,
}
if (type) params.type = type
if (flatMode) params.flat_mode = flatMode // 平铺模式
if (this.ctx.user.checkHasBasicUserInfo()) {
params.name = this.ctx.user.data.nick
params.email = this.ctx.user.data.email
}
if (paramsEditor) paramsEditor(params)
return POST<ListData>(this.ctx, `${this.baseURL}/get`, params)
}
/** 评论 · 创建 */
public async add(comment: { nick: string, email: string, link: string, content: string, rid: number }) {
const params: any = {
name: comment.nick,
email: comment.email,
link: comment.link,
content: comment.content,
rid: comment.rid,
page_key: this.ctx.conf.pageKey,
page_title: this.ctx.conf.pageTitle || '',
}
if (this.ctx.conf.site) params.site_name = this.ctx.conf.site
const data = await POST<any>(this.ctx, `${this.baseURL}/add`, params)
return (data.comment as CommentData)
}
/** 评论 · 修改 */
public async commentEdit(data: CommentData) {
const params: any = {
...data,
}
const d = await POST<any>(this.ctx, `${this.baseURL}/admin/comment-edit`, params)
return (d.comment as CommentData)
}
/** 评论 · 删除 */
public commentDel(commentID: number, siteName?: string) {
const params: any = {
id: String(commentID),
site_name: siteName || '',
}
return POST(this.ctx, `${this.baseURL}/admin/comment-del`, params)
}
// ============================
// 用户 User
// ============================
/** 用户 · 登录 */
public async login(name: string, email: string, password: string) {
const params: any = {
name, email, password
}
if (this.ctx.conf.site) params.site_name = this.ctx.conf.site
const data = await POST<any>(this.ctx, `${this.baseURL}/login`, params)
return (data.token as string)
}
/** 用户 · 获取 */
public userGet(name: string, email: string) {
const ctrl = new AbortController()
const { signal } = ctrl
const params: any = {
name, email, site_name: (this.ctx.conf.site || '')
}
const req = Fetch(this.ctx, `${this.baseURL}/user-get`, {
method: 'POST',
body: ToFormData(params),
signal,
}).then((json) => ({
user: json.data.user as UserData|null,
is_login: json.data.is_login as boolean,
unread: (json.data.unread || []) as NotifyData[],
unread_count: json.data.unread_count || 0,
}))
return {
req,
abort: () => { ctrl.abort() },
}
}
// ============================
// 页面 Page
// ============================
/** 页面 · 获取 */
public async pageGet(siteName?: string) {
const params: any = {
site_name: siteName || ''
}
const d = await POST<any>(this.ctx, `${this.baseURL}/admin/page-get`, params)
return (d.pages as PageData[])
}
/** 页面 · 修改 */
public async pageEdit(data: PageData) {
const params: any = {
id: data.id,
key: data.key,
title: data.title,
admin_only: data.admin_only,
site_name: data.site_name || this.ctx.conf.site,
}
const d = await POST<any>(this.ctx, `${this.baseURL}/admin/page-edit`, params)
return (d.page as PageData)
}
/** 页面 · 删除 */
public pageDel(pageKey: string, siteName?: string) {
const params: any = {
key: String(pageKey),
site_name: siteName || '',
}
return POST(this.ctx, `${this.baseURL}/admin/page-del`, params)
}
/** 页面 · 数据更新 */
public async pageFetch(id: number) {
const params: any = {
id,
}
const d = await POST<any>(this.ctx, `${this.baseURL}/admin/page-fetch`, params)
return (d.page as PageData)
}
// ============================
// 站点 Site
// ============================
/** 站点 · 获取 */
public async siteGet() {
const params: any = {}
const d = await POST<any>(this.ctx, `${this.baseURL}/admin/site-get`, params)
return (d.sites as SiteData[])
}
/** 站点 · 创建 */
public async siteAdd(name: string, urls: string) {
const params: any = {
name, urls,
}
const d = await POST<any>(this.ctx, `${this.baseURL}/admin/site-add`, params)
return (d.site as SiteData)
}
/** 站点 · 修改 */
public async siteEdit(id: number, data: {
name: string
urls: string
}) {
const params: any = {
id,
name: data.name,
urls: data.urls,
}
const d = await POST<any>(this.ctx, `${this.baseURL}/admin/site-edit`, params)
return (d.site as SiteData)
}
/** 站点 · 删除 */
public siteDel(id: number, delContent = false) {
const params: any = { id, del_content: delContent }
return POST(this.ctx, `${this.baseURL}/admin/site-del`, params)
}
// ============================
// 杂项
// ============================
/** 投票 */
public async vote(targetID: number, type: string) {
const params: any = {
site_name: this.ctx.conf.site || '',
target_id: targetID,
type,
}
if (this.ctx.user.checkHasBasicUserInfo()) {
params.name = this.ctx.user.data.nick
params.email = this.ctx.user.data.email
}
const data = await POST<any>(this.ctx, `${this.baseURL}/vote`, params)
return ((data?.vote_num || 0) as number)
}
/** 已读标记 */
public markRead(notifyKey: string, readAll = false) {
const params: any = {
site_name: this.ctx.conf.site || '',
notify_key: notifyKey,
}
if (readAll) {
delete params.notify_key
params.read_all = true
params.name = this.ctx.user.data.nick
params.email = this.ctx.user.data.email
}
return POST(this.ctx, `${this.baseURL}/mark-read`, params)
}
// ============================
// 验证码
// ============================
/** 验证码 · 获取 */
public async captchaGet() {
const data = await GET<any>(this.ctx, `${this.baseURL}/captcha/refresh`)
return (data.img_data || '') as string
}
/** 验证码 · 检验 */
public async captchaCheck(value: string) {
const data = await GET<any>(this.ctx, `${this.baseURL}/captcha/check`, { value })
return (data.img_data || '') as string
}
}
+118
View File
@@ -0,0 +1,118 @@
import Context from '../Context'
/** 公共请求函数 */
export async function Fetch(ctx: Context, input: RequestInfo, init: RequestInit): Promise<any> {
// JWT
if (ctx.user.data.token) {
const requestHeaders: HeadersInit = new Headers()
requestHeaders.set('Authorization', `Bearer ${ctx.user.data.token}`)
init.headers = requestHeaders
}
// 15s timeout default
try {
const resp = await timeoutPromise(ctx.conf.reqTimeout || 15000, fetch(input, init))
if (!resp.ok && resp.status !== 401) throw new Error(`请求响应 ${resp.status}`)
// 解析获取响应的 json
let json: any = await resp.json()
// 重新发起请求
const recall = (resolve, reject) => {
Fetch(ctx, input, init).then(d => {
resolve(d)
}).catch(err => {
reject(err)
})
}
// 请求弹出层验证
if (json.data && json.data.need_captcha) {
// 请求需要验证码
json = await (new Promise<any>((resolve, reject) => {
ctx.trigger('checker-captcha', {
imgData: json.data.img_data,
onSuccess: () => {
recall(resolve, reject)
},
onCancel: () => {
reject(json)
}
})
}))
} else if ((json.data && json.data.need_login) || resp.status === 401) {
// 请求需要管理员权限
json = await (new Promise<any>((resolve, reject) => {
ctx.trigger('checker-admin', {
onSuccess: () => {
recall(resolve, reject)
},
onCancel: () => {
reject(json)
}
})
}))
}
if (!json.success) throw json // throw 相当于 reject(json)
return json
} catch (err) {
// 错误处理
console.error(err)
if (err instanceof TypeError)
throw new Error(`网络错误`)
throw err
}
}
/** 公共 POST 请求 */
export async function POST<T>(ctx: Context, url: string, data?: {[key: string]: any}) {
const init: RequestInit = {
method: 'POST',
}
if (data) init.body = ToFormData(data)
const json = await Fetch(ctx, url, init)
return ((json.data || {}) as T)
}
/** 公共 GET 请求 */
export async function GET<T>(ctx: Context, url: string, data?: {[key: string]: any}) {
const json = await Fetch(ctx, url + (data ? (`?${new URLSearchParams(data)}`) : ''), {
method: 'GET',
})
return ((json.data || {}) as T)
}
/** 对象转 FormData */
export function ToFormData(object: {[key: string]: any}): FormData {
const formData = new FormData()
Object.keys(object).forEach(key => formData.append(key, String(object[key])))
return formData
}
/** 我靠,fetch 一个 timeout,都要丑陋的实现 */
function timeoutPromise<T>(ms: number, promise: Promise<T>): Promise<T> {
return new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => {
reject(new Error("请求超时"))
}, ms)
promise.then(
(res) => {
clearTimeout(timeoutId)
resolve(res)
},
(err) => {
clearTimeout(timeoutId)
reject(err)
}
)
})
}
+16 -7
View File
@@ -7,7 +7,7 @@ import * as Ui from '../lib/ui'
import UADetect from '../lib/detect'
import { CommentData } from '~/types/artalk-data'
import CommentHTML from './html/comment.html?raw'
import Api from '../lib/api'
import Api from '../api'
export default class Comment extends Component {
public data: CommentData
@@ -20,7 +20,7 @@ export default class Comment extends Component {
public parent: Comment|null
public nestedNum: number
private readonly maxNestingNum = 3 // 最多嵌套层数
private maxNestingNum: number // 最多嵌套层数
public children: Comment[] = []
public replyTo?: CommentData // 回复对象(flatMode 用)
@@ -35,6 +35,9 @@ export default class Comment extends Component {
constructor (ctx: Context, data: CommentData) {
super(ctx)
// 最大嵌套数
this.maxNestingNum = ctx.conf.maxNesting || 3
this.data = { ...data }
this.data.date = this.data.date.replace(/-/g, '/') // 解决 Safari 日期解析 NaN 问题
@@ -130,7 +133,9 @@ export default class Comment extends Component {
<div class="atk-content"></div>
</div>`)
replyToEl.querySelector<HTMLElement>('.atk-nick')!.innerText = `@${this.replyTo.nick}`
replyToEl.querySelector<HTMLElement>('.atk-content')!.innerHTML = Utils.marked(this.ctx, this.replyTo.content)
let replyContent = Utils.marked(this.ctx, this.replyTo.content)
if (this.replyTo.is_collapsed) replyContent = '[已折叠]'
replyToEl.querySelector<HTMLElement>('.atk-content')!.innerHTML = replyContent
this.bodyEl.prepend(replyToEl)
}
@@ -167,6 +172,8 @@ export default class Comment extends Component {
child.parent?.getChildrenEl().appendChild(child.renderElem())
child.playFadeInAnim()
})
this.ctx.trigger('comments-loaded')
}
initActionBtn () {
@@ -182,7 +189,8 @@ export default class Comment extends Component {
}
refreshVote()
voteBtnUp.onclick = () => {
voteBtnUp.onclick = (e) => {
e.stopPropagation() // 防止穿透
new Api(this.ctx).vote(this.data.id, "comment_up")
.then((num) => {
this.data.vote_up = num
@@ -192,7 +200,8 @@ export default class Comment extends Component {
window.alert(`投票失败:${err.msg || String(err)}`)
})
}
voteBtnDown.onclick = () => {
voteBtnDown.onclick = (e) => {
e.stopPropagation()
new Api(this.ctx).vote(this.data.id, "comment_down")
.then((num) => {
this.data.vote_down = num
@@ -208,7 +217,7 @@ export default class Comment extends Component {
const replyBtn = Utils.createElement(`<span data-atk-action="comment-reply">回复</span>`)
this.actionsEl.append(replyBtn)
replyBtn.addEventListener('click', (e) => {
this.ctx.dispatchEvent('editor-reply', this.data)
this.ctx.trigger('editor-reply', this.data)
e.stopPropagation() // 防止穿透
})
}
@@ -328,7 +337,7 @@ export default class Comment extends Component {
this.data = comment
this.refreshUI()
Ui.playFadeInAnim(this.bodyEl)
this.ctx.dispatchEvent('list-refresh-ui')
this.ctx.trigger('list-refresh-ui')
}).catch((err) => {
console.error(err)
btnElem.classList.add('atk-error')
+17 -13
View File
@@ -9,7 +9,7 @@ import EditorHTML from './html/editor.html?raw'
import EmoticonsPlug from './editor-plugs/EmoticonsPlug'
import PreviewPlug from './editor-plugs/PreviewPlug'
import { CommentData } from '~/types/artalk-data'
import Api from '../lib/api'
import Api from '../api'
export default class Editor extends Component {
private readonly LOADABLE_PLUG_LIST = [EmoticonsPlug, PreviewPlug]
@@ -60,12 +60,12 @@ export default class Editor extends Component {
this.initBottomPart()
// 监听事件
this.ctx.addEventListener('editor-open-comment', () => (this.openComment()))
this.ctx.addEventListener('editor-close-comment', () => (this.closeComment()))
this.ctx.addEventListener('editor-reply', (commentData) => (this.setReply(commentData)))
this.ctx.addEventListener('editor-show-loading', () => (Ui.showLoading(this.el)))
this.ctx.addEventListener('editor-hide-loading', () => (Ui.hideLoading(this.el)))
this.ctx.addEventListener('editor-notify', (f) => (this.showNotify(f.msg, f.type)))
this.ctx.on('editor-open', () => (this.open()))
this.ctx.on('editor-close', () => (this.close()))
this.ctx.on('editor-reply', (commentData) => (this.setReply(commentData)))
this.ctx.on('editor-show-loading', () => (Ui.showLoading(this.el)))
this.ctx.on('editor-hide-loading', () => (Ui.hideLoading(this.el)))
this.ctx.on('editor-notify', (f) => (this.showNotify(f.msg, f.type)))
}
initLocalStorage () {
@@ -127,7 +127,7 @@ export default class Editor extends Component {
}
// 未读消息更新
this.ctx.dispatchEvent('unread-update', { notifies: data.unread, })
this.ctx.trigger('unread-update', { notifies: data.unread, })
// 若用户为管理员,执行登陆操作
if (this.user.checkHasBasicUserInfo() && !data.is_login && data.user && data.user.is_admin) {
@@ -150,7 +150,7 @@ export default class Editor extends Component {
}
showLoginDialog () {
this.ctx.dispatchEvent('checker-admin', {
this.ctx.trigger('checker-admin', {
onSuccess: () => {
}
})
@@ -158,7 +158,7 @@ export default class Editor extends Component {
saveUser () {
this.user.save()
this.ctx.dispatchEvent('user-changed')
this.ctx.trigger('user-changed', this.ctx.user.data)
}
saveContent () {
@@ -358,6 +358,8 @@ export default class Editor extends Component {
return
}
this.ctx.trigger('editor-submit')
Ui.showLoading(this.el)
try {
@@ -369,9 +371,11 @@ export default class Editor extends Component {
rid: this.replyComment === null ? 0 : this.replyComment.id
})
this.ctx.dispatchEvent('list-insert', nComment)
this.ctx.trigger('list-insert', nComment)
this.clearEditor() // 清空编辑器
this.ctx.trigger('editor-submitted')
} catch (err: any) {
console.error(err)
this.showNotify(`评论失败,${err.msg || String(err)}`, 'e')
} finally {
Ui.hideLoading(this.el)
@@ -383,7 +387,7 @@ export default class Editor extends Component {
}
/** 关闭评论 */
closeComment () {
close () {
this.closeCommentEl.style.display = ''
if (!this.user.data.isAdmin) {
@@ -398,7 +402,7 @@ export default class Editor extends Component {
}
/** 打开评论 */
openComment () {
open () {
this.closeCommentEl.style.display = 'none'
this.textareaEl.style.display = ''
this.bottomEl.style.display = ''
+20 -18
View File
@@ -3,7 +3,7 @@ import '../style/list.less'
import Context from '../Context'
import * as Utils from '../lib/utils'
import * as Ui from '../lib/ui'
import Api from '../lib/api'
import Api from '../api'
import ListHTML from './html/list.html?raw'
import ListLite from './ListLite'
import { ListData } from '~/types/artalk-data'
@@ -21,19 +21,21 @@ export default class List extends ListLite {
el.querySelector('.atk-list-body')!.append(this.el)
this.el = el
// 平铺模式
this.flatMode = this.ctx.conf.flatMode || false
// 操作按钮
this.initListActionBtn()
this.el.querySelector<HTMLElement>('.atk-copyright')!.innerHTML = `Powered By <a href="https://artalk.js.org" target="_blank" title="Artalk v${ARTALK_VERSION}">Artalk</a>`
this.ctx.addEventListener('list-reload', () => (this.reqComments(0))) // 刷新评论
this.ctx.addEventListener('list-clear', () => (this.clearComments())) // 清空评论
this.ctx.addEventListener('list-refresh-ui', () => (this.refreshUI()))
this.ctx.addEventListener('list-import', (data) => (this.importComments(data)))
this.ctx.addEventListener('list-insert', (data) => (this.insertComment(data)))
this.ctx.addEventListener('list-comment-del', (comment) => (this.deleteComment(comment.id)))
this.ctx.addEventListener('list-update-data', (updateData) => { updateData(this.data);this.refreshUI() } )
this.ctx.addEventListener('unread-update', (data) => (this.showUnreadBadge(data.notifies?.length || 0)))
this.ctx.on('list-reload', () => (this.reqComments(0))) // 刷新评论
this.ctx.on('list-refresh-ui', () => (this.refreshUI()))
this.ctx.on('list-import', (data) => (this.importComments(data)))
this.ctx.on('list-insert', (data) => (this.insertComment(data)))
this.ctx.on('list-delete', (comment) => (this.deleteComment(comment.id)))
this.ctx.on('list-update', (updateData) => { updateData(this.data);this.refreshUI() } )
this.ctx.on('unread-update', (data) => (this.showUnreadBadge(data.notifies?.length || 0)))
}
/** 刷新界面 */
@@ -50,14 +52,14 @@ export default class List extends ListLite {
}
// 仅管理员显示控制
this.ctx.dispatchEvent('check-admin-show-el')
this.ctx.trigger('check-admin-show-el')
// 关闭评论
if (!!this.data && !!this.data.page && this.data.page.admin_only === true) {
this.ctx.dispatchEvent('editor-close-comment')
this.ctx.trigger('editor-close')
this.closeCommentBtnEl.innerHTML = '打开评论'
} else {
this.ctx.dispatchEvent('editor-open-comment')
this.ctx.trigger('editor-open')
this.closeCommentBtnEl.innerHTML = '关闭评论'
}
}
@@ -98,7 +100,7 @@ export default class List extends ListLite {
new Api(this.ctx).markRead(notifyKey)
.then(() => {
this.unread = this.unread.filter(o => o.comment_id !== comment.data.id)
this.ctx.dispatchEvent('unread-update', {
this.ctx.trigger('unread-update', {
notifies: this.unread
})
})
@@ -110,13 +112,13 @@ export default class List extends ListLite {
// 侧边栏呼出按钮
this.openSidebarBtnEl = this.el.querySelector('[data-action="open-sidebar"]')!
this.openSidebarBtnEl.addEventListener('click', () => {
this.ctx.dispatchEvent('sidebar-show', { viewName: 'message' })
this.ctx.trigger('sidebar-show', { viewName: 'message' })
})
// 控制台呼出
this.openAdminPanelBtnEl = this.el.querySelector('[data-action="open-admin-panel"]')!
this.openAdminPanelBtnEl.addEventListener('click', () => {
this.ctx.dispatchEvent('sidebar-show', { viewName: 'admin' })
this.ctx.trigger('sidebar-show', { viewName: 'admin' })
})
@@ -136,7 +138,7 @@ export default class List extends ListLite {
public adminPageEditSave () {
if (!this.data || !this.data.page) return
this.ctx.dispatchEvent('editor-show-loading')
this.ctx.trigger('editor-show-loading')
new Api(this.ctx).pageEdit(this.data.page)
.then((page) => {
if (this.data)
@@ -144,10 +146,10 @@ export default class List extends ListLite {
this.refreshUI()
})
.catch(err => {
this.ctx.dispatchEvent('editor-notify', { msg: `修改页面数据失败:${err.msg || String(err)}`, type: 'e'})
this.ctx.trigger('editor-notify', { msg: `修改页面数据失败:${err.msg || String(err)}`, type: 'e'})
})
.finally(() => {
this.ctx.dispatchEvent('editor-hide-loading')
this.ctx.trigger('editor-hide-loading')
})
}
+82 -30
View File
@@ -2,9 +2,9 @@ import Context from '../Context'
import Component from '../lib/component'
import * as Utils from '../lib/utils'
import * as Ui from '../lib/ui'
import Api from '../lib/api'
import Api from '../api'
import Comment from './Comment'
import { ListData, CommentData, NotifyData } from '~/types/artalk-data'
import { ListData, CommentData, NotifyData, ApiVersionData } from '~/types/artalk-data'
export default class ListLite extends Component {
public el: HTMLElement
@@ -28,7 +28,7 @@ export default class ListLite extends Component {
private isLoading: boolean = false
public isFirstLoad = true
public flatMode = false
public flatMode?: boolean
public unread: NotifyData[] = []
public unreadHighlight = false
@@ -66,7 +66,7 @@ export default class ListLite extends Component {
})
}, 30 * 1000) // 30s 更新一次
this.ctx.addEventListener('unread-update', (data) => (this.updateUnread(data.notifies)))
this.ctx.on('unread-update', (data) => (this.updateUnread(data.notifies)))
}
public async reqComments(offset: number = 0) {
@@ -76,6 +76,7 @@ export default class ListLite extends Component {
// set loading
this.isLoading = true
this.ctx.trigger('comments-load')
if (offset === 0) Ui.showLoading(this.el)
else this.readMoreBtnSetLoading(true)
@@ -88,7 +89,7 @@ export default class ListLite extends Component {
let listData: ListData
try {
listData = await new Api(this.ctx).get(offset, this.type, this.paramsEditor)
listData = await new Api(this.ctx).get(offset, this.type, this.flatMode, this.paramsEditor)
} catch (e: any) {
this.onError(e.msg || String(e))
throw e
@@ -96,6 +97,12 @@ export default class ListLite extends Component {
hideLoading()
}
// version check
if (this.ctx.conf.versionCheck) {
const needUpdate = this.apiVersionCheck(listData.api_version || {})
if (needUpdate) return
}
// load data
this.offset = offset
@@ -130,7 +137,7 @@ export default class ListLite extends Component {
this.initScrollBottomAutoLoad()
}
this.ctx.dispatchEvent('unread-update', { notifies: data.unread || [] })
this.ctx.trigger('unread-update', { notifies: data.unread || [] })
this.isFirstLoad = false
}
@@ -147,7 +154,7 @@ export default class ListLite extends Component {
errEl.appendChild(retryBtn)
const adminBtn = Utils.createElement('<span atk-only-admin-show> | <span style="cursor:pointer;">打开控制台</span></span>')
adminBtn.onclick = () => {
this.ctx.dispatchEvent('sidebar-show', { viewName: 'admin' })
this.ctx.trigger('sidebar-show', { viewName: 'admin' })
}
if (!this.ctx.user.data.isAdmin) {
adminBtn.classList.add('atk-hide')
@@ -202,42 +209,64 @@ export default class ListLite extends Component {
queryImportChildren(rootComment)
})
} else {
// 平铺模式
rawData.forEach((commentData: CommentData) => {
if (commentData.is_collapsed) commentData.is_allow_reply = false
const comment = this.createComment(commentData)
if (commentData.rid !== 0) {
const rComment = rawData.find(o => o.id === commentData.rid)
if (rComment) comment.replyTo = rComment
}
comment.renderElem()
this.comments.push(comment) // 将评论导入 comments 总表中
if (commentData.visible) {
this.commentsWrapEl.appendChild(comment.getEl())
comment.playFadeInAnim()
}
this.putCommentFlatMode(commentData, rawData, 'append')
})
}
this.refreshUI()
this.ctx.trigger('comments-loaded')
}
public insertComment (commentData: CommentData) {
const comment = this.createComment(commentData)
private putCommentFlatMode (commentItem: CommentData, comments: CommentData[], insertMode: 'append'|'prepend') {
if (commentItem.is_collapsed) commentItem.is_allow_reply = false
const comment = this.createComment(commentItem)
if (commentItem.rid !== 0) {
const rComment = comments.find(o => o.id === commentItem.rid)
if (rComment) comment.replyTo = rComment
}
comment.renderElem()
if (commentData.rid !== 0) {
this.findComment(commentData.rid)?.putChild(comment)
// 将评论导入 comments 总表中
if (insertMode === 'append') {
this.comments.push(comment)
} else {
this.commentsWrapEl.prepend(comment.getEl())
this.comments.unshift(comment)
}
Ui.scrollIntoView(comment.getEl()) // 滚动到可以见
comment.playFadeInAnim() // 播放评论渐出动画
if (commentItem.visible) {
if (insertMode === 'append') {
this.commentsWrapEl.appendChild(comment.getEl())
} else {
this.commentsWrapEl.prepend(comment.getEl())
}
comment.playFadeInAnim()
}
}
public insertComment (commentData: CommentData) {
if (!this.flatMode) {
const comment = this.createComment(commentData)
comment.renderElem()
if (commentData.rid !== 0) {
this.findComment(commentData.rid)?.putChild(comment)
} else {
this.commentsWrapEl.prepend(comment.getEl())
this.comments.unshift(comment)
}
Ui.scrollIntoView(comment.getEl()) // 滚动到可以见
comment.playFadeInAnim() // 播放评论渐出动画
} else {
this.putCommentFlatMode(commentData, this.comments.map(c => c.data), 'prepend')
}
if (this.data) this.data.total += 1 // 评论数增加 1
this.refreshUI() // 更新 list 界面
this.ctx.trigger('comments-loaded')
}
/** 刷新界面 */
@@ -258,7 +287,7 @@ export default class ListLite extends Component {
noCommentEl.remove()
// 仅管理员显示控制
this.ctx.dispatchEvent('check-admin-show-el')
this.ctx.trigger('check-admin-show-el')
}
/** 获取评论总数 (包括子评论) */
@@ -394,7 +423,7 @@ export default class ListLite extends Component {
comment.setOpenURL(notify.read_link)
comment.openEvt = () => {
this.unread = this.unread.filter(o => o.comment_id !== comment.data.id) // remove
this.ctx.dispatchEvent('unread-update', {
this.ctx.trigger('unread-update', {
notifies: this.unread
})
}
@@ -404,4 +433,27 @@ export default class ListLite extends Component {
})
}
}
public apiVersionCheck(versionData: ApiVersionData): boolean {
const needVersion = versionData?.fe_min_version || '0.0.0'
const needUpdate = Utils.versionCompare(needVersion, ARTALK_VERSION) === 1
if (needUpdate) {
// 需要更新
const errEl = Utils.createElement(`<div>前端 Artalk 版本已过时,请更新以获得完整体验<br/>`
+ `若您是站点管理员,请前往 “<a href="https://artalk.js.org/" target="_blank">官方文档</a>” 获取帮助`
+ `<br/><br/>`
+ `<span style="color: var(--at-color-meta);">前端版本 ${ARTALK_VERSION},需求版本 >= ${needVersion}</span><br/><br/>`
+ `</div>`)
const ignoreBtn = Utils.createElement('<span style="cursor:pointer;">忽略</span>')
ignoreBtn.onclick = () => {
Ui.setError(this.ctx, null)
this.ctx.conf.versionCheck = false
this.reqComments(0)
}
errEl.append(ignoreBtn)
Ui.setError(this.ctx, errEl, '<span class="atk-warn-title">Artalk Warn</span>')
}
return needUpdate
}
}
+2 -2
View File
@@ -42,8 +42,8 @@ export default class Sidebar extends Component {
})
// event
this.ctx.addEventListener('sidebar-show', (payload) => (this.show(payload?.viewName)))
this.ctx.addEventListener('sidebar-hide', () => (this.hide()))
this.ctx.on('sidebar-show', (payload) => (this.show(payload?.viewName)))
this.ctx.on('sidebar-hide', () => (this.hide()))
// titles
this.registerViews.forEach(View => {
+11 -5
View File
@@ -4,7 +4,7 @@ import * as Utils from '../../lib/utils'
import Comment from '../Comment'
import ListLite from '../ListLite'
import { CreateCommentList } from './MessageView'
import Api from '~/src/lib/api'
import Api from '../../api'
export default class AdminView extends SidebarView {
name = 'admin'
@@ -227,8 +227,11 @@ export default class AdminView extends SidebarView {
delBtn.onclick = () => {
const del = () => {
new Api(this.ctx).pageDel(page.key, page.site_name)
.then(success => {
if (success) pageItemEl.remove()
.then(() => {
pageItemEl.remove()
})
.catch((err) => {
window.alert(`删除失败 ${String(err)}`)
})
}
if (window.confirm(`确认删除页面 "${page.title || page.key}"?将会删除所有相关数据`)) del()
@@ -310,8 +313,11 @@ export default class AdminView extends SidebarView {
delBtn.onclick = () => {
const del = () => {
new Api(this.ctx).siteDel(site.id, true)
.then(success => {
if (success) siteItemEl.remove()
.then(() => {
siteItemEl.remove()
})
.catch((err) => {
window.alert(`删除失败 ${String(err)}`)
})
}
if (window.confirm(`确认删除站点 "${site.name || site.first_url}"?将会删除所有相关数据`))
-380
View File
@@ -1,380 +0,0 @@
import { CommentData, ListData, UserData, PageData, SiteData, NotifyData } from '~/types/artalk-data'
import Context from '../Context'
export default class Api {
private ctx: Context
private serverURL: string
constructor (ctx: Context) {
this.ctx = ctx
this.serverURL = ctx.conf.server
}
public get(offset: number, type?: string, paramsEditor?: (params: any) => void): Promise<ListData> {
const params: any = {
page_key: this.ctx.conf.pageKey,
limit: this.ctx.conf.readMore?.pageSize || 15,
offset,
}
if (type) {
params.type = type
}
if (this.ctx.user.checkHasBasicUserInfo()) {
params.name = this.ctx.user.data.nick
params.email = this.ctx.user.data.email
}
if (this.ctx.conf.site) params.site_name = this.ctx.conf.site
if (paramsEditor) paramsEditor(params)
return CommonFetch(this.ctx, `${this.serverURL}/get`, {
method: 'POST',
body: getFormData(params),
}).then((json) => (json.data as ListData))
}
public add(comment: { nick: string, email: string, link: string, content: string, rid: number }): Promise<CommentData> {
const params: any = {
name: comment.nick,
email: comment.email,
link: comment.link,
content: comment.content,
rid: comment.rid,
page_key: this.ctx.conf.pageKey,
page_url: this.ctx.conf.pageUrl || '',
page_title: this.ctx.conf.pageTitle || '',
}
if (this.ctx.conf.site) params.site_name = this.ctx.conf.site
return CommonFetch(this.ctx, `${this.serverURL}/add`, {
method: 'POST',
body: getFormData(params),
}).then((json) => (json.data.comment as CommentData))
}
public login(name: string, email: string, password: string): Promise<string> {
const params: any = {
name, email, password
}
if (this.ctx.conf.site) params.site_name = this.ctx.conf.site
return CommonFetch(this.ctx, `${this.serverURL}/login`, {
method: 'POST',
body: getFormData(params),
}).then((json) => (json.data.token))
}
public userGet(name: string, email: string) {
const ctrl = new AbortController()
const { signal } = ctrl
const params: any = {
name, email
}
if (this.ctx.conf.site) params.site_name = this.ctx.conf.site
const req = CommonFetch(this.ctx, `${this.serverURL}/user-get`, {
method: 'POST',
body: getFormData(params),
signal,
}).then((json) => ({
user: json.data.user as UserData|null,
is_login: json.data.is_login as boolean,
unread: (json.data.unread || []) as NotifyData[],
unread_count: json.data.unread_count || 0,
}))
return {
req,
abort: () => { ctrl.abort() },
}
}
public vote(targetID: number, type: string) {
const params: any = {
target_id: targetID,
type,
}
if (this.ctx.user.checkHasBasicUserInfo()) {
params.name = this.ctx.user.data.nick
params.email = this.ctx.user.data.email
}
if (this.ctx.conf.site) params.site_name = this.ctx.conf.site
return CommonFetch(this.ctx, `${this.serverURL}/vote`, {
method: 'POST',
body: getFormData(params),
}).then((json) => ((json.data?.vote_num || 0) as number))
}
public markRead(notifyKey: string, readAll = false) {
const params: any = {
notify_key: notifyKey,
}
if (readAll) {
delete params.notify_key
params.read_all = true
params.name = this.ctx.user.data.nick
params.email = this.ctx.user.data.email
}
if (this.ctx.conf.site) params.site_name = this.ctx.conf.site
return CommonFetch(this.ctx, `${this.serverURL}/mark-read`, {
method: 'POST',
body: getFormData(params),
}).then((json) => (json.success as boolean))
}
public captchaGet(): Promise<string> {
return CommonFetch(this.ctx, `${this.serverURL}/captcha/refresh`, {
method: 'GET',
}).then((json) => {
if (!!json.success && !!json.data.img_data) {
return json.data.img_data
}
return ''
})
}
public captchaCheck(value: string): Promise<string> {
return CommonFetch(this.ctx, `${this.serverURL}/captcha/check?${new URLSearchParams({ value })}`, {
method: 'GET',
}).then((json) => {
if (!json.success && !!json.data.img_data) {
return json.data.img_data
}
return ''
})
}
public commentEdit(data: CommentData) {
const params: any = {
...data,
}
if (params.is_collapsed !== undefined) params.is_collapsed = params.is_collapsed ? '1' : '0'
if (params.is_pending !== undefined) params.is_pending = params.is_pending ? '1' : '0'
if (this.ctx.conf.site) params.site_name = this.ctx.conf.site
return CommonFetch(this.ctx, `${this.serverURL}/admin/comment-edit`, {
method: 'POST',
body: getFormData(params),
}).then((json) => (json.data.comment as CommentData))
}
public pageEdit(data: PageData) {
const params: any = {
id: data.id,
key: data.key,
title: data.title,
admin_only: data.admin_only ? '1' : '0',
}
if (this.ctx.conf.site) params.site_name = this.ctx.conf.site
return CommonFetch(this.ctx, `${this.serverURL}/admin/page-edit`, {
method: 'POST',
body: getFormData(params),
}).then((json) => (json.data.page as PageData))
}
public pageFetch(id: number) {
const params: any = {
id,
}
return CommonFetch(this.ctx, `${this.serverURL}/admin/page-fetch`, {
method: 'POST',
body: getFormData(params),
}).then((json) => (json.data.page as PageData))
}
public siteGet(): Promise<SiteData[]> {
const params: any = {}
return CommonFetch(this.ctx, `${this.serverURL}/admin/site-get`, {
method: 'POST',
body: getFormData(params),
}).then((json) => (json.data.sites as SiteData[]))
}
public pageGet(siteName?: string): Promise<PageData[]> {
const params: any = {
site_name: siteName || ''
}
return CommonFetch(this.ctx, `${this.serverURL}/admin/page-get`, {
method: 'POST',
body: getFormData(params),
}).then((json) => (json.data.pages as PageData[]))
}
public pageDel(pageKey: string, siteName?: string) {
const params: any = {
key: String(pageKey),
site_name: siteName || '',
}
return CommonFetch(this.ctx, `${this.serverURL}/admin/page-del`, {
method: 'POST',
body: getFormData(params),
}).then((json) => (json.success as boolean))
}
public siteDel(id: number, delContent = false) {
const params: any = {
id,
del_content: delContent ? '1' : '0'
}
return CommonFetch(this.ctx, `${this.serverURL}/admin/site-del`, {
method: 'POST',
body: getFormData(params),
}).then((json) => (json.success as boolean))
}
public siteAdd(name: string, urls: string) {
const params: any = {
name, urls,
}
return CommonFetch(this.ctx, `${this.serverURL}/admin/site-add`, {
method: 'POST',
body: getFormData(params),
}).then((json) => (json.site as SiteData))
}
public siteEdit(id: number, data: {
name: string
urls: string
}) {
const params: any = {
id,
name: data.name,
urls: data.urls,
}
return CommonFetch(this.ctx, `${this.serverURL}/admin/site-edit`, {
method: 'POST',
body: getFormData(params),
}).then((json) => (json.site as SiteData))
}
public commentDel(commentID: number, siteName?: string) {
const params: any = {
id: String(commentID),
site_name: siteName || '',
}
return CommonFetch(this.ctx, `${this.serverURL}/admin/comment-del`, {
method: 'POST',
body: getFormData(params),
}).then((json) => (json.success as boolean))
}
}
/** 公共请求函数 */
function CommonFetch(ctx: Context, input: RequestInfo, init: RequestInit): Promise<any> {
if (ctx.user.data.token) {
const requestHeaders: HeadersInit = new Headers();
requestHeaders.set('Authorization', `Bearer ${ctx.user.data.token}`);
init.headers = requestHeaders
}
// 15s timeout
return timeoutPromise(15000, fetch(input, init)).then(async (resp) => {
if (!resp.ok && resp.status !== 401) {
throw new Error(`请求响应 ${resp.status}`)
}
// 解析获取响应的 json
let json: any = await resp.json()
// 重新发起请求
const recall = (resolve, reject) => {
CommonFetch(ctx, input, init).then(d => {
resolve(d)
}).catch(err => {
reject(err)
})
}
if (json.data && json.data.need_captcha) {
// 请求需要验证码
json = await (new Promise<any>((resolve, reject) => {
ctx.dispatchEvent('checker-captcha', {
imgData: json.data.img_data,
onSuccess: () => {
recall(resolve, reject)
},
onCancel: () => {
reject(json)
}
})
}))
} else if ((json.data && json.data.need_login) || resp.status === 401) {
// 请求需要管理员权限
json = await (new Promise<any>((resolve, reject) => {
ctx.dispatchEvent('checker-admin', {
onSuccess: () => {
recall(resolve, reject)
},
onCancel: () => {
reject(json)
}
})
}))
}
if (!json.success)
throw json // throw 相当于 reject(json)
else
return json
})
.catch(err => {
console.error(err)
if (err instanceof TypeError)
throw new Error(`网络错误`)
throw err
})
}
function getFormData (object: any): FormData {
const formData = new FormData()
Object.keys(object).forEach(key => formData.append(key, String(object[key])))
return formData
}
/** TODO: 我靠,一个 timeout,都要丑陋的实现 */
function timeoutPromise<T>(ms: number, promise: Promise<T>): Promise<T> {
return new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => {
reject(new Error("请求超时"))
}, ms);
promise.then(
(res) => {
clearTimeout(timeoutId);
resolve(res);
},
(err) => {
clearTimeout(timeoutId);
reject(err);
}
);
})
}
+7 -7
View File
@@ -2,9 +2,9 @@ import Context from "../Context"
import * as Utils from "./utils"
import User from "./user"
import * as Ui from './ui'
import BuildLayer from "../components/Layer"
import Api from "./api"
import { CheckerCaptchaConf } from '../Event'
import BuildLayer from '../components/Layer'
import Api from '../api'
import { CheckerCaptchaConf } from '~/types/event'
interface CheckerConf {
el?: HTMLElement
@@ -29,8 +29,8 @@ const AdminChecker = {
that.user.data.isAdmin = true
that.user.data.token = userToken
that.user.save()
that.ctx.dispatchEvent('user-changed')
that.ctx.dispatchEvent('list-reload')
that.ctx.trigger('user-changed', that.ctx.user.data)
that.ctx.trigger('list-reload')
},
onError: (that, err, inputVal, formEl) => {
@@ -75,14 +75,14 @@ export default class Checker {
this.ctx = ctx
this.user = ctx.user
this.ctx.addEventListener('checker-captcha', (conf) => {
this.ctx.on('checker-captcha', (conf) => {
if (conf.imgData) {
this.submitCaptchaImgData = conf.imgData
}
this.check('captcha', conf)
})
this.ctx.addEventListener('checker-admin', (conf) => {
this.ctx.on('checker-admin', (conf) => {
this.check('admin', conf)
})
}
+2 -2
View File
@@ -168,7 +168,7 @@ export function playFadeOutAnim(elem: HTMLElement, after?: () => void) {
}
/** 设置全局错误 */
export function setError(parentElem: HTMLElement|Context, html: string | HTMLElement | null) {
export function setError(parentElem: HTMLElement|Context, html: string | HTMLElement | null, title: string = '<span class="atk-error-title">Artalk Error</span>') {
if (parentElem instanceof Context) parentElem = parentElem.rootEl
let elem = parentElem.querySelector<HTMLElement>('.atk-error-layer')
@@ -178,7 +178,7 @@ export function setError(parentElem: HTMLElement|Context, html: string | HTMLEle
}
if (!elem) {
elem = Utils.createElement(
'<div class="atk-error-layer"><span class="atk-error-title">Artalk Error</span><span class="atk-error-text"></span></div>'
`<div class="atk-error-layer">${title}<span class="atk-error-text"></span></div>`
)
parentElem.appendChild(elem)
}
+2 -8
View File
@@ -1,13 +1,7 @@
import { ArtalkConfig } from "~/types/artalk-config"
import { ArtalkConfig, LocalUser } from "~/types/artalk-config"
export default class User {
public data: {
nick: string,
email: string,
link: string,
token: string,
isAdmin: boolean
}
public data: LocalUser
constructor (conf: ArtalkConfig) {
// 从 localStorage 导入
+15
View File
@@ -99,6 +99,21 @@ export function sleep(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/** 版本号比较(a < b :-1 | 0 | b < a :1) */
export function versionCompare(a: string, b: string) {
const pa = a.split('.')
const pb = b.split('.')
for (let i = 0; i < 3; i++) {
const na = Number(pa[i])
const nb = Number(pb[i])
if (na > nb) return 1
if (nb > na) return -1
if (!Number.isNaN(na) && Number.isNaN(nb)) return 1
if (Number.isNaN(na) && !Number.isNaN(nb)) return -1
}
return 0
}
let markedInstance: any = null
export function marked (ctx: Context, src: string): string {
if (!markedInstance) {
+2 -2
View File
@@ -14,7 +14,7 @@
--at-color-main: #0083ff;
--at-color-red: #ff5652;
--at-color-pink: #fa5a57;
--at-color-yellow: #ffa928;
--at-color-yellow: #ff7c37;
--at-color-quote: #687a86;
}
@@ -33,7 +33,7 @@
--at-color-main: #0083ff;
--at-color-red: #ff5652;
--at-color-pink: #fa5a57;
--at-color-yellow: #ffa928;
--at-color-yellow: #ff7c37;
--at-color-quote: #687a86;
}
}
+20
View File
@@ -176,9 +176,29 @@
color: @color-red;
}
.atk-warn-title {
color: @color-yellow;
}
.atk-error-title, .atk-warn-title {
display: inline-block;
padding: 0 15px;
margin-bottom: 20px;
font-size: 20px;
letter-spacing: -0.5px;
}
.atk-error-text {
text-align: center;
padding: 0 20px;
* {
color: @color-deep;
}
a {
color: @color-meta;
}
}
}
+43 -45
View File
@@ -1,70 +1,41 @@
export interface ArtalkConfig {
/**
* 装载元素
*/
/** 装载元素 */
el: string
/**
* 评论框占位字符
*/
/** 评论框占位字符 */
placeholder?: string
/**
* 评论为空时显示字符
*/
/** 评论为空时显示字符 */
noComment?: string
/**
* 发送按钮文字
*/
/** 发送按钮文字 */
sendBtn?: string
/**
* 默认头像(URL or Gravatar Type)
*/
/** 默认头像(URL or Gravatar Type) */
defaultAvatar?: string
/**
* 页面唯一标识
*/
/** 页面唯一标识(完整 URL) */
pageKey: string
/**
* 页面可访问链接
*/
pageUrl?: string
/**
* 页面标题
*/
/** 页面标题 */
pageTitle?: string
/**
* 服务器地址
*/
/** 服务器地址 */
server: string
/**
* 站点名
*/
/** 站点名 */
site?: string
/**
* 表情包
*/
/** 表情包 */
emoticons?: object|any
/**
* 头像
*/
/** 头像 */
gravatar?: {
/** CDN 地址 */
cdn?: string
}
/**
* 查看更多配置
*/
/** 查看更多配置 */
readMore?: {
/** 每次请求获取数量 */
pageSize?: number
@@ -72,8 +43,35 @@ export interface ArtalkConfig {
autoLoad?: boolean
}
/**
* 暗黑模式
*/
/** 暗黑模式 */
darkMode?: boolean
/** 请求超时(单位:秒) */
reqTimeout?: number
/** 平铺模式 */
flatMode?: boolean
/** 最大嵌套数 */
maxNesting?: number
/** 版本检测 */
versionCheck?: boolean
}
export interface LocalUser {
/** 昵称 */
nick: string
/** 邮箱 */
email: string
/** 链接 */
link: string
/** TOKEN */
token: string
/** 是否为管理员 */
isAdmin: boolean
}
+32 -1
View File
@@ -76,11 +76,17 @@ export interface ListData {
/** 页面信息 */
page: PageData
/** 站点信息 */
site: SiteData // TODO
/** 未读的 Notifies */
unread: NotifyData[]
/** 未读计数 */
unread_count: number
sites?: SiteData[]
/** API 版本 */
api_version: ApiVersionData
}
export interface PageData {
@@ -144,10 +150,35 @@ export interface UserData {
}
export interface NotifyData {
/** 通知 ID */
id: number
/** 用户 ID */
user_id: number
/** 评论 ID */
comment_id: number
/** 是否已读 */
is_read: boolean
/** 是否已发送邮件通知 */
is_emailed: boolean
/** 标为已读地址 */
read_link: string
}
export interface ApiVersionData {
/** API 程序名 */
app: string
/** API 程序版本号 */
version: string
/** API 程序 CommitHash */
commit_hash: string
/** API 程序要求前端最小版本 */
fe_min_version: string
}
+77
View File
@@ -0,0 +1,77 @@
import { ListData, CommentData, NotifyData } from './artalk-data'
import { LocalUser } from './artalk-config'
/** EventName to EventPayload Type */
export interface EventPayloadMap {
'comments-load': undefined // 评论加载时
'comments-loaded': undefined // 评论装载后
'editor-submit': undefined // 编辑器提交时
'editor-submitted': undefined // 编辑器提交后
'user-changed': LocalUser // 本地用户数据变更时
// List 操作(外部:不建议 listen,仅 trigger)
'list-reload': undefined // 重新加载 List
'list-import': CommentData[] // 评论导入
'list-insert': CommentData // 评论添加
'list-delete': CommentData // 评论删除
'list-update': ListUpdatePayload // 更新评论数据
'list-refresh-ui': undefined // 刷新 List UI
// Sidebar
'sidebar-show'?: SidebarShowPayload // 侧边栏显示
'sidebar-hide': undefined // 侧边栏隐藏
// Editor
'editor-open': undefined // 打开评论
'editor-close': undefined // 关闭评论
'editor-show-loading': undefined // 加载显示
'editor-hide-loading': undefined // 加载隐藏
'editor-notify': NotifyConf // 显示提示
'editor-reply': CommentData // 设置回复
// Notify
'unread-update': UnreadUpdatePayload // 未读数据更新
// Checker
'checker-admin': CheckerConf // 检查管理员
'checker-captcha': CheckerCaptchaConf // 检查验证码
'check-admin-show-el': undefined // 检查并更新仅管理员显示的元素
}
export interface CheckerConf {
onSuccess?: (inputVal: string, dialogEl?: HTMLElement) => void
onMount?: (dialogEl: HTMLElement) => void
onCancel?: () => void
}
export interface NotifyConf {
msg: string
type: any
}
export interface CheckerCaptchaConf extends CheckerConf {
imgData?: string
}
export interface SidebarShowPayload {
viewName?: string
}
export interface UnreadUpdatePayload {
notifies: NotifyData[]
}
export type ListUpdatePayload = (data: ListData | undefined) => void
// ============================================
export interface Handler<P> {
(payload: P): void
}
export type EventScopeType = 'internal'|'external'
export interface Event<K extends keyof EventPayloadMap = keyof EventPayloadMap> {
name: keyof EventPayloadMap
handler: Handler<EventPayloadMap[K]>
scope: EventScopeType
}
+1 -6
View File
@@ -1,8 +1,3 @@
import { ArtalkConfig } from "./artalk-config"
import Artalk from '../src/Artalk'
declare class Artalk {
constructor (conf: ArtalkConfig)
}
export as namespace Artalk
export default Artalk
+1 -2
View File
@@ -1,6 +1,5 @@
import { defineConfig } from 'vite'
import { resolve } from 'path'
import dts from 'vite-plugin-dts'
import commonConf from './vite-common.config'
import tsconfigPaths from 'vite-tsconfig-paths'
@@ -32,6 +31,6 @@ export default defineConfig({
}
}
},
plugins: [dts(), tsconfigPaths()],
plugins: [tsconfigPaths()],
...commonConf
})
+2 -67
View File
@@ -83,16 +83,6 @@
estree-walker "^1.0.1"
picomatch "^2.2.2"
"@ts-morph/common@~0.10.1":
version "0.10.1"
resolved "https://registry.yarnpkg.com/@ts-morph/common/-/common-0.10.1.tgz#be15b9ab13a32bbc1f6a6bd7dc056b2247b272eb"
integrity sha512-rKN/VtZUUlW4M+6vjLFSaFc1Z9sK+1hh0832ucPtPkXqOw/mSWE80Lau4z2zTPNTqtxAjfZbvKpQcEwJy0KIEg==
dependencies:
fast-glob "^3.2.5"
minimatch "^3.0.4"
mkdirp "^1.0.4"
path-browserify "^1.0.1"
"@types/estree@0.0.39":
version "0.0.39"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
@@ -403,11 +393,6 @@ cli-width@^3.0.0:
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6"
integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==
code-block-writer@^10.1.1:
version "10.1.1"
resolved "https://registry.yarnpkg.com/code-block-writer/-/code-block-writer-10.1.1.tgz#ad5684ed4bfb2b0783c8b131281ae84ee640a42f"
integrity sha512-67ueh2IRGst/51p0n6FvPrnRjAGHY5F8xdjkgrYE7DDzpJe6qA07RYQ9VcoUeo5ATOjSOiWpSL3SWBRRbempMw==
color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
@@ -883,7 +868,7 @@ fast-deep-equal@^3.1.1:
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
fast-glob@^3.1.1, fast-glob@^3.2.5, fast-glob@^3.2.7:
fast-glob@^3.1.1:
version "3.2.7"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1"
integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==
@@ -958,15 +943,6 @@ fraction.js@^4.1.1:
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.1.1.tgz#ac4e520473dae67012d618aab91eda09bcb400ff"
integrity sha512-MHOhvvxHTfRFpF1geTK9czMIZ6xclsEor2wkIGYYq+PxcQqT7vStJqjhe6S1TenZrMZzo+wlqOufBDVepUEgPg==
fs-extra@^10.0.0:
version "10.0.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.0.0.tgz#9ff61b655dde53fb34a82df84bb214ce802e17c1"
integrity sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==
dependencies:
graceful-fs "^4.2.0"
jsonfile "^6.0.1"
universalify "^2.0.0"
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
@@ -1052,7 +1028,7 @@ globrex@^0.1.2:
resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098"
integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==
graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0:
graceful-fs@^4.1.2:
version "4.2.8"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a"
integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==
@@ -1324,15 +1300,6 @@ json5@^1.0.1:
dependencies:
minimist "^1.2.0"
jsonfile@^6.0.1:
version "6.1.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==
dependencies:
universalify "^2.0.0"
optionalDependencies:
graceful-fs "^4.1.6"
"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.1.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz#41108d2cec408c3453c1bbe8a4aae9e1e2bd8f82"
@@ -1477,11 +1444,6 @@ mkdirp@^0.5.1:
dependencies:
minimist "^1.2.5"
mkdirp@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
@@ -1663,11 +1625,6 @@ parse-node-version@^1.0.1:
resolved "https://registry.yarnpkg.com/parse-node-version/-/parse-node-version-1.0.1.tgz#e2b5dbede00e7fa9bc363607f53327e8b073189b"
integrity sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==
path-browserify@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd"
integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==
path-exists@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
@@ -2120,14 +2077,6 @@ to-regex-range@^5.0.1:
dependencies:
is-number "^7.0.0"
ts-morph@^11.0.3:
version "11.0.3"
resolved "https://registry.yarnpkg.com/ts-morph/-/ts-morph-11.0.3.tgz#01a92b3c2b5a48ccdf318ec90864229b8061d056"
integrity sha512-ymuPkndv9rzqTLiHWMkVrFXWcN4nBiBGhRP/kTC9F5amAAl7BNLfyrsTzMD1o9A0zishKoF1KQT/0yyFhJnPgA==
dependencies:
"@ts-morph/common" "~0.10.1"
code-block-writer "^10.1.1"
tsconfig-paths@^3.9.0:
version "3.11.0"
resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz#954c1fe973da6339c78e06b03ce2e48810b65f36"
@@ -2182,11 +2131,6 @@ unbox-primitive@^1.0.1:
has-symbols "^1.0.2"
which-boxed-primitive "^1.0.2"
universalify@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717"
integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==
uri-js@^4.2.2:
version "4.4.1"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
@@ -2207,15 +2151,6 @@ validate-npm-package-license@^3.0.1:
spdx-correct "^3.0.0"
spdx-expression-parse "^3.0.0"
vite-plugin-dts@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/vite-plugin-dts/-/vite-plugin-dts-0.7.0.tgz#5a73d64ee35d74c6efc93d83abb474660e19218a"
integrity sha512-L25t5nscMrqvRewl4IARV7fXJTPKny8etIq5LP1WhuVKTBpvkcgAzC3JWK8ZD8MeHUS3qlz7pQ6TSYx7d47m6A==
dependencies:
fast-glob "^3.2.7"
fs-extra "^10.0.0"
ts-morph "^11.0.3"
vite-tsconfig-paths@^3.3.14:
version "3.3.14"
resolved "https://registry.yarnpkg.com/vite-tsconfig-paths/-/vite-tsconfig-paths-3.3.14.tgz#4f1e099e04105952dbdef1c70f72fc5ae9047575"