Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f053b376ee | ||
|
|
f41e67748f | ||
|
|
5fcc8be5a5 | ||
|
|
b812924ebb | ||
|
|
952a888a54 | ||
|
|
6f4bce7327 | ||
|
|
a009a05237 | ||
|
|
3d5931dcf9 | ||
|
|
9dbf03ce60 | ||
|
|
499df5f8f3 | ||
|
|
dae500190e | ||
|
|
bc40e691af | ||
|
|
e19b18a234 | ||
|
|
f54de59778 | ||
|
|
ffd62f09ac | ||
|
|
eebe77e3fa | ||
|
|
9c2cd74793 |
No files matched your search
@@ -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
@@ -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
|
||||
@@ -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
|
||||
|
||||
[](https://starchart.cc/ArtalkJS/Artalk)
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2FArtalkJS%2FArtalk?ref=badge_shield)
|
||||
|
||||
## License
|
||||
[GPL-3.0](./LICENSE)
|
||||
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2FArtalkJS%2FArtalk?ref=badge_shield)
|
||||
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2FArtalkJS%2FArtalk?ref=badge_large)
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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
@@ -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
@@ -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
@@ -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))
|
||||
}
|
||||
}
|
||||
Vendored
-59
@@ -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]>
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 导入
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Vendored
+43
-45
@@ -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
|
||||
}
|
||||
Vendored
+32
-1
@@ -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
|
||||
}
|
||||
Vendored
+77
@@ -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
|
||||
}
|
||||
Vendored
+1
-6
@@ -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
@@ -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
|
||||
})
|
||||
@@ -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"
|
||||
|
||||
Reference in new issue
Block a user