refactor: 重構後端代碼, 換用 Deno

This commit is contained in:
CrescentLeaf
2025-06-15 00:22:53 +08:00
parent 6c225b7bc5
commit d65ead11e1
29 changed files with 362 additions and 15645 deletions

2
.gitignore vendored
View File

@@ -1,2 +0,0 @@
node_modules/
whitesilk_data/

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,5 @@
{
"html.customData": ["./.vscode/mdui/html-data.zh-cn.json"],
"css.customData": ["./.vscode/mdui/css-data.zh-cn.json"],
}
"deno.enable": true,
"deno.lint": true,
"deno.unstable": false
}

View File

@@ -1,19 +0,0 @@
/*
* ©2024 The LingChair Project
*
* Make a more colorful world...
*
* License - Apache License 2.0
* Author - @MoonLeeeaf <https://github.com/MoonLeeeaf>
* Organization - @LingChair <https://github.com/LingChair>
*/
class Client {
}
class ClientUser {
}

View File

@@ -1,164 +0,0 @@
/*
* ©2024 The LingChair Project
*
* Make a more colorful world...
*
* License - Apache License 2.0
* Author - @MoonLeeeaf <https://github.com/MoonLeeeaf>
* Organization - @LingChair <https://github.com/LingChair>
*/
class MessageNormal extends HTMLElement {
static observedAttributes = ['avatar', 'sender-name', 'sender-id', 'msg', 'direction']
constructor() {
super()
const shadow = this.attachShadow({ mode: "open" })
}
connectedCallback() {
const shadow = this.shadowRoot
shadow.appendChild($('#message-normal-template').get(0).content.cloneNode(true))
$(shadow).find('#sender-name-left').hide()
this.update()
}
attributeChangedCallback(_name, _oldValue, _newValue) {
this.update()
}
update() {
const shadow = this.shadowRoot
// 消息视图的的左右方向
let isRightDirection = this.getAttribute('direction') == 'right'
$(shadow).find('#_direction_1').css('justify-content', isRightDirection ? 'flex-end' : 'flex-start')
$(shadow).find('#_direction_2').css('justify-content', isRightDirection ? 'flex-end' : 'flex-start')
$(shadow).find('#_direction_3').css('align-self', isRightDirection ? 'flex-end' : 'flex-start')
$(shadow).find('#_direction_3').css('margin-left', isRightDirection ? '' : '55px')
$(shadow).find('#_direction_3').css('margin-right', isRightDirection ? '55px' : '')
$(shadow).find('#sender-name-left')[isRightDirection ? 'show' : 'hide']()
$(shadow).find('#sender-name-right')[isRightDirection ? 'hide' : 'show']()
// 头像
let avatar = $(shadow).find('#avatar')
this.hasAttribute('avatar') ? avatar.attr('src', this.getAttribute('avatar')) : avatar.text((this.getAttribute('sender-name') || '').substring(0, 1))
// 发送者
$(shadow).find('#sender-name-left').text(this.getAttribute('sender-name'))
$(shadow).find('#sender-name-right').text(this.getAttribute('sender-name'))
$(shadow).find('#sender-id').text(this.getAttribute('sender-id'))
// 消息
this.hasAttribute('msg') && $(shadow).find('#msg').text(this.getAttribute('msg'))
}
}
class MessageSystem extends HTMLElement {
constructor() {
super()
const shadow = this.attachShadow({ mode: "open" })
}
connectedCallback() {
const shadow = this.shadowRoot
shadow.appendChild($('#message-system-template').get(0).content.cloneNode(true))
}
}
class MessageHolder extends HTMLElement {
constructor() {
super()
const shadow = this.attachShadow({ mode: "open" })
}
connectedCallback() {
const shadow = this.shadowRoot
shadow.appendChild($('#message-holder-template').get(0).content.cloneNode(true))
}
addMessage({ senderId = '', senderName = '', msg = '', avatar, direction = 'left' }, atStart) {
const v = new MessageNormal()
$(v).attr('sender-id', senderId).attr('sender-name', senderName).attr('avatar', avatar).attr('direction', direction).text(msg)
$(this)[atStart ? 'prepend' : 'append'](v)
}
addSystemMessage(msg, atStart) {
const v = new MessageSystem()
$(v).text(msg)
$(this)[atStart ? 'prepend' : 'append'](v)
}
getMessages(withSystemMessage) {
let ls = []
$(this).find('message-normal' + withSystemMessage ? ', message-system' : '').each((_i, e) => {
let a = $(e)
ls.push({
senderName: a.attr('sender-name'),
avatar: a.attr('avatar'),
senderId: a.attr('sender-id'),
direction: a.attr('direction'),
})
})
}
}
customElements.define('message-normal', MessageNormal)
customElements.define('message-system', MessageSystem)
customElements.define('message-holder', MessageHolder)
customElements.define('main-navigation-item', class extends mdui.NavigationRailItem {
static observedAttributes = ['img', 'id', 'text']
constructor() {
super()
const shadow = this.attachShadow({ mode: "open" })
}
connectedCallback() {
// 现在这是 mdui-navigation-rail-item, 不应该加到shadow而是自身
// 害得我修了好久
this.appendChild($('#main-navigation-item-template').get(0).content.cloneNode(true))
const self = this
const avatar = $(this).find('#avatar')
const avatarImg = $(this).find('#img')
avatarImg.bind('error', () => {
avatar.text((self.getAttribute('text') || '').substring(0, 1))
})
this.myUpdate()
super.connectedCallback()
}
attributeChangedCallback(_name, _oldValue, _newValue) {
this.myUpdate()
super.attributeChangedCallback()
}
myUpdate() {
this.hasAttribute('img') ? $(this).find('#img').attr('src', this.getAttribute('img')) : $(this).find('#avatar').text((this.getAttribute('text') || '').substring(0, 1))
$(this).find('#tip').attr('content', this.getAttribute('text'))
this.hasAttribute('id') && $(this).attr('value', this.getAttribute('id'))
}
})
customElements.define('message-img', class extends HTMLElement {
constructor() {
super()
}
connectedCallback() {
let e = new Image()
e.style.maxWidth = "100%"
e.style.maxHeight = "90%"
e.style.marginTop = "13px"
e.style.borderRadius = "var(--mdui-shape-corner-medium)"
e.src = $(this).attr('src')
e.alt = $(this).attr('alt')
e.onerror = () => {
$(this).html(`<br/><mdui-icon name="broken_image" style="font-size: 2rem;"></mdui-icon>`)
$(this).attr('alt', '图像损坏')
}
e.onclick = () => {
openImageViewer($(this).attr('src'))
}
this.appendChild(e)
}
})

View File

@@ -1,13 +0,0 @@
/*
* ©2024 The LingChair Project
*
* Make a more colorful world...
*
* License - Apache License 2.0
* Author - @MoonLeeeaf <https://github.com/MoonLeeeaf>
* Organization - @LingChair <https://github.com/LingChair>
*/
fetch('client_config.json').then((re) => re.json()).then((config) => {
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

View File

@@ -1,448 +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" />
<!-- Tools -->
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js">
</script>
<script src="https://cdn.jsdelivr.net/gh/GoogleChromeLabs/pinch-zoom@1.1.1/dist/pinch-zoom-min.js">
</script>
<!-- Connection -->
<script src="https://cdn.jsdelivr.net/npm/socket.io-client@4.7.5/dist/socket.io.min.js">
</script>
<title>TheWhiteSilk</title>
<link href="style-shared.css" rel="stylesheet" />
<script src="utils-shared.js">
</script>
</head>
<body>
<a id="download-helper" style="display: none;">
</a>
<!-- 自定义元素 -->
<!-- 消息列表容器 -->
<template id="message-holder-template">
<div style="display: flex; flex-direction: column; justify-content: flex-end; /* align-items: center */; padding-top: 10px; padding-bottom: 14px;"
id="holder">
<slot name="top" part="top">
</slot>
<!-- 默认槽位 如果没有 内部嵌套元素将不会被渲染 -->
<slot>
</slot>
</div>
</template>
<!-- 普通消息 -->
<template id="message-normal-template">
<style>
/* 精准指针设备 令桌面端 */
@media (pointer: fine) {
#_direction_3 {
max-width: 50%;
}
}
/* 不精准指针设备 令移动端 */
@media (pointer: coarse) {
#_direction_3 {
max-width: 77%;
}
}
</style>
<div style="display: none;" id="sender-id">
</div>
<div id="_direction_1" slot="trigger"
style="width: 100%; display: flex; justify-content: flex-start; flex-direction: column;">
<div id="_direction_2" style="display: flex; justify-content: flex-start;">
<span style="align-self: center; font-size: 90%;" id="sender-name-left">
</span>
<mdui-avatar style="width: 43px; height: 43px; margin: 11px;" id="avatar">
</mdui-avatar>
<span style="align-self: center; font-size: 90%;" id="sender-name-right">
</span>
</div>
<mdui-card id="_direction_3" variant="elevated"
style="min-width: 0%; margin-left: 55px; margin-top: -5px; padding: 15px; align-self: flex-start;">
<span id="msg" style="font-size: 94%;">
<slot>
</slot>
</span>
</mdui-card>
</div>
</template>
<!-- 系统消息 -->
<template id="message-system-template">
<style>
/* 精准指针设备 令桌面端 */
@media (pointer: fine) {
#msg {
max-width: 50%;
font-size: 94%;
}
}
/* 不精准指针设备 令移动端 */
@media (pointer: coarse) {
#msg {
max-width: 90%;
font-size: 90%;
}
}
</style>
<div style="width: 100%; flex-direction: column; display: flex; margin-top: 25px; margin-bottom: 20px;">
<mdui-card variant="filled" id="msg"
style="align-self: center; padding-top: 9px; padding-bottom: 9px; padding-left: 18px; padding-right: 18px; font-size: 92%;">
<slot>
</slot>
</mdui-card>
</div>
</template>
<!-- 普通消息 -->
<template id="message-normal-template">
<div style="display: none;" id="sender-id">
</div>
<div id="_direction_1" slot="trigger"
style="width: 100%; display: flex; justify-content: flex-start; flex-direction: column;">
<div id="_direction_2" style="display: flex; justify-content: flex-start;">
<span style="align-self: center; font-size: 90%;" id="sender-name-left">
</span>
<mdui-avatar style="width: 43px; height: 43px; margin: 11px;" id="avatar">
</mdui-avatar>
<span style="align-self: center; font-size: 90%;" id="sender-name-right">
</span>
</div>
<mdui-card id="_direction_3" variant="elevated"
style="max-width: 80%; min-width: 0%; margin-left: 55px; margin-top: -5px; padding: 15px; align-self: flex-start;">
<span id="msg">
<slot>
</slot>
</span>
</mdui-card>
</div>
</template>
<!-- 系统消息 -->
<template id="message-system-template">
<div style="width: 100%; flex-direction: column; display: flex; margin-top: 25px; margin-bottom: 20px;">
<mdui-card variant="filled" id="msg"
style="max-width: 90%; align-self: center; padding-top: 9px; padding-bottom: 9px; padding-left: 18px; padding-right: 18px; font-size: 92%;">
<slot>
</slot>
</mdui-card>
</div>
</template>
<!-- 侧边导航栏 - 列表项目 -->
<template id="main-navigation-item-template">
<mdui-tooltip slot="icon" id="tip">
<mdui-avatar id="avatar">
<img id="img" style="width: 100%; height: 100%; object-fit: contain;" />
</mdui-avatar>
</mdui-tooltip>
</template>
<script src="custom-elements-index.js">
</script>
<!-- 主视图 -->
<!-- 巨坑: 父元素的高度必须小于子元素 否则overflow-y无效!!! 参考: https://blog.csdn.net/Jet_Lover/article/details/121957321 -->
<div style="display: flex;" id="app">
<style>
/* 让侧边栏能多个图标滑动 */
mdui-navigation-rail::part(items) {
overflow-y: auto;
}
</style>
<mdui-navigation-rail>
<shadow-inner>
<style>
*::-webkit-scrollbar {
width: 0px !important;
}
</style>
</shadow-inner>
<mdui-button-icon lowered="lowered" icon="menu" slot="top">
</mdui-button-icon>
<mdui-button-icon lowered="lowered" icon="add" slot="top">
</mdui-button-icon>
<mdui-dropdown slot="top" trigger="hover">-
<mdui-button-icon icon="watch_later--outlined" slot="trigger" id="switch-navigation-list-button">
</mdui-button-icon>
<mdui-menu id="switch-navigation-list-menu" selects="single" value="1">
<mdui-menu-item value="0">详细列表<mdui-icon slot="icon" name="watch_later--outlined"
id="switch-navigation-list-info-menuicon">
</mdui-icon>
</mdui-menu-item>
<mdui-divider>
</mdui-divider>
<mdui-menu-item value="1">最近</mdui-menu-item>
<mdui-menu-item value="2">联系人</mdui-menu-item>
<mdui-menu-item value="3">群聊</mdui-menu-item>
</mdui-menu>
</mdui-dropdown>
<mdui-dropdown slot="bottom" trigger="hover">
<mdui-button-icon icon="settings" slot="trigger">
</mdui-button-icon>
<mdui-menu id="switch-navigation-list-menu">
<mdui-menu-item onclick="$('#dialog-about').get(0).open = true">关于</mdui-menu-item>
</mdui-menu>
</mdui-dropdown>
<div id="main-navigation-list-1" class="contents-only">
<main-navigation-item text="满月" img="https://avatars.githubusercontent.com/u/150461955?v=4">
</main-navigation-item>
<main-navigation-item text="满月" img="https://avatars.githubusercontent.com/u/150461955?v=4">
</main-navigation-item>
<main-navigation-item text="满月" img="https://avatars.githubusercontent.com/u/150461955?v=4">
</main-navigation-item>
<main-navigation-item text="满月" img="https://avatars.githubusercontent.com/u/150461955?v=4">
</main-navigation-item>
<main-navigation-item text="满月" img="https://avatars.githubusercontent.com/u/150461955?v=4">
</main-navigation-item>
<main-navigation-item text="满月" img="https://avatars.githubusercontent.com/u/150461955?v=4">
</main-navigation-item>
<main-navigation-item text="满月" img="https://avatars.githubusercontent.com/u/150461955?v=4">
</main-navigation-item>
<main-navigation-item text="满月" img="https://avatars.githubusercontent.com/u/150461955?v=4">
</main-navigation-item>
</div>
<div id="main-navigation-list-2" class="contents-only">
<main-navigation-item text="喵呜" img="114514">
</main-navigation-item>
</div>
<div id="main-navigation-list-3" class="contents-only">
<main-navigation-item text="裙子">
</main-navigation-item>
<main-navigation-item text="裙子">
</main-navigation-item>
<main-navigation-item text="裙子">
</main-navigation-item>
<main-navigation-item text="裙子">
</main-navigation-item>
<main-navigation-item text="裙子">
</main-navigation-item>
<main-navigation-item text="裙子">
</main-navigation-item>
<main-navigation-item text="裙子">
</main-navigation-item>
<main-navigation-item text="裙子">
</main-navigation-item>
<main-navigation-item text="裙子">
</main-navigation-item>
<main-navigation-item text="裙子">
</main-navigation-item>
<main-navigation-item text="裙子">
</main-navigation-item>
<main-navigation-item text="裙子">
</main-navigation-item>
<main-navigation-item text="裙子">
</main-navigation-item>
<main-navigation-item text="裙子">
</main-navigation-item>
<main-navigation-item text="裙子">
</main-navigation-item>
<main-navigation-item text="裙子">
</main-navigation-item>
<main-navigation-item text="裙子">
</main-navigation-item>
<main-navigation-item text="裙子">
</main-navigation-item>
</div>
</mdui-navigation-rail>
<mdui-dialog id="nav-list-information-dialog" close-on-overlay-click="close-on-overlay-click">
<shadow-inner>
<style>
*::-webkit-scrollbar {
width: 0px !important;
}
</style>
</shadow-inner>
<mdui-list>
</mdui-list>
<mdui-button slot="action" variant="text" onclick="this.parentNode.open = false">关闭</mdui-button>
</mdui-dialog>
<mdui-dialog close-on-overlay-click="close-on-overlay-click" id="dialog-about">
<shadow-inner>
<style>
*::-webkit-scrollbar {
width: 0px !important;
}
</style>
</shadow-inner>
<div slot="description" style="display: flex; margin-bottom: -4px;">
<mdui-avatar src="icon.ico">
</mdui-avatar>
<div style="margin-left: 17px;">
<span style="font-size: 1.15rem;">LingChair</span>
<br />
<span style="margin-top: 20px; font-size: 0.8rem;">
Nightly
<br />
<span style="margin-top: 5px">
<a href="https://github.com/LingChair/LingChair">GitHub</a> 查看源码
</span>
</span>
</div>
</div>
</mdui-dialog>
<style>
#image-viewer-dialog::part(panel) {
background: rgba(0, 0, 0, 0) !important;
padding: 0 !important;
}
#image-viewer-dialog>mdui-button-icon[icon=close] {
z-index: 114514;
position: fixed;
top: 15px;
right: 15px;
}
#image-viewer-dialog>mdui-button-icon[icon=download] {
z-index: 114514;
position: fixed;
top: 15px;
right: 65px;
}
</style>
<mdui-dialog id="image-viewer-dialog" fullscreen="fullscreen">
<mdui-button-icon icon="download"
onclick="downloadFromUrl($('#image-viewer-dialog-inner > *').attr('src')).catch((e) => mdui.snackbar({message: '无法下载, 也许是被拒绝了吧?', closeOnOutsideClick: true}))">
</mdui-button-icon>
<mdui-button-icon icon="close" onclick="this.parentNode.open = false">
</mdui-button-icon>
<pinch-zoom id="image-viewer-dialog-inner" class="size-as-window">
</pinch-zoom>
</mdui-dialog>
<div id="right-contents" style="flex-grow: 1; overflow-y: visible; position:relative;">
<mdui-top-app-bar style="position: sticky; /* 好耶 是黏黏的胶水 想被胶水姐姐死死黏住不放~啊~(〃∇〃)姐姐好温柔~~suki♥ */; top: 0;"
id="input_toolbar">
<mdui-dropdown trigger="hover">
<mdui-button-icon icon="arrow_drop_down" slot="trigger">
</mdui-button-icon>
<mdui-menu>
<mdui-menu-item>主面板</mdui-menu-item>
</mdui-menu>
</mdui-dropdown>
<mdui-top-app-bar-title>院审</mdui-top-app-bar-title>
<div style="flex-grow: 1">
</div>
<mdui-dropdown trigger="hover">
<mdui-button-icon icon="more_vert" slot="trigger">
</mdui-button-icon>
<mdui-menu>
<mdui-menu-item>资料<mdui-icon slot="icon" name="info">
</mdui-icon>
</mdui-menu-item>
<mdui-menu-item>设置<mdui-icon slot="icon" name="settings">
</mdui-icon>
</mdui-menu-item>
</mdui-menu>
</mdui-dropdown>
</mdui-top-app-bar>
<message-holder>
<mdui-button variant="text" slot="top" style="align-self: center;">加载更多</mdui-button>
<div class="contents-only">
<message-normal direction="left" sender-name="测试" sender-id="test">
我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神
</message-normal>
<message-normal direction="left" sender-name="测试" sender-id="test">启动</message-normal>
<message-normal direction="left" sender-name="测试" sender-id="test">
我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神
</message-normal>
<message-system>原神 被 测试 踢出了群聊</message-system>
<message-system>
满月姐姐满月姐姐满月姐姐满月姐姐满月姐姐满月姐姐满月姐姐满月姐姐满月姐姐满月姐姐满月姐姐满月姐姐满月姐姐满月姐姐满月姐姐满月姐姐满月姐姐满月姐姐满月姐姐满月姐姐满月姐姐
</message-system>
<message-normal direction="left" sender-name="测试" sender-id="test">启动</message-normal>
<message-normal direction="right" sender-name="阿弥诺斯" sender-id="阿弥诺斯">阿弥诺斯</message-normal>
<message-normal direction="right" sender-name="阿弥诺斯" sender-id="阿弥诺斯">
我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神
</message-normal>
<message-normal direction="right" sender-name="阿弥诺斯" sender-id="阿弥诺斯">我要玩原神<br />
<message-img src="https://www.yuanshen.com/images/ys.96a55539.png" />
</message-normal>
<message-normal direction="right" sender-name="阿弥诺斯" sender-id="阿弥诺斯">我要玩院审😭👊<br />
<message-img
src="https://huiji-thumb.huijistatic.com/aceattorney/uploads/thumb/e/e6/Skärmbild_(985).png/640px-Skärmbild_(985).png" />
</message-normal>
</div>
</message-holder>
<mdui-top-app-bar
style="z-index: 100; bottom: 0; position: sticky; /* 好耶 是黏黏的胶水 想被胶水姐姐死死黏住不放~啊~(〃∇〃)姐姐好温柔~~suki♥ */"
scroll-target="#right-contents" id="input_toolbar">
<mdui-text-field placeholder="(=・ω・=)" variant="outlined" autosize="autosize" id="input_message"
style="align-self: center; flex-grow: 1; max-height: 46px;">
</mdui-text-field>
<style>
#input_message::part(input) {
line-height: 15px;
}
</style>
<mdui-button-icon icon="send" id="send_message">
</mdui-button-icon>
<mdui-dropdown trigger="hover">
<mdui-button-icon icon="more_vert" slot="trigger">
</mdui-button-icon>
<mdui-menu>
<mdui-menu-item>插入图片<mdui-icon slot="icon" name="image">
</mdui-icon>
</mdui-menu-item>
<mdui-menu-item>插入音频<mdui-icon slot="icon" name="keyboard_voice">
</mdui-icon>
</mdui-menu-item>
<mdui-menu-item>插入文件<mdui-icon slot="icon" name="insert_drive_file">
</mdui-icon>
</mdui-menu-item>
<mdui-menu-item>插入链接<mdui-icon slot="icon" name="link">
</mdui-icon>
</mdui-menu-item>
<mdui-divider>
</mdui-divider>
<mdui-menu-item>插入名片<mdui-icon slot="icon" name="account_box">
</mdui-icon>
</mdui-menu-item>
<mdui-divider>
</mdui-divider>
<mdui-menu-item>展开输入框<mdui-icon slot="icon" name="open_in_full">
</mdui-icon>
</mdui-menu-item>
</mdui-menu>
</mdui-dropdown>
</mdui-top-app-bar>
</div>
</div>
<script src="client-side.js">
</script>
<script src="ui-controller-shared.js">
</script>
<script src="ui-controller-index.js">
</script>
<script src="global-appconfig.js">
</script>
</body>
</html>

View File

@@ -1,62 +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" />
<!-- Tools -->
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js">
</script>
<!-- Connection -->
<script src="https://cdn.jsdelivr.net/npm/socket.io-client@4.7.5/dist/socket.io.min.js">
</script>
<title>登录到 铃之椅</title>
<link href="style-shared.css" rel="stylesheet" />
<script src="utils-shared.js">
</script>
</head>
<body style="display: flex; flex-direction: column;" id="app">
<div class="size-as-window"
style="margin-top: calc(var(--window-height) / 2 - 190px); margin-left: 30px; margin-right: 30px;">
<h2>登录到 铃之椅</h2>
<mdui-text-field label="账号" style="margin-top: 30px;">
</mdui-text-field>
<mdui-text-field label="密码" type="password" toggle-password="toggle-password" style="margin-top: 30px;">
</mdui-text-field>
<div style="margin-top: 30px; display: flex; justify-content: center; margin-left: 40px; margin-right: 40px;">
<mdui-button class="fill-width">登录账号</mdui-button>
<div style="margin: 5px">
</div>
<mdui-button class="fill-width">注册账号</mdui-button>
<div style="margin: 5px">
</div>
<mdui-button class="fill-width">游客登录</mdui-button>
</div>
</div>
<span style="position: fixed; bottom: 10px; width: 100%; text-align: center;">©2024 满月叶</span>
<script src="ui-controller-shared.js">
</script>
<script src="client-side.js">
</script>
<script src="global-appconfig.js">
</script>
</body>
</html>

View File

@@ -1,87 +0,0 @@
/*
* ©2024 The LingChair Project
*
* Make a more colorful world...
*
* License - Apache License 2.0
* Author - @MoonLeeeaf <https://github.com/MoonLeeeaf>
* Organization - @LingChair <https://github.com/LingChair>
*/
/* 滑条*/
.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;
}
/* 防止小尺寸图片模糊*/
* {
image-rendering: -moz-crisp-edges;
image-rendering: -o-crisp-edges;
image-rendering: -webkit-optimize-contrast;
image-rendering: crisp-edges;
-ms-interpolation-mode: nearest-neighbor;
}
/* 与窗口同大小*/
.size-as-window {
width: var(--window-width);
height: var(--window-height);
}
/* Flex - 铺满*/
.fill-width {
width: 100%;
}
.fill-height {
height: 100%;
}
/* 链接使用主题色*/
a {
color: rgb(var(--mdui-color-primary));
}
.contents-only {
display: contents;
}

View File

@@ -1,190 +0,0 @@
/*
* ©2024 The LingChair Project
*
* Make a more colorful world...
*
* License - Apache License 2.0
* Author - @MoonLeeeaf <https://github.com/MoonLeeeaf>
* Organization - @LingChair <https://github.com/LingChair>
*/
/**
* ========================================================
* 侧边导航栏列表
* ========================================================
*/
// 按钮:查看详细列表
$('#switch-navigation-list-info-menuicon').parent().click(() => {
let list = $('#nav-list-information-dialog > mdui-list').empty()
let selected = $('#switch-navigation-list-menu').get(0).value
$('#main-navigation-list-' + selected + " > main-navigation-item").each((_i, e) => {
let i = $.parseHTML(`<mdui-list-item rounded></mdui-list-item>`)
let a = $.parseHTML(`<mdui-avatar slot="icon"></mdui-avatar>`)
let img = new Image()
img.style.cssText = `width: 100%; height: 100%; object-fit: contain;`
img.src = e.getAttribute('img')
img.onerror = () => {
$(a).text((e.getAttribute('text') || '').substring(0, 1))
}
$(a).append(img)
$(i).append(e.getAttribute('text'))
$(i).append(a)
list.append(i)
})
$('#nav-list-information-dialog').attr('headline', (function () {
let t
switch (selected + '') {
case "1":
t = '最近'
break;
case "2":
t = '联系人'
break;
case "3":
t = '群组'
break;
}
return t
})())
$('#nav-list-information-dialog').attr('open', true)
})
// 切换列表选项
let lastValue = $('#switch-navigation-list-menu').get(0).value
$('#switch-navigation-list-menu').on('change', (e) => {
// 当前选择的值
let value = e.target.value
// 特殊:详细列表
if (value == 0) {
// 选回原来的选项
e.target.value = lastValue
value = lastValue
$('#switch-navigation-list-menu > mdui-menu-item[value=' + value + ']').get(0).selected = true
$('#switch-navigation-list-menu > mdui-menu-item[value=0]').get(0).selected = false
// 错误示范: 在这里写点击事件
}
// 禁止空选择
if (value == null) {
e.target.value = lastValue
value = lastValue
$('#switch-navigation-list-menu > mdui-menu-item[value=' + value + ']').get(0).selected = true
}
// 显示指定的列表
$('#main-navigation-list-1').hide()
$('#main-navigation-list-2').hide()
$('#main-navigation-list-3').hide()
$('#main-navigation-list-' + value).show()
// 修改图标
let icon = (function () {
let ico
switch (value + '') {
case "1":
ico = 'watch_later--outlined'
break;
case "2":
ico = 'contacts--outlined'
break;
case "3":
ico = 'group--outlined'
break;
}
return ico
})()
$('#switch-navigation-list-button').attr('icon', icon)
$('#switch-navigation-list-info-menuicon').attr('name', icon)
// 更新最后的值用以防止空选择
lastValue = value
})
// 最开始只选择 最近, 隐藏其他列表
$('#main-navigation-list-2').hide()
$('#main-navigation-list-3').hide()
// 子项目被点击时
$('mdui-navigation-rail').on('click', (event) => {
let e = event.target
let tagName = e.tagName.toLowerCase()
while (tagName != 'main-navigation-item') {
e = e.parentNode
tagName = (e.tagName || 'mdui-navigation-rail').toLowerCase()
if (tagName == 'mdui-navigation-rail') return
}
// 获取到Item
})
/**
* ========================================================
* 输入框与消息编辑
* ========================================================
*/
windowOnResizingCallbacks.push((w, h) => {
$('#input_message').width(w - ($('mdui-navigation-rail').width() + $('#send_message').width() * 2 + 100))
})
/**
* ========================================================
* 消息列表
* ========================================================
*/
function scrollMessageHolderToBottom() {
window.scrollBy({
top: 1145141919810,
behavior: "smooth",
})
}
/**
* ========================================================
* 图片查看对话框
* ========================================================
*/
function openImageViewer(src) {
$('#image-viewer-dialog-inner').empty()
let e = new Image()
e.src = src
e.onerror = () => {
$('#image-viewer-dialog-inner').empty()
$('#image-viewer-dialog-inner').append($.parseHTML(`<mdui-icon name="broken_image" style="font-size: 2rem;"></mdui-icon>`))
}
$('#image-viewer-dialog-inner').append(e)
e.onload = () => $('#image-viewer-dialog-inner').get(0).setTransform({
scale: 0.6,
x: $(window).width() / 2 - (e.width / 4),
y: $(window).height() / 2 - (e.height / 3),
})
$('#image-viewer-dialog').get(0).open = true
}
/**
* ========================================================
* 下载
* ========================================================
*/
async function downloadFromUrl(src) {
let re = await fetch(src)
let blob = await re.blob()
let url = URL.createObjectURL(blob)
$('#download-helper').attr('download', url).attr('href', url).get(0).click()
setTimeout(() => URL.revokeObjectURL(url), 10000)
}
/**
* ========================================================
* 杂项
* ========================================================
*/
// mdui.setColorScheme("#FFB4AA")

View File

@@ -1,41 +0,0 @@
/*
* ©2024 The LingChair Project
*
* Make a more colorful world...
*
* License - Apache License 2.0
* Author - @MoonLeeeaf <https://github.com/MoonLeeeaf>
* Organization - @LingChair <https://github.com/LingChair>
*/
/**
* ========================================================
* 窗口大小
* ========================================================
*/
// 在窗口加载完毕后会将所有页面大小变化的回调都调用一次
const windowOnResizingCallbacks = []
function updateWindowSize() {
document.body.style.setProperty('--window-width', `${window.innerWidth}px`)
document.body.style.setProperty('--window-height', `${window.innerHeight}px`)
windowOnResizingCallbacks.forEach((v) => v(window.innerWidth, window.innerHeight))
}
window.addEventListener('resize', updateWindowSize)
// 初步确定(值有偏差)
$(() => updateWindowSize())
// 完全确定(值已经确定)
window.addEventListener('load', updateWindowSize)
/**
* ========================================================
* Shadow 元素辅助代码
* ========================================================
*/
// 将组件添加到影子DOM中
$(() => $('* > shadow-inner').each((_i, v) => {
$(v.parentElement.shadowRoot).append(v.children)
v.parentNode.removeChild(v)
}))

View File

@@ -1,30 +0,0 @@
/*
* ©2024 The LingChair Project
*
* Make a more colorful world...
*
* License - Apache License 2.0
* Author - @MoonLeeeaf <https://github.com/MoonLeeeaf>
* Organization - @LingChair <https://github.com/LingChair>
*/
/**
* ========================================================
* 移动端检测
* ========================================================
*/
const isMobile = () => ('ontouchstart' in document.documentElement)
/**
* ========================================================
* 移动端调试
* ========================================================
*/
if (isMobile()) {
let a = document.createElement('script')
a.src = "https://unpkg.com/eruda/eruda.js"
a.onload = () => eruda.init()
document.head.appendChild(a)
}

3
compile-webpage.ts Normal file
View File

@@ -0,0 +1,3 @@
import config from './server/config.ts'
config.ensureAllDirsAreCreated()
await import('./server/build.ts').then(a => a.default(config.dirs.WEB_PAGE_DIR))

20
deno.jsonc Normal file
View File

@@ -0,0 +1,20 @@
{
// 導入包
"imports": {
// API & Web
"express": "npm:express@^4.21.2",
"socket.io": "npm:socket.io@^4.8.1",
// Database
"sequelize": "npm:sequelize@^6.37.7",
"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",
"babel-minify": "npm:babel-minify@^0.5.2"
},
"tasks": {
// 編譯前端頁面
"compile-webpage": "deno run --allow-read --allow-write --allow-import --allow-env ./compile-webpage.ts"
}
}

317
deno.lock generated Normal file
View File

@@ -0,0 +1,317 @@
{
"version": "4",
"specifiers": {
"npm:@babel/core@^7.26.10": "7.27.4",
"npm:@types/babel-core@*": "6.25.10",
"npm:@types/babel__core@*": "7.20.5",
"npm:@types/node@*": "22.5.4"
},
"npm": {
"@ampproject/remapping@2.3.0": {
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
"dependencies": [
"@jridgewell/gen-mapping",
"@jridgewell/trace-mapping"
]
},
"@babel/code-frame@7.27.1": {
"integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
"dependencies": [
"@babel/helper-validator-identifier",
"js-tokens",
"picocolors"
]
},
"@babel/compat-data@7.27.5": {
"integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg=="
},
"@babel/core@7.27.4": {
"integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==",
"dependencies": [
"@ampproject/remapping",
"@babel/code-frame",
"@babel/generator",
"@babel/helper-compilation-targets",
"@babel/helper-module-transforms",
"@babel/helpers",
"@babel/parser",
"@babel/template",
"@babel/traverse",
"@babel/types",
"convert-source-map",
"debug",
"gensync",
"json5",
"semver"
]
},
"@babel/generator@7.27.5": {
"integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==",
"dependencies": [
"@babel/parser",
"@babel/types",
"@jridgewell/gen-mapping",
"@jridgewell/trace-mapping",
"jsesc"
]
},
"@babel/helper-compilation-targets@7.27.2": {
"integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
"dependencies": [
"@babel/compat-data",
"@babel/helper-validator-option",
"browserslist",
"lru-cache",
"semver"
]
},
"@babel/helper-module-imports@7.27.1": {
"integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
"dependencies": [
"@babel/traverse",
"@babel/types"
]
},
"@babel/helper-module-transforms@7.27.3_@babel+core@7.27.4": {
"integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==",
"dependencies": [
"@babel/core",
"@babel/helper-module-imports",
"@babel/helper-validator-identifier",
"@babel/traverse"
]
},
"@babel/helper-string-parser@7.27.1": {
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="
},
"@babel/helper-validator-identifier@7.27.1": {
"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="
},
"@babel/helper-validator-option@7.27.1": {
"integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="
},
"@babel/helpers@7.27.6": {
"integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==",
"dependencies": [
"@babel/template",
"@babel/types"
]
},
"@babel/parser@7.27.5": {
"integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==",
"dependencies": [
"@babel/types"
]
},
"@babel/template@7.27.2": {
"integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
"dependencies": [
"@babel/code-frame",
"@babel/parser",
"@babel/types"
]
},
"@babel/traverse@7.27.4": {
"integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==",
"dependencies": [
"@babel/code-frame",
"@babel/generator",
"@babel/parser",
"@babel/template",
"@babel/types",
"debug",
"globals"
]
},
"@babel/types@7.27.6": {
"integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==",
"dependencies": [
"@babel/helper-string-parser",
"@babel/helper-validator-identifier"
]
},
"@jridgewell/gen-mapping@0.3.8": {
"integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
"dependencies": [
"@jridgewell/set-array",
"@jridgewell/sourcemap-codec",
"@jridgewell/trace-mapping"
]
},
"@jridgewell/resolve-uri@3.1.2": {
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="
},
"@jridgewell/set-array@1.2.1": {
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A=="
},
"@jridgewell/sourcemap-codec@1.5.0": {
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
},
"@jridgewell/trace-mapping@0.3.25": {
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
"dependencies": [
"@jridgewell/resolve-uri",
"@jridgewell/sourcemap-codec"
]
},
"@types/babel-core@6.25.10": {
"integrity": "sha512-VyqZTf+n8wj+8Powi4k2PwQiHT6ESJb6BEX5IETfmPzdZ9G2sOtFIopdpC2TI3T4Tmf1Shys2QeRv4ZBi4U2EA==",
"dependencies": [
"@types/babel-generator",
"@types/babel-template",
"@types/babel-traverse",
"@types/babel-types",
"@types/babylon"
]
},
"@types/babel-generator@6.25.8": {
"integrity": "sha512-f5l89J0UpYhTE6TFCxy3X+8pJVru1eig1fcvF9qHmOk9h1VxZimd+++tu5GShntCOdhE/MoZZ0SlpGTyh4XrKg==",
"dependencies": [
"@types/babel-types"
]
},
"@types/babel-template@6.25.5": {
"integrity": "sha512-1f+n65xfg9ukqz+BaHYrCWe3Fymv4Ho0i9fU/MHri8MQKU+S4n+vexR5hXs+94d/fsaMLMP45q0hX6FRspKLiQ==",
"dependencies": [
"@types/babel-types",
"@types/babylon"
]
},
"@types/babel-traverse@6.25.10": {
"integrity": "sha512-B3XitTFG8YeXb5lr7Nj62t1DikCDuAJ/4BDeK6GCuWREEmdunI9DWnv+81oHl2yQBQPWY/C1PmV3vaRZB4LQmw==",
"dependencies": [
"@types/babel-types"
]
},
"@types/babel-types@7.0.16": {
"integrity": "sha512-5QXs9GBFTNTmilLlWBhnsprqpjfrotyrnzUdwDrywEL/DA4LuCWQT300BTOXA3Y9ngT9F2uvmCoIxI6z8DlJEA=="
},
"@types/babel__core@7.20.5": {
"integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
"dependencies": [
"@babel/parser",
"@babel/types",
"@types/babel__generator",
"@types/babel__template",
"@types/babel__traverse"
]
},
"@types/babel__generator@7.27.0": {
"integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
"dependencies": [
"@babel/types"
]
},
"@types/babel__template@7.4.4": {
"integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
"dependencies": [
"@babel/parser",
"@babel/types"
]
},
"@types/babel__traverse@7.20.7": {
"integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==",
"dependencies": [
"@babel/types"
]
},
"@types/babylon@6.16.9": {
"integrity": "sha512-sEKyxMVEowhcr8WLfN0jJYe4gS4Z9KC2DGz0vqfC7+MXFbmvOF7jSjALC77thvAO2TLgFUPa9vDeOak+AcUrZA==",
"dependencies": [
"@types/babel-types"
]
},
"@types/node@22.5.4": {
"integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==",
"dependencies": [
"undici-types"
]
},
"browserslist@4.25.0": {
"integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==",
"dependencies": [
"caniuse-lite",
"electron-to-chromium",
"node-releases",
"update-browserslist-db"
]
},
"caniuse-lite@1.0.30001723": {
"integrity": "sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw=="
},
"convert-source-map@2.0.0": {
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="
},
"debug@4.4.1": {
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
"dependencies": [
"ms"
]
},
"electron-to-chromium@1.5.167": {
"integrity": "sha512-LxcRvnYO5ez2bMOFpbuuVuAI5QNeY1ncVytE/KXaL6ZNfzX1yPlAO0nSOyIHx2fVAuUprMqPs/TdVhUFZy7SIQ=="
},
"escalade@3.2.0": {
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="
},
"gensync@1.0.0-beta.2": {
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="
},
"globals@11.12.0": {
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="
},
"js-tokens@4.0.0": {
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"jsesc@3.1.0": {
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="
},
"json5@2.2.3": {
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="
},
"lru-cache@5.1.1": {
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
"dependencies": [
"yallist"
]
},
"ms@2.1.3": {
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node-releases@2.0.19": {
"integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="
},
"picocolors@1.1.1": {
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
},
"semver@6.3.1": {
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="
},
"undici-types@6.19.8": {
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="
},
"update-browserslist-db@1.1.3_browserslist@4.25.0": {
"integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
"dependencies": [
"browserslist",
"escalade",
"picocolors"
]
},
"yallist@3.1.1": {
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
}
},
"workspace": {
"dependencies": [
"npm:@babel/core@^7.26.10",
"npm:@babel/preset-env@^7.26.9",
"npm:@babel/preset-react@^7.26.3",
"npm:babel-minify@~0.5.2",
"npm:express@^4.21.2",
"npm:sequelize@^6.37.7",
"npm:socket.io@^4.8.1",
"npm:sqlite3@^5.1.7"
]
}
}

View File

@@ -1,4 +1,4 @@
Copyright © 2024-2025 月有阴晴圆缺
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:

4862
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +0,0 @@
{
"private": "true",
"type": "module",
"dependencies": {
"@types/react": "^19.1.1",
"@types/react-dom": "^19.1.2",
"express": "^4.21.2",
"socket.io": "^4.8.1"
},
"devDependencies": {
"@babel/core": "^7.26.10",
"@babel/preset-env": "^7.26.9",
"@babel/preset-react": "^7.26.3",
"babel-minify": "^0.5.2"
}
}

View File

@@ -1,42 +0,0 @@
## TheWhiteSilk
仍在开发中...
### 开发清单
* 前端
* 侧边导航栏
* 用户资料卡入口
* 最近聊天列表
* 联系人列表
* 聊天页面
* 聊天消息组件 🥰
* 右键菜单
* 消息编辑框
* 顶栏
* 设置页面
* 资料卡页面
* 登录页面
* 后端
* 数据库 (纯文本) 🥰 - 虽然我知道这很低效但是我目前只会使用这个 以后会考虑更换
* 用户
* 初始化信息
* 唯一 ID (不可用于**用户**查询账号, 管理用途) 🥰
* 安全
* 令牌机制
* 单令牌自动刷新
* 多客户端多令牌
* 两步验证
* 多客户端 - 目前打算和令牌机制配合使用 根据环境特征(UA, Fake ID, Android ID, ...)
* 资料
* 用户名(仅用于**用户**查询, 因为随时可改, 因此不具备管理用途) 🥰
* 用户头像 🥰
* 昵称 🥰
* 用户简介 🥰
* ...
### 关于
创日: 2025/3/17

View File

@@ -1,39 +0,0 @@
export class TheWhiteSilkParams {
/**
* @type { String }
*/
method
/**
* @type { Object }
*/
args
}
export class CallbackMessage {
static Code = class {
/**
* 无权限
*/
static PERMISSION_DENIED = 401
/**
* 不存在
*/
static NOT_FOUND = 404
/**
* 服务端错误
*/
static SERVER_ERROR = 500
/**
* 请求成功
*/
static OK = 200
}
/**
* @type { String }
*/
msg
/**
* @type { Number }
*/
code
}

View File

@@ -1,55 +0,0 @@
import io from '../lib/io.js';
import { sha256 } from '../lib/crypto.js'
const baseDir = 'whitesilk_data/chat'
io.mkdirs(baseDir)
export class ChatManager {
/**
* 获取私聊实例 (双方可对调)
* @param { String } a 用户A 的 ID
* @param { String } b 用户B 的 ID
* @returns { Chat }
*/
static getPrivateChat(a, b) {
let id = sha256([
a,
b,
].sort().join())
io.mkdirs(`${baseDir}/${id}`)
let chat = new Chat(id)
chat.id = id
chat.updateInfo()
return chat
}
}
export class Chat {
constructor(id) {
if (!io.exists(`${baseDir}/${id}`)) throw new Error(`聊天 [id=${id}]不存在!`)
// 尽管所有的键都是 undefined 但是仍然是键哦
for (let k of Object.keys(this)) {
this[k] = io.open(`${baseDir}/${id}/${k}`, 'rw').checkExistsOrWrite('').readAllAndClose().toString()
}
}
updateInfo() {
// 尽管所有的键都是 undefined 但是仍然是键哦
for (let k of Object.keys(this)) {
io.open(`${baseDir}/${this.id}/${k}`, 'w').writeAll((this[k] || '') + '').close()
}
// 防止服务端错误修改此值 主要是都是属性了再搞特殊对待很麻烦的
io.open(`${baseDir}/${this.id}/id`, 'w').writeAll(this.id + '').close()
}
/**
* 聊天 ID
* @type { String }
*/
id
}
export class ChatApi {
static createUser() {
}
}

View File

@@ -1,139 +0,0 @@
import io from '../lib/io.js';
import { sha256 } from '../lib/crypto.js'
import { CallbackMessage } from '../Types.js';
const baseDir = 'whitesilk_data/user'
io.mkdirs(baseDir)
export class UserManager {
static getUserProfileDir(id) {
return `${baseDir}/${id}`
}
static getUserById(id) {
return new User(id)
}
static getUserByName(name) {
let list = io.listFolders(baseDir, {
fullPath: false,
})
}
/**
* 创建新用户
* @param { Object } arg
* @param { String } [arg.name] 用户名
* @returns { User }
*/
static createUser({ name } = {}) {
let idCountFile = io.open(`${baseDir}/idcount`, 'rw').checkExistsOrWrite('10000')
let idCount = parseInt(idCountFile.readAll())
io.mkdirs(`${baseDir}/${idCount}`)
idCount++
idCountFile.writeAll(idCount + '').close()
idCount--
let user = new User(idCount)
user.id = idCount
user.name = name
user.updateProfile()
return user
}
}
export class User {
constructor(id) {
if (!io.exists(`${baseDir}/${id}`)) throw new Error(`用户 [id=${id}]不存在!`)
// 尽管所有的键都是 undefined 但是仍然是键哦
for (let k of Object.keys(this)) {
this[k] = io.open(`${baseDir}/${id}/${k}`, 'rw').checkExistsOrWrite('').readAllAndClose().toString()
}
}
updateProfile() {
// 尽管所有的键都是 undefined 但是仍然是键哦
for (let k of Object.keys(this)) {
io.open(`${baseDir}/${this.id}/${k}`, 'w').writeAll((this[k] || '') + '').close()
}
// 防止服务端错误修改此值 主要是都是属性了再搞特殊对待很麻烦的
io.open(`${baseDir}/${this.id}/id`, 'w').writeAll(this.id + '').close()
}
/**
* 设置头像
* @param { Buffer } data 头像数据
*/
setAvatar(data) {
io.open(`${baseDir}/${this.id}/avatar`, 'w').writeAll(data).close()
}
/**获取头像
* @returns { Buffer } data 头像数据
*/
getAvatar() {
return io.open(`${baseDir}/${this.id}/avatar`, 'r').readAllAndClose()
}
/**
* 用户 ID
* @type { String }
*/
id
/**
* 用户名
* @type { String }
*/
name
/**
* 用户昵称
* @type { String }
*/
nick
/**
* 用户简介
* @type { String }
*/
description
/**
* 密码(经过加盐哈希处理后的 哈希后的密码)
* @type { String }
*/
passwordHashed
}
export const UserApi = {
API_NAME: 'UserApi',
/**
* 注册用户
* @param { Object } args
* @param { String } args.name 用户名(必须, 注册后可以删除)
* @param { String } args.password 密码(哈希后)
* @returns { CallbackMessage }
*/
["createUser"]: (args) => {
// TODO: 想办法解决账号注册的问题
// 思路之一: 扫名称 重复则不允许
// 思路之二: 邮箱制 但是无法多账号 以及其他遗留问题
// 长远思考
// 2025.6.2 決定
// 使用郵箱驗證, 以賬號ID為基準, 用戶名可改但不可重複制度
// 關聯性字符串數據庫
// 具體方案: 先使用郵箱驗證
// 再注冊賬號
// 賬號支持修改不重複的用戶名
// 可以一個郵箱多個賬號
// 修改郵箱 = 修改文件名
// 多賬號管理 = 郵箱 (文件夾) + 多賬戶ID
// 忘記賬號 = 郵箱驗證 + 給出所有賬號
let user = UserManager.createUser({
name: args.name,
})
user.passwordHashed = sha256('我是盐 这个后面会给成配置文件来配置的喵~' + args.password)
user.updateProfile()
return {
msg: '🥰🥰🥰🥰🥰',
code: CallbackMessage.Code.OK,
data: args,
}
},
}

View File

@@ -1,7 +1,8 @@
// @ts-types="npm:@types/babel__core"
import babel from '@babel/core'
import io from './lib/io.js'
function compileJs(path) {
function compileJs(path: string) {
babel.transformFileAsync(path, {
presets: [
[
@@ -22,13 +23,14 @@ function compileJs(path) {
},
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) {
export default function (path: string) {
io.listFiles(path, {
recursive: true,
fullPath: true,

13
server/config.ts Normal file
View File

@@ -0,0 +1,13 @@
import io from './lib/io.js'
export default class Config {
static ensureAllDirsAreCreated() {
for (const key of Object.keys(Config.dirs)) {
io.mkdirs(Config.dirs[key])
}
}
static BASE_DIR = 'whitesilk'
static dirs : { [key: string]: string } = {
WEB_PAGE_DIR: this.BASE_DIR + '/_webpage'
}
}

View File

@@ -1,50 +0,0 @@
import http from 'node:http'
// import https from 'node:https' // 暂时
import express from 'express'
import { Server as SocketIoServer } from 'socket.io'
// 类型提示
import { TheWhiteSilkParams, CallbackMessage } from './Types.js'
const app = express()
const httpApp = http.createServer(app)
const sio = new SocketIoServer(httpApp, {})
// 编译前端代码
import io from './lib/io.js'
io.copyDir('./client/', './whitesilk_data/page_builded/')
await import('./build.js').then(a => a.default('whitesilk_data/page_builded/'))
app.use('/', express.static('whitesilk_data/page_builded/'))
const events = {}
import { UserApi } from './api/User.js'
for (let i of [
UserApi,
]) {
for (let i2 of Object.keys(i)) {
if (i2 == 'API_NAME') continue
events[i.API_NAME + '.' + [i2]] = i[i2]
}
}
sio.on("connection", (socket) => {
socket.on('the_white_silk',
/**
* @param { TheWhiteSilkParams } params
* @param { Function } callback
*/
(params, callback) => {
if ((params || callback) == null || typeof callback != 'function') return;
/** @type { CallbackMessage } */
let data = events[params.method] ? events[params.method](params.args) : {
msg: '找不到此方法',
code: CallbackMessage.Code.NOT_FOUND,
}
callback(data)
}
)
})
httpApp.listen(80)

0
start-server.ts Normal file
View File