Compare commits
4 Commits
200a867171
...
d0b5890b99
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0b5890b99 | ||
|
|
0ecd6c6585 | ||
|
|
2aa33ef814 | ||
|
|
c2ccdfa594 |
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
# 配置文件
|
||||
thewhitesilk_config.json
|
||||
# **默认**数据目录
|
||||
thewhitesilk_data/
|
||||
9
deno.jsonc
Normal file
9
deno.jsonc
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"tasks": {
|
||||
"main": "deno run --allow-read --allow-write src/main.ts",
|
||||
"test": "deno run --allow-read --allow-write src/main_test.ts"
|
||||
},
|
||||
"imports": {
|
||||
"chalk": "npm:chalk@5.4.1"
|
||||
}
|
||||
}
|
||||
16
deno.lock
generated
Normal file
16
deno.lock
generated
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"version": "4",
|
||||
"specifiers": {
|
||||
"npm:chalk@5.4.1": "5.4.1"
|
||||
},
|
||||
"npm": {
|
||||
"chalk@5.4.1": {
|
||||
"integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="
|
||||
}
|
||||
},
|
||||
"workspace": {
|
||||
"dependencies": [
|
||||
"npm:chalk@5.4.1"
|
||||
]
|
||||
}
|
||||
}
|
||||
17
src/config.ts
Normal file
17
src/config.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import fs from 'node:fs/promises'
|
||||
import chalk from 'chalk'
|
||||
|
||||
let config = {
|
||||
data_path: "./thewhitesilk_data"
|
||||
}
|
||||
|
||||
try {
|
||||
config = JSON.parse(await fs.readFile('thewhitesilk_config.json'))
|
||||
} catch (_e) {
|
||||
console.log(chalk.yellow("配置文件貌似不存在, 正在创建..."))
|
||||
await fs.writeFile('thewhitesilk_config.json', JSON.stringify(config))
|
||||
}
|
||||
|
||||
await fs.mkdir(config.data_path, { recursive: true })
|
||||
|
||||
export default config
|
||||
64
src/data/Chat.ts
Normal file
64
src/data/Chat.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { DatabaseSync } from "node:sqlite"
|
||||
import { Buffer } from "node:buffer"
|
||||
import path from 'node:path'
|
||||
|
||||
import config from '../config.ts'
|
||||
import ChatBean from './ChatBean.ts'
|
||||
|
||||
/**
|
||||
* Chat.ts - Wrapper and manager
|
||||
* Wrap with ChatBean to directly update database
|
||||
* Manage the database by itself (static)
|
||||
*/
|
||||
export default class Chat {
|
||||
static table_name: string = "Chat"
|
||||
private static database: DatabaseSync = Chat.init()
|
||||
private static init(): DatabaseSync {
|
||||
const db: DatabaseSync = new DatabaseSync(path.join(config.data_path, 'Chats.db'))
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS ${Chat.table_name} (
|
||||
/* 序号 */ count INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
/* Chat ID, 哈希 */ id TEXT,
|
||||
/* 设置 */ settings TEXT NOT NULL
|
||||
);
|
||||
`)
|
||||
return db
|
||||
}
|
||||
|
||||
private static findAllByCondition(condition: string, ...args: unknown[]): UserBean[] {
|
||||
return database.prepare(`SELECT count, id FROM ${User.table_name} WHERE ${condition}`).all(...args)
|
||||
}
|
||||
|
||||
|
||||
|
||||
declare bean: ChatBean
|
||||
constructor(bean: ChatBean) {
|
||||
this.bean = bean
|
||||
}
|
||||
private setAttr(key: string, value: unknown): void {
|
||||
User.database.prepare(`UPDATE ${User.table_name} SET ${key} = ? WHERE id = ?`).run(value, this.bean.id)
|
||||
this.bean[key] = value
|
||||
}
|
||||
getUserName(): string {
|
||||
return this.bean.username
|
||||
}
|
||||
setUserName(userName: string): void {
|
||||
this.setAttr("username", userName)
|
||||
}
|
||||
getNickName(): string {
|
||||
return this.bean.nickname
|
||||
}
|
||||
setNickName(nickName: string): void {
|
||||
this.setAttr("nickname", nickName)
|
||||
}
|
||||
getAvatar(): Uint8Array {
|
||||
return this.bean.avatar
|
||||
}
|
||||
setAvatar(avatar: Buffer): void {
|
||||
this.setAttr("avatar", avatar)
|
||||
}
|
||||
|
||||
getContacts() {
|
||||
|
||||
}
|
||||
}
|
||||
3
src/data/ChatBean.ts
Normal file
3
src/data/ChatBean.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export default interface ChatBean {
|
||||
id: string
|
||||
}
|
||||
101
src/data/User.ts
Normal file
101
src/data/User.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import { DatabaseSync } from "node:sqlite"
|
||||
import { Buffer } from "node:buffer"
|
||||
import path from 'node:path'
|
||||
import crypto from 'node:crypto'
|
||||
|
||||
import config from '../config.ts'
|
||||
import UserBean from './UserBean.ts'
|
||||
|
||||
/**
|
||||
* User.ts - Wrapper and manager
|
||||
* Wrap with UserBean to directly update database
|
||||
* Manage the database by itself (static)
|
||||
*/
|
||||
export default class User {
|
||||
static table_name: string = "Users"
|
||||
private static database: DatabaseSync = User.init()
|
||||
private static init(): DatabaseSync {
|
||||
const db: DatabaseSync = new DatabaseSync(path.join(config.data_path, 'Users.db'))
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS ${TABEL_NAME} (
|
||||
/* 序号 */ count INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
/* 用户 ID, 哈希 */ id TEXT
|
||||
/* 注册时间, 时间戳 */ registered_time INT8 NOT NULL,
|
||||
/* 用戶名, 可選 */ username TEXT,
|
||||
/* 昵称 */ nickname TEXT NOT NULL,
|
||||
/* 头像, 可选 */ avatar BLOB,
|
||||
/* 设置 */ settings TEXT NOT NULL
|
||||
);
|
||||
`)
|
||||
return db
|
||||
}
|
||||
|
||||
static create(userName: string | null, nickName: string, avatar: Buffer | null): User {
|
||||
return new User(
|
||||
User.findAllByCondition(
|
||||
'count = ?',
|
||||
database.prepare(`INSERT INTO ${User.table_name} (id, username, registered_time, nickname, avatar, settings) VALUES (?, ?, ?, ?, ?)`).run(
|
||||
crypto.randomUUID(),
|
||||
userName,
|
||||
Date.now(),
|
||||
nickName,
|
||||
avatar,
|
||||
"{}"
|
||||
).lastInsertRowid
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private static findAllByCondition(condition: string, ...args: unknown[]): UserBean[] {
|
||||
return database.prepare(`SELECT * FROM ${User.table_name} WHERE ${condition}`).all(...args)
|
||||
}
|
||||
private static checkLengthOrThrow(array: Array, leng: number, errMsg: string): Array {
|
||||
if (array.length != leng) throw new Error(errMsg)
|
||||
return array
|
||||
}
|
||||
static findById(id: string): User {
|
||||
return new User(checkLengthOrThrow(User.findAllByCondition('id = ?', id), 1, `找不到用户 ID 为 ${id} 的用户`)[0])
|
||||
}
|
||||
static findByUserName(userName: string): User {
|
||||
return new User(checkLengthOrThrow(User.findAllByCondition('username = ?', userName), 1, `找不到用户名为 ${userName} 的用户`)[0])
|
||||
}
|
||||
|
||||
declare bean: UserBean
|
||||
constructor(bean: UserBean) {
|
||||
this.bean = bean
|
||||
}
|
||||
/* 一切的基础都是 count ID */
|
||||
private setAttr(key: string, value: unknown): void {
|
||||
User.database.prepare(`UPDATE ${User.table_name} SET ${key} = ? WHERE count = ?`).run(value, this.bean.count)
|
||||
this.bean[key] = value
|
||||
}
|
||||
getUserName(): string {
|
||||
return this.bean.username
|
||||
}
|
||||
setUserName(userName: string): void {
|
||||
this.setAttr("username", userName)
|
||||
}
|
||||
getNickName(): string {
|
||||
return this.bean.nickname
|
||||
}
|
||||
setNickName(nickName: string): void {
|
||||
this.setAttr("nickname", nickName)
|
||||
}
|
||||
getAvatar(): Uint8Array {
|
||||
return this.bean.avatar
|
||||
}
|
||||
setAvatar(avatar: Buffer): void {
|
||||
this.setAttr("avatar", avatar)
|
||||
}
|
||||
|
||||
/* getSettings(): Settings {
|
||||
|
||||
}
|
||||
|
||||
static Settings = class {
|
||||
|
||||
}
|
||||
static SettingsBean = interface {
|
||||
|
||||
} */
|
||||
}
|
||||
8
src/data/UserBean.ts
Normal file
8
src/data/UserBean.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export default interface UserBean {
|
||||
count: number,
|
||||
username: string,
|
||||
registered_time: number,
|
||||
nickname: string,
|
||||
avatar: Uint8Array,
|
||||
settings: string,
|
||||
}
|
||||
47
src/data/_user.ts
Normal file
47
src/data/_user.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { DatabaseSync } from "node:sqlite"
|
||||
import fs from 'node:fs/promises'
|
||||
|
||||
await fs.mkdir('data', { recursive: true })
|
||||
|
||||
const db = new DatabaseSync("data/users.db")
|
||||
const TABEL_NAME = "Users"
|
||||
|
||||
// 初始化表格
|
||||
db.exec(
|
||||
`
|
||||
CREATE TABLE IF NOT EXISTS ${TABEL_NAME} (
|
||||
/* 伺服器中 ID */ id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
/* 用戶名, 可選 */ username TEXT,
|
||||
/* 姓名 */ nickname TEXT NOT NULL,
|
||||
/* 头像, 可选 */ avatar BLOB
|
||||
);
|
||||
`,
|
||||
)
|
||||
|
||||
// 插入测试数据
|
||||
db.prepare(
|
||||
`
|
||||
INSERT INTO ${TABEL_NAME} (username, nickname, avatar) VALUES (?, ?, ?);
|
||||
`,
|
||||
).run("SisterWen", "文姐", await fs.readFile('in.webp'))
|
||||
|
||||
let rows = db.prepare(`SELECT id, username, nickname, avatar FROM ${TABEL_NAME}`).all();
|
||||
for (const row of rows) {
|
||||
console.log(row)
|
||||
}
|
||||
|
||||
// 更新用户名
|
||||
// 用户名要合规, 以免导致 SQL 注入!
|
||||
db.prepare(
|
||||
`
|
||||
UPDATE ${TABEL_NAME} SET username = '${ "Sister_Wen" }' WHERE ${ "username" } = ${ "'SisterWen'" };
|
||||
`,
|
||||
).run()
|
||||
|
||||
rows = db.prepare(`SELECT id, username, nickname, avatar FROM ${TABEL_NAME}`).all();
|
||||
for (const row of rows) {
|
||||
console.log(row)
|
||||
await fs.writeFile('out.webp', row.avatar)
|
||||
}
|
||||
|
||||
db.close()
|
||||
0
src/main.ts
Normal file
0
src/main.ts
Normal file
42
src/main_test.ts
Normal file
42
src/main_test.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { DatabaseSync } from "node:sqlite"
|
||||
import fs from 'node:fs/promises'
|
||||
|
||||
await fs.mkdir('data', { recursive: true })
|
||||
|
||||
const db = new DatabaseSync("data/users.db")
|
||||
const TABEL_NAME = "Users"
|
||||
|
||||
// 初始化表格
|
||||
db.exec(
|
||||
`
|
||||
CREATE TABLE IF NOT EXISTS ${TABEL_NAME} (
|
||||
/* 伺服器中 ID */ id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
/* 用戶名, 可選 */ username TEXT,
|
||||
/* 姓名 */ nickname TEXT NOT NULL,
|
||||
/* 头像, 可选 */ avatar BLOB
|
||||
);
|
||||
`,
|
||||
)
|
||||
|
||||
// 插入测试数据
|
||||
db.prepare(
|
||||
`
|
||||
INSERT INTO ${TABEL_NAME} (username, nickname, avatar) VALUES (?, ?, ?);
|
||||
`,
|
||||
).run("SisterWen", "文姐", null)
|
||||
|
||||
let rows = db.prepare(`SELECT id, username, nickname, avatar FROM ${TABEL_NAME}`).all();
|
||||
for (const row of rows) {
|
||||
console.log(row)
|
||||
}
|
||||
|
||||
// 更新用户名
|
||||
// 用户名要合规, 以免导致 SQL 注入!
|
||||
db.prepare(`UPDATE ${TABEL_NAME} SET username = ? WHERE id = ?`).run("文姐", 1)
|
||||
|
||||
rows = db.prepare(`SELECT id, username, nickname, avatar FROM ${TABEL_NAME} WHERE username = ?`).all("文姐")
|
||||
for (const row of rows) {
|
||||
console.log(row)
|
||||
}
|
||||
|
||||
db.close()
|
||||
2
暫存/.gitignore
vendored
2
暫存/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
# 程式數據目錄
|
||||
whitesilk_data/
|
||||
5
暫存/.vscode/settings.json
vendored
5
暫存/.vscode/settings.json
vendored
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"deno.enable": true,
|
||||
"deno.lint": true,
|
||||
"deno.unstable": false
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN" class="mdui-theme-auto">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, shrink-to-fit=no" />
|
||||
<meta name="renderer" content="webkit" />
|
||||
|
||||
<!-- UI -->
|
||||
<script src="https://unpkg.com/mdui@2/mdui.global.js">
|
||||
</script>
|
||||
<link rel="icon" href="icon.ico" />
|
||||
<link rel="stylesheet" href="https://unpkg.com/mdui@2/mdui.css" />
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined" rel="stylesheet" />
|
||||
<script src="https://unpkg.com/split.js/dist/split.min.js"></script>
|
||||
|
||||
|
||||
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
|
||||
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
|
||||
<title>TheWhiteSilk</title>
|
||||
|
||||
<style>
|
||||
/* 滑条*/
|
||||
.no-scroll-bar::-webkit-scrollbar {
|
||||
width: 0px !important;
|
||||
}
|
||||
|
||||
/* https://blog.csdn.net/qq_39347364/article/details/111996581*/
|
||||
*::-webkit-scrollbar {
|
||||
width: 7px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-track {
|
||||
width: 6px;
|
||||
background: rgba(#101F1C, 0.1);
|
||||
-webkit-border-radius: 2em;
|
||||
-moz-border-radius: 2em;
|
||||
border-radius: 2em;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(144, 147, 153, .5);
|
||||
background-clip: padding-box;
|
||||
min-height: 28px;
|
||||
-webkit-border-radius: 2em;
|
||||
-moz-border-radius: 2em;
|
||||
border-radius: 2em;
|
||||
transition: background-color .3s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgba(144, 147, 153, .3);
|
||||
}
|
||||
|
||||
/* 使用系统字体 在部分系统表现很好*/
|
||||
/* 我们至今仍未能知道桌面端浏览器字体的秘密*/
|
||||
*:not(.material-icons, .mdui-icon, mdui-icon, .fa, .google-symbols) {
|
||||
font-family: -apple-system, system-ui, -webkit-system-font !important;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
margin: 0 0 0 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
html {
|
||||
margin: 0 0 0 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 防止小尺寸图片模糊*/
|
||||
* {
|
||||
image-rendering: -moz-crisp-edges;
|
||||
image-rendering: -o-crisp-edges;
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
image-rendering: crisp-edges;
|
||||
-ms-interpolation-mode: nearest-neighbor;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
<script nomodule>
|
||||
alert('很抱歉, 此应用无法在较旧的浏览器运行, 请使用基于 Chromium 89+ 的浏览器(内核)使用 :(')
|
||||
</script>
|
||||
<script type="module">
|
||||
import App from './ui/App.js'
|
||||
ReactDOM.createRoot(document.getElementById('app')).render(React.createElement(App, null))
|
||||
|
||||
let onResize = () => {
|
||||
document.body.style.setProperty('--whitesilk-widget-message-maxwidth', mdui.breakpoint().down('md') ? "80%" : "70%")
|
||||
document.body.style.setProperty('--whitesilk-window-width', window.innerWidth + 'px')
|
||||
document.body.style.setProperty('--whitesilk-window-height', window.innerHeight + 'px')
|
||||
}
|
||||
window.addEventListener('resize', onResize)
|
||||
onResize()
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,40 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN" class="mdui-theme-auto">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, shrink-to-fit=no" />
|
||||
<meta name="renderer" content="webkit" />
|
||||
|
||||
<link rel="stylesheet" href="https://unpkg.com/mdui@2/mdui.css">
|
||||
<script src="https://unpkg.com/mdui@2/mdui.global.js"></script>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<script src="https://cdn.socket.io/4.8.1/socket.io.min.js"></script>
|
||||
|
||||
<title>TheWhiteSilk Debugger</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<mdui-button id="send">Send</mdui-button>
|
||||
<mdui-text-field id="edittext" autosize></mdui-text-field>
|
||||
<div id="out">
|
||||
|
||||
</div>
|
||||
<script>
|
||||
const socket = io()
|
||||
$('#edittext').val(`{
|
||||
"method": "",
|
||||
"args": {
|
||||
|
||||
}
|
||||
}`)
|
||||
$('#send').click(() => {
|
||||
socket.emit("the_white_silk", JSON.parse($('#edittext').val()), (response) => {
|
||||
$('#out').text(JSON.stringify(response))
|
||||
});
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,283 +0,0 @@
|
||||
import Message from "./chat/Message.js"
|
||||
import MessageContainer from "./chat/MessageContainer.js"
|
||||
import ContactsListItem from "./main/ContactsListItem.js"
|
||||
import RecentsListItem from "./main/RecentsListItem.js"
|
||||
import useEventListener from './useEventListener.js'
|
||||
|
||||
export default function App() {
|
||||
const [recentsList, setRecentsList] = React.useState([
|
||||
{
|
||||
userId: 0,
|
||||
avatar: "https://www.court-records.net/mugshot/aa6-004-maya.png",
|
||||
nickName: "麻油衣酱",
|
||||
content: "成步堂君, 我又坐牢了("
|
||||
},
|
||||
{
|
||||
userId: 0,
|
||||
avatar: "https://www.court-records.net/mugshot/aa6-004-maya.png",
|
||||
nickName: "Maya Fey",
|
||||
content: "我是绫里真宵, 是一名灵媒师~"
|
||||
},
|
||||
{
|
||||
userId: 0,
|
||||
avatar: "https://www.court-records.net/mugshot/aa6-004-maya.png",
|
||||
nickName: "麻油衣酱",
|
||||
content: "成步堂君, 我又坐牢了("
|
||||
},
|
||||
{
|
||||
userId: 0,
|
||||
avatar: "https://www.court-records.net/mugshot/aa6-004-maya.png",
|
||||
nickName: "Maya Fey",
|
||||
content: "我是绫里真宵, 是一名灵媒师~"
|
||||
},
|
||||
{
|
||||
userId: 0,
|
||||
avatar: "https://www.court-records.net/mugshot/aa6-004-maya.png",
|
||||
nickName: "麻油衣酱",
|
||||
content: "成步堂君, 我又坐牢了("
|
||||
},
|
||||
{
|
||||
userId: 0,
|
||||
avatar: "https://www.court-records.net/mugshot/aa6-004-maya.png",
|
||||
nickName: "Maya Fey",
|
||||
content: "我是绫里真宵, 是一名灵媒师~"
|
||||
},
|
||||
{
|
||||
userId: 0,
|
||||
avatar: "https://www.court-records.net/mugshot/aa6-004-maya.png",
|
||||
nickName: "麻油衣酱",
|
||||
content: "成步堂君, 我又坐牢了("
|
||||
},
|
||||
{
|
||||
userId: 0,
|
||||
avatar: "https://www.court-records.net/mugshot/aa6-004-maya.png",
|
||||
nickName: "Maya Fey",
|
||||
content: "我是绫里真宵, 是一名灵媒师~"
|
||||
},
|
||||
{
|
||||
userId: 0,
|
||||
avatar: "https://www.court-records.net/mugshot/aa6-004-maya.png",
|
||||
nickName: "麻油衣酱",
|
||||
content: "成步堂君, 我又坐牢了("
|
||||
},
|
||||
{
|
||||
userId: 0,
|
||||
avatar: "https://www.court-records.net/mugshot/aa6-004-maya.png",
|
||||
nickName: "Maya Fey",
|
||||
content: "我是绫里真宵, 是一名灵媒师~"
|
||||
},
|
||||
])
|
||||
const [contactsMap, setContactsMap] = React.useState({
|
||||
默认分组: [
|
||||
{
|
||||
userId: 0,
|
||||
avatar: "https://www.court-records.net/mugshot/aa6-004-maya.png",
|
||||
nickName: "麻油衣酱",
|
||||
},
|
||||
{
|
||||
userId: 0,
|
||||
avatar: "https://www.court-records.net/mugshot/aa6-004-maya.png",
|
||||
nickName: "Maya Fey",
|
||||
}
|
||||
],
|
||||
测试分组114514: [
|
||||
{
|
||||
userId: 0,
|
||||
avatar: "https://www.court-records.net/mugshot/aa6-004-maya.png",
|
||||
nickName: "麻油衣酱",
|
||||
},
|
||||
{
|
||||
userId: 0,
|
||||
avatar: "https://www.court-records.net/mugshot/aa6-004-maya.png",
|
||||
nickName: "Maya Fey",
|
||||
}
|
||||
],
|
||||
})
|
||||
const [navigationItemSelected, setNavigationItemSelected] = React.useState('Recents')
|
||||
|
||||
const navigationRailRef = React.useRef(null)
|
||||
useEventListener(navigationRailRef, 'change', (event) => {
|
||||
setNavigationItemSelected(event.target.value)
|
||||
})
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
display: "flex",
|
||||
position: 'relative',
|
||||
width: 'calc(var(--whitesilk-window-width) - 80px)',
|
||||
height: 'var(--whitesilk-window-height)',
|
||||
}}>
|
||||
{
|
||||
// 移动端用 页面调试
|
||||
// 換個地方弄
|
||||
// (new URL(location.href).searchParams.get('debug') == 'true') && <script src="https://unpkg.com/eruda/eruda.js"></script>
|
||||
}
|
||||
<mdui-navigation-rail contained value="Recents" ref={navigationRailRef}>
|
||||
<mdui-button-icon icon="menu" slot="top"></mdui-button-icon>
|
||||
|
||||
<mdui-navigation-rail-item icon="watch_later--outlined" value="Recents"></mdui-navigation-rail-item>
|
||||
<mdui-navigation-rail-item icon="contacts--outlined" value="Contacts"></mdui-navigation-rail-item>
|
||||
|
||||
<mdui-button-icon icon="settings" slot="bottom"></mdui-button-icon>
|
||||
</mdui-navigation-rail>
|
||||
{
|
||||
// 侧边列表
|
||||
}
|
||||
{
|
||||
// 最近聊天
|
||||
(navigationItemSelected == "Recents") &&
|
||||
<mdui-list style={{
|
||||
width: "35%",
|
||||
overflowY: 'auto',
|
||||
paddingRight: '10px'
|
||||
}}>
|
||||
{
|
||||
recentsList.map((v) =>
|
||||
<RecentsListItem
|
||||
nickName={v.nickName}
|
||||
avatar={v.avatar}
|
||||
content={v.content} />
|
||||
)
|
||||
}
|
||||
</mdui-list>
|
||||
}
|
||||
{
|
||||
// 联系人列表
|
||||
(navigationItemSelected == "Contacts") &&
|
||||
<mdui-list style={{
|
||||
width: "35%",
|
||||
overflowY: 'auto',
|
||||
paddingRight: '10px'
|
||||
}}>
|
||||
{
|
||||
Object.keys(contactsMap).map((v) =>
|
||||
<>
|
||||
<mdui-list-subheader>{v}</mdui-list-subheader>
|
||||
{
|
||||
contactsMap[v].map((v2) =>
|
||||
<ContactsListItem
|
||||
nickName={v2.nickName}
|
||||
avatar={v2.avatar} />
|
||||
)
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
</mdui-list>
|
||||
}
|
||||
{
|
||||
// 分割线
|
||||
}
|
||||
<div style={{
|
||||
// 我们删除了 body 的padding 因此不需要再 calc 了
|
||||
height: 'var(--whitesilk-window-height)',
|
||||
marginRight: '10px',
|
||||
}}>
|
||||
<mdui-divider vertical></mdui-divider>
|
||||
</div>
|
||||
{
|
||||
// 聊天页面
|
||||
}
|
||||
<div style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
overflowY: 'auto',
|
||||
}}>
|
||||
<mdui-top-app-bar style={{
|
||||
position: 'sticky',
|
||||
}}>
|
||||
<mdui-button-icon icon="menu"></mdui-button-icon>
|
||||
<mdui-top-app-bar-title>Title</mdui-top-app-bar-title>
|
||||
<mdui-button-icon icon="more_vert"></mdui-button-icon>
|
||||
</mdui-top-app-bar>
|
||||
<div>
|
||||
<MessageContainer style={{
|
||||
height: '100%',
|
||||
paddingBottom: '20px',
|
||||
}}>
|
||||
<Message
|
||||
nickName="Fey"
|
||||
avatar="https://www.court-records.net/mugshot/aa6-004-maya.png">
|
||||
Test
|
||||
</Message>
|
||||
<Message
|
||||
direction="right"
|
||||
nickName="Fey"
|
||||
avatar="https://www.court-records.net/mugshot/aa6-004-maya.png">
|
||||
Test
|
||||
</Message>
|
||||
<Message
|
||||
nickName="Fey"
|
||||
avatar="https://www.court-records.net/mugshot/aa6-004-maya.png">
|
||||
Test
|
||||
</Message>
|
||||
<Message
|
||||
direction="right"
|
||||
nickName="Fey"
|
||||
avatar="https://www.court-records.net/mugshot/aa6-004-maya.png">
|
||||
Test
|
||||
</Message>
|
||||
<Message
|
||||
nickName="Fey"
|
||||
avatar="https://www.court-records.net/mugshot/aa6-004-maya.png">
|
||||
Test
|
||||
</Message>
|
||||
<Message
|
||||
direction="right"
|
||||
nickName="Fey"
|
||||
avatar="https://www.court-records.net/mugshot/aa6-004-maya.png">
|
||||
Test
|
||||
</Message>
|
||||
<Message
|
||||
nickName="Fey"
|
||||
avatar="https://www.court-records.net/mugshot/aa6-004-maya.png">
|
||||
Test
|
||||
</Message>
|
||||
<Message
|
||||
direction="right"
|
||||
nickName="Fey"
|
||||
avatar="https://www.court-records.net/mugshot/aa6-004-maya.png">
|
||||
Test
|
||||
</Message>
|
||||
</MessageContainer>
|
||||
{
|
||||
// 输入框
|
||||
}
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
paddingBottom: '0.1rem',
|
||||
paddingTop: '0.1rem',
|
||||
height: '4rem',
|
||||
position: 'sticky',
|
||||
bottom: '0',
|
||||
backgroundColor: 'rgb(var(--mdui-color-background))',
|
||||
}}>
|
||||
<mdui-text-field variant="outlined" placeholder="喵呜~" style={{
|
||||
marginRight: '10px',
|
||||
}}></mdui-text-field>
|
||||
<mdui-button-icon slot="end-icon" icon="more_vert" style={{
|
||||
marginRight: '6px',
|
||||
}}></mdui-button-icon>
|
||||
<mdui-button-icon icon="send" style={{
|
||||
marginRight: '7px',
|
||||
}}></mdui-button-icon>
|
||||
</div>
|
||||
{/* <mdui-top-app-bar style={{
|
||||
position: 'sticky',
|
||||
bottom: '0',
|
||||
}}>
|
||||
<mdui-button-icon icon="menu"></mdui-button-icon>
|
||||
<mdui-top-app-bar-title>Title</mdui-top-app-bar-title>
|
||||
<div style={{
|
||||
flexGrow: 1,
|
||||
}}></div>
|
||||
<mdui-button-icon icon="more_vert"></mdui-button-icon>
|
||||
</mdui-top-app-bar> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
export default function Avatar({ src, text, icon = 'person', ...props } = {}) {
|
||||
return (
|
||||
src ? <mdui-avatar {...props}>
|
||||
<img src={src} alt={'(头像)' + text || ''} />
|
||||
</mdui-avatar>
|
||||
: (
|
||||
text ? <mdui-avatar {...props}>
|
||||
{
|
||||
text.substring(0, 0)
|
||||
}
|
||||
</mdui-avatar>
|
||||
: <mdui-avatar icon={icon} {...props} />
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export default function ChatFragment() {
|
||||
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
import Avatar from "../Avatar.js"
|
||||
|
||||
/**
|
||||
* 一条消息
|
||||
* @param { Object } param
|
||||
* @param { "left" | "right" } [param.direction="left"] 消息方向
|
||||
* @param { String } [param.avatar] 头像链接
|
||||
* @param { String } [param.nickName] 昵称
|
||||
* @returns { React.JSX.Element }
|
||||
*/
|
||||
export default function Message({ direction = 'left', avatar, nickName, children, ...props } = {}) {
|
||||
let isAtRight = direction == 'right'
|
||||
return (
|
||||
<div
|
||||
slot="trigger"
|
||||
style={{
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: isAtRight ? "flex-end" : "flex-start",
|
||||
flexDirection: "column"
|
||||
}}
|
||||
{...props}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: isAtRight ? "flex-end" : "flex-start",
|
||||
}}>
|
||||
{
|
||||
// 发送者昵称(左)
|
||||
isAtRight && <span
|
||||
style={{
|
||||
alignSelf: "center",
|
||||
fontSize: "90%"
|
||||
}}>
|
||||
{nickName}
|
||||
</span>
|
||||
}
|
||||
{
|
||||
// 发送者头像
|
||||
}
|
||||
<Avatar
|
||||
src={avatar}
|
||||
text={nickName}
|
||||
style={{
|
||||
width: "43px",
|
||||
height: "43px",
|
||||
margin: "11px"
|
||||
}} />
|
||||
{
|
||||
// 发送者昵称(右)
|
||||
!isAtRight && <span
|
||||
style={{
|
||||
alignSelf: "center",
|
||||
fontSize: "90%"
|
||||
}}>
|
||||
{nickName}
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
<mdui-card
|
||||
variant="elevated"
|
||||
style={{
|
||||
maxWidth: 'var(--whitesilk-widget-message-maxwidth)', // (window.matchMedia('(pointer: fine)') && "50%") || (window.matchMedia('(pointer: coarse)') && "77%"),
|
||||
minWidth: "0%",
|
||||
[isAtRight ? "marginRight" : "marginLeft"]: "55px",
|
||||
marginTop: "-5px",
|
||||
padding: "15px",
|
||||
alignSelf: isAtRight ? "flex-end" : "flex-start",
|
||||
}}>
|
||||
<span
|
||||
id="msg"
|
||||
style={{
|
||||
fontSize: "94%"
|
||||
}}>
|
||||
{
|
||||
// 消息内容
|
||||
children
|
||||
}
|
||||
</span>
|
||||
</mdui-card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
/**
|
||||
* 消息容器
|
||||
* @returns { React.JSX.Element }
|
||||
*/
|
||||
export default function MessageContainer({ children, style, ...props } = {}) {
|
||||
return (
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center',
|
||||
...style,
|
||||
}}
|
||||
{...props}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/**
|
||||
* 一条系统提示消息
|
||||
* @returns { React.JSX.Element }
|
||||
*/
|
||||
export default function SystemMessage({ children } = {}) {
|
||||
return (
|
||||
<div style={{
|
||||
width: '100%',
|
||||
flexDirection: 'column',
|
||||
display: 'flex',
|
||||
marginTop: '25px',
|
||||
marginBottom: '20px',
|
||||
}}>
|
||||
<mdui-card variant="filled"
|
||||
style={{
|
||||
alignSelf: 'center',
|
||||
paddingTop: '9px',
|
||||
paddingBottom: '9px',
|
||||
paddingLeft: '18px',
|
||||
paddingRight: '18px',
|
||||
fontSize: '92%',
|
||||
}}>
|
||||
{children}
|
||||
</mdui-card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import Avatar from "../Avatar.js"
|
||||
|
||||
export default function ContactsListItem({ nickName, avatar }) {
|
||||
return (
|
||||
<mdui-list-item rounded style={{
|
||||
marginTop: '3px',
|
||||
marginBottom: '3px',
|
||||
width: '100%',
|
||||
}}>
|
||||
<span style={{
|
||||
width: "100%",
|
||||
}}>{nickName}</span>
|
||||
<Avatar src={avatar} text="title" slot="icon" />
|
||||
</mdui-list-item>
|
||||
)
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import Avatar from "../Avatar.js"
|
||||
|
||||
export default function RecentsListItem({ nickName, avatar, content }) {
|
||||
return (
|
||||
<mdui-list-item rounded style={{
|
||||
marginTop: '3px',
|
||||
marginBottom: '3px',
|
||||
}}>
|
||||
{nickName}
|
||||
<Avatar src={avatar} text={nickName} slot="icon" />
|
||||
<span slot="description"
|
||||
style={{
|
||||
width: "100%",
|
||||
display: "inline-block",
|
||||
whiteSpace: "nowrap", /* 禁止换行 */
|
||||
overflow: "hidden", /* 隐藏溢出内容 */
|
||||
textOverflow: "ellipsis", /* 显示省略号 */
|
||||
}}>{content}</span>
|
||||
</mdui-list-item>
|
||||
)
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* @callback callback
|
||||
* @param { Event } event
|
||||
*/
|
||||
|
||||
/**
|
||||
* 绑定事件
|
||||
* @param { React.Ref } ref
|
||||
* @param { String } eventName
|
||||
* @param { callback } callback
|
||||
*/
|
||||
export default function useEventListener(ref, eventName, callback) {
|
||||
React.useEffect(() => {
|
||||
ref.current.addEventListener(eventName, callback)
|
||||
return () => ref.current.removeEventListener(eventName, callback)
|
||||
}, [ref, eventName, callback])
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import config from './server/config.ts'
|
||||
import io from './server/lib/io.js'
|
||||
|
||||
config.ensureAllDirsAreCreated()
|
||||
io.copyDir('./client', config.dirs.WEB_PAGE_DIR)
|
||||
|
||||
await import('./server/build.ts').then(a => a.default(config.dirs.WEB_PAGE_DIR))
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
// 導入包
|
||||
"imports": {
|
||||
// API & Web
|
||||
"express": "npm:express@^4.21.2",
|
||||
"socket.io": "npm:socket.io@^4.8.1",
|
||||
// Database
|
||||
"sqlite3": "npm:sqlite3@^5.1.7",
|
||||
// Front-end Compiling
|
||||
"@babel/core": "npm:@babel/core@^7.26.10",
|
||||
"@babel/preset-env": "npm:@babel/preset-env@^7.26.9",
|
||||
"@babel/preset-react": "npm:@babel/preset-react@^7.26.3"
|
||||
},
|
||||
"tasks": {
|
||||
// 編譯並運行
|
||||
"run": "deno run --allow-read --allow-write --allow-import --allow-env --allow-net --allow-sys ./start-server.ts",
|
||||
// 編譯前端頁面
|
||||
"compile-webpage": "deno run --allow-read --allow-write --allow-import --allow-env --allow-sys ./compile-webpage.ts"
|
||||
}
|
||||
}
|
||||
2805
暫存/deno.lock
generated
2805
暫存/deno.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,7 +0,0 @@
|
||||
Copyright © 2024-2025 月有陰晴圓缺 (CrescentLeaf/MoonLeeeaf)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -1,4 +0,0 @@
|
||||
echo "主文件夾 = $HOME"
|
||||
# mkdir usr_home || true
|
||||
# HOME=./usr_home
|
||||
deno task run
|
||||
@@ -1,42 +0,0 @@
|
||||
// @ts-types="npm:@types/babel__core"
|
||||
import babel from '@babel/core'
|
||||
import io from './lib/io.js'
|
||||
|
||||
function compileJs(path: string) {
|
||||
babel.transformFileAsync(path, {
|
||||
presets: [
|
||||
[
|
||||
"@babel/preset-env", {
|
||||
modules: false,
|
||||
},
|
||||
],
|
||||
"@babel/preset-react",
|
||||
// "minify",
|
||||
],
|
||||
targets: {
|
||||
chrome: "53",
|
||||
android: "40",
|
||||
},
|
||||
sourceMaps: true,
|
||||
}).then(function (result) {
|
||||
if (result == null) throw new Error('result == null')
|
||||
io.open(path, 'w').writeAll(result.code + '\n' + `//@ sourceMappingURL=${io.getName(path)}.map`).close()
|
||||
io.open(path + '.map', 'w').writeAll(JSON.stringify(result.map)).close()
|
||||
console.log(`Compile js: ${path}`)
|
||||
})
|
||||
}
|
||||
|
||||
export default function (path: string) {
|
||||
io.listFiles(path, {
|
||||
recursive: true,
|
||||
fullPath: true,
|
||||
}).forEach(function (v) {
|
||||
if (v.endsWith('.js'))
|
||||
compileJs(v)
|
||||
else if (v.endsWith('.jsx')) {
|
||||
const v2 = `${io.getParent(v)}//${io.getName(v).replace(/\.jsx/, '.js')}`
|
||||
io.move(v, v2)
|
||||
compileJs(v2)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import io from './lib/io.js'
|
||||
|
||||
export default class Config {
|
||||
static ensureAllDirsAreCreated() {
|
||||
for (const key of Object.keys(Config.dirs) as Array<keyof typeof Config.dirs>) {
|
||||
io.mkdirs(Config.dirs[key])
|
||||
}
|
||||
}
|
||||
static BASE_DIR = 'whitesilk_data'
|
||||
static dirs = {
|
||||
WEB_PAGE_DIR: this.BASE_DIR + '/_webpage',
|
||||
DATABASES_DIR: this.BASE_DIR + '/databases',
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import { DatabaseSync } from "node:sqlite"
|
||||
|
||||
export default class User {
|
||||
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
// deno-lint-ignore-file ban-types
|
||||
|
||||
import http from "node:http"
|
||||
import https from "node:https"
|
||||
// @ts-types="npm:@types/express"
|
||||
import express from "express"
|
||||
// @ts-types="npm:socket.io"
|
||||
import { Server as SocketIoServer } from "socket.io"
|
||||
|
||||
interface TheWhiteSilkParams {
|
||||
method: string
|
||||
args: object
|
||||
}
|
||||
|
||||
interface TheWhiteSilkCallback {
|
||||
code: 200 | 400 | 401 | 403 | 404 | 500 | 501
|
||||
msg: string
|
||||
}
|
||||
|
||||
interface ClientToServerEvents {
|
||||
the_white_silk: (arg: TheWhiteSilkParams, callback: (ret: TheWhiteSilkCallback) => void) => void
|
||||
}
|
||||
|
||||
const useHttps = false
|
||||
|
||||
const app = express()
|
||||
const httpApp = useHttps ? https.createServer(app) : http.createServer(app)
|
||||
const sio = new SocketIoServer<
|
||||
ClientToServerEvents,
|
||||
{},
|
||||
{},
|
||||
{}
|
||||
>(httpApp, {})
|
||||
|
||||
app.use("/", express.static("whitesilk_data/page_builded/"))
|
||||
|
||||
sio.on("connection", (socket) => {
|
||||
socket.on("the_white_silk", (params, callback) => {
|
||||
if ((params || callback) == null || typeof callback == "function") return
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
export {
|
||||
app as expressApp,
|
||||
httpApp as httpServer,
|
||||
sio as SocketIoServer,
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import crypto from 'node:crypto'
|
||||
|
||||
/**
|
||||
* 获取 Sha-256 Hex 格式哈希
|
||||
* @param { crypto.BinaryLike } data
|
||||
* @returns
|
||||
*/
|
||||
export function sha256(data) {
|
||||
return crypto.createHash('sha256').update(data).digest().toString('hex')
|
||||
}
|
||||
@@ -1,263 +0,0 @@
|
||||
/*
|
||||
* Simple File Access Library
|
||||
* Author - @MoonLeeeaf <https://github.com/MoonLeeeaf>
|
||||
*/
|
||||
|
||||
import fs from 'node:fs'
|
||||
|
||||
/**
|
||||
* 简单文件类
|
||||
*/
|
||||
export default class io {
|
||||
/**
|
||||
* 构建函数
|
||||
* @param { String } path
|
||||
* @param { String } mode
|
||||
*/
|
||||
constructor(path, mode) {
|
||||
this.path = path
|
||||
this.r = mode.includes('r')
|
||||
this.w = mode.includes('w')
|
||||
}
|
||||
/**
|
||||
* 构建函数
|
||||
* @param { String } path
|
||||
* @param { String } mode
|
||||
*/
|
||||
static open(path, mode) {
|
||||
if (!mode || mode == '')
|
||||
throw new Error('当前文件对象未设置属性!')
|
||||
return new io(path, mode)
|
||||
}
|
||||
/**
|
||||
* 检测文件或目录是否存在
|
||||
* @param { String } path
|
||||
* @returns { Boolean }
|
||||
*/
|
||||
static exists(path) {
|
||||
return fs.existsSync(path)
|
||||
}
|
||||
/**
|
||||
* 枚举目录下所有文件
|
||||
* @param { String } 扫描路径
|
||||
* @param { Object } extra 额外参数
|
||||
* @param { Function<String> } [extra.filter] 过滤器<文件路径>
|
||||
* @param { Boolean } [extra.recursive] 是否搜索文件夹内的文件
|
||||
* @param { Boolean } [extra.fullPath] 是否返回完整文件路径
|
||||
* @returns { String[] } 文件路径列表
|
||||
*/
|
||||
static listFiles(path, { filter, recursive = false, fullPath = true } = {}) {
|
||||
let a = fs.readdirSync(path, { recursive: recursive })
|
||||
a.forEach(function (v, index, arrayThis) {
|
||||
arrayThis[index] = `${path}//${v}`
|
||||
})
|
||||
|
||||
a = a.filter(function (v) {
|
||||
if (!fs.lstatSync(v).isFile()) return false
|
||||
|
||||
if (filter) return filter(v)
|
||||
return true
|
||||
})
|
||||
if (!fullPath)
|
||||
a.forEach(function (v, index, arrayThis) {
|
||||
arrayThis[index] = v.substring(v.lastIndexOf('/') + 1)
|
||||
})
|
||||
return a
|
||||
}
|
||||
/**
|
||||
* 枚举目录下所有文件夹
|
||||
* @param { String } 扫描路径
|
||||
* @param { Object } extra 额外参数
|
||||
* @param { Function<String> } [extra.filter] 过滤器<文件夹路径>
|
||||
* @param { Boolean } [extra.recursive] 是否搜索文件夹内的文件夹
|
||||
* @param { Boolean } [extra.fullPath] 是否返回完整文件路径
|
||||
* @returns { String[] } 文件夹路径列表
|
||||
*/
|
||||
static listFolders(path, { filter, recursive = false, fullPath = true } = {}) {
|
||||
let a = fs.readdirSync(path, { recursive: recursive })
|
||||
a.forEach(function (v, index, arrayThis) {
|
||||
arrayThis[index] = `${path}//${v}`
|
||||
})
|
||||
|
||||
a = a.filter(function (v) {
|
||||
if (!fs.lstatSync(v).isDirectory()) return false
|
||||
|
||||
if (filter) return filter(v)
|
||||
return true
|
||||
})
|
||||
if (!fullPath)
|
||||
a.forEach(function (v, index, arrayThis) {
|
||||
arrayThis[index] = v.substring(v.lastIndexOf('/') + 1)
|
||||
})
|
||||
return a
|
||||
}
|
||||
/**
|
||||
* 获取文件(夹)的全名
|
||||
* @param { String } path
|
||||
* @returns { String } name
|
||||
*/
|
||||
static getName(path) {
|
||||
let r = /\\|\//
|
||||
let s = path.search(r)
|
||||
while (s != -1) {
|
||||
path = path.substring(s + 1)
|
||||
s = path.search(r)
|
||||
}
|
||||
return path
|
||||
}
|
||||
/**
|
||||
* 获取文件(夹)的父文件夹路径
|
||||
* @param { String } path
|
||||
* @returns { String } parentPath
|
||||
*/
|
||||
static getParent(path) {
|
||||
return path.substring(0, path.lastIndexOf(this.getName(path)) - 1)
|
||||
}
|
||||
/**
|
||||
* 复制某文件夹的全部内容, 自动创建文件夹
|
||||
* @param { String } from
|
||||
* @param { String } to
|
||||
*/
|
||||
static copyDir(from, to) {
|
||||
this.mkdirs(to)
|
||||
this.listFiles(from).forEach(function (v) {
|
||||
io.open(v, 'r').pipe(io.open(`${to}//${io.getName(v)}`, 'w')).close()
|
||||
})
|
||||
this.listFolders(from).forEach(function (v) {
|
||||
io.copyDir(v, `${to}//${io.getName(v)}`)
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 删除文件
|
||||
* @param { String } path
|
||||
*/
|
||||
static remove(f) {
|
||||
fs.rmSync(f, { recursive: true })
|
||||
}
|
||||
/**
|
||||
* 移动文件
|
||||
* @param { String }} path
|
||||
* @param { String } newPath
|
||||
*/
|
||||
static move(path, newPath) {
|
||||
fs.renameSync(path, newPath)
|
||||
}
|
||||
/**
|
||||
* 创建文件夹, 有则忽略
|
||||
* @param { String } path
|
||||
* @returns { String } path
|
||||
*/
|
||||
static mkdirs(path) {
|
||||
if (!this.exists(path))
|
||||
fs.mkdirSync(path, { recursive: true })
|
||||
return path
|
||||
}
|
||||
/**
|
||||
* 将文件内容写入到另一个文件
|
||||
* @param { io } file
|
||||
* @returns { io } this
|
||||
*/
|
||||
pipe(file) {
|
||||
file.writeAll(this.readAll())
|
||||
file.close()
|
||||
return this
|
||||
}
|
||||
/**
|
||||
* 检查文件是否存在, 若无则写入, 有则忽略
|
||||
* @param { Buffer | String } 写入数据
|
||||
* @returns { io } 对象自身
|
||||
*/
|
||||
checkExistsOrWrite(data) {
|
||||
if (!io.exists(this.path))
|
||||
this.writeAll(data)
|
||||
return this
|
||||
}
|
||||
/**
|
||||
* 检查文件是否存在, 若无则写入 JSON 数据, 有则忽略
|
||||
* @param { Object } 写入数据
|
||||
* @returns { io } 对象自身
|
||||
*/
|
||||
checkExistsOrWriteJson(data) {
|
||||
if (!io.exists(this.path))
|
||||
this.writeAllJson(data)
|
||||
return this
|
||||
}
|
||||
/**
|
||||
* 读取一个文件
|
||||
* @returns { Buffer } 文件数据字节
|
||||
*/
|
||||
readAll() {
|
||||
if (this.r)
|
||||
return fs.readFileSync(this.path)
|
||||
throw new Error('当前文件对象未设置可读')
|
||||
}
|
||||
/**
|
||||
* 读取一个文件并关闭
|
||||
* @returns { Buffer } 文件数据
|
||||
*/
|
||||
readAllAndClose() {
|
||||
let r
|
||||
if (this.r)
|
||||
r = this.readAll()
|
||||
else
|
||||
throw new Error('当前文件对象未设置可读!')
|
||||
this.close()
|
||||
return r
|
||||
}
|
||||
/**
|
||||
* 写入一个文件
|
||||
* @param { Buffer | String } 写入数据
|
||||
* @returns { io } 对象自身
|
||||
*/
|
||||
writeAll(data) {
|
||||
if (this.w)
|
||||
fs.writeFileSync(this.path, data)
|
||||
else
|
||||
throw new Error('当前文件对象未设置可写!')
|
||||
return this
|
||||
}
|
||||
/**
|
||||
* 写入一个JSON文件
|
||||
* @param { Object } 写入数据
|
||||
* @returns { io } 对象自身
|
||||
*/
|
||||
writeAllJson(data) {
|
||||
if (!data instanceof Object)
|
||||
throw new Error('你只能输入一个 JSON 对象!')
|
||||
if (this.w)
|
||||
this.writeAll(JSON.stringify(data))
|
||||
else
|
||||
throw new Error('当前文件对象未设置可写!')
|
||||
return this
|
||||
}
|
||||
/**
|
||||
* 读取一个JSON文件
|
||||
* @returns { Object } 文件数据
|
||||
*/
|
||||
readAllJson() {
|
||||
if (this.r)
|
||||
return JSON.parse(this.readAll().toString())
|
||||
throw new Error('当前文件对象未设置可读!')
|
||||
}
|
||||
/**
|
||||
* 读取一个JSON文件并关闭
|
||||
* @returns { Object } 文件数据
|
||||
*/
|
||||
readAllJsonAndClose() {
|
||||
let r
|
||||
if (this.r)
|
||||
r = JSON.parse(this.readAll().toString())
|
||||
else
|
||||
throw new Error('当前文件对象未设置可读!')
|
||||
this.close()
|
||||
return r
|
||||
}
|
||||
/**
|
||||
* 回收文件对象
|
||||
*/
|
||||
close() {
|
||||
delete this.path
|
||||
delete this.r
|
||||
delete this.w
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import './compile-webpage.ts'
|
||||
|
||||
import {
|
||||
expressApp,
|
||||
httpServer,
|
||||
SocketIoServer,
|
||||
} from './server/http.ts'
|
||||
|
||||
httpServer.listen(8080)
|
||||
|
||||
console.log("TheWhiteSilk server started successfully")
|
||||
Reference in New Issue
Block a user