10 Commits

Author SHA1 Message Date
MoonLeeeaf
71b5b5b2df test: webpack 2024-05-25 10:44:35 +08:00
MoonLeeeaf
9c1dc4c540 chore: readme 2024-05-24 20:54:35 +08:00
MoonLeeeaf
f3bf5f88b5 chore: 软键盘调整布局(测试中) 2024-05-19 23:11:31 +08:00
MoonLeeeaf
971b8aff85 chore: Settings dialog UI 2024-05-19 22:41:28 +08:00
MoonLeeeaf
ddfbe547e1 chore: readme 2024-05-19 13:47:08 +08:00
MoonLeeeaf
b4bbe08ff2 fix: 无法设置昵称 2024-05-19 12:17:00 +08:00
MoonLeeeaf
6ec0005122 chore: ui 2024-05-19 12:14:47 +08:00
MoonLeeeaf
541f043531 feat: 新的界面布局方式(WIP) 2024-05-18 16:13:29 +08:00
MoonLeeeaf
7253f0a4ec chore: 项目说明, feat: 添加好友(WIP), fix: 输入框无法置底 2024-05-18 14:29:33 +08:00
MoonLeeeaf
f19c3c793a chore: 剪贴板相关 2024-05-17 23:35:07 +08:00
31 changed files with 2135 additions and 72 deletions

39
.github/QA.md vendored Normal file
View File

@@ -0,0 +1,39 @@
### Q&A
#### 1. 协助项目
1. 若修改代码,请务必添加注释或者尽可能让方法名能被人所理解
2. 修改 readme 相关时,请不要留废话
3. 其它非代码注明性注释会不定期清理
4. 待补充
#### 2. 商业用途
不建议,不推荐,不赞同用于商业用途, 本项目的设计初衷是为了个人和团队使用, 而非用于圈钱, 因此如果确实需要商业化, 请考虑其他项目
因为这个项目还在初级阶段, 还有很多地方不够完善, 甚至有很多漏洞被利用, 造成不必要的损失
#### 3. 提问
1. 禁止人身攻击性回复
2. 请详细说明你的问题
3. 漏洞、BUG 类请给出代码位置
#### 4. 功能请求
目前不考虑,因为每一个做开源的人都应该以生活,以自己的感受为本
就像 weishu 大佬所说的:
> 我发现很多搞开源的开发者都把自己弄得很累
> 其实你把它当作是钓鱼,摩托,音响,单反那样的兴趣爱好就好多了
> 既然是爱好,就不要搞得像打第二份工一样,开心了就玩,玩腻了就扔一边
另外如果你提交功能 PR我有可能会直接 Close我没有太多精力去维护一个用不上的功能
#### 5. 待补充

BIN
.github/模块化_Babel.zip vendored Normal file

Binary file not shown.

BIN
.github/模块化_Webpack.zip vendored Normal file

Binary file not shown.

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
node_modules/ node_modules/
ling_chair_data/ ling_chair_data/
ling_chair_config/ ling_chair_config/
ling_chair_http/

1
babel_lib/.gitignore vendored Normal file
View File

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

3
babel_lib/readme.md Normal file
View File

@@ -0,0 +1,3 @@
## Babel
请从本仓库根目录的 .github 文件夹内找到对应的zip文件并把 node_modules 解压在此,方可使用

View File

@@ -43,3 +43,12 @@ body {
margin-top: 60px; margin-top: 60px;
z-index: 100; z-index: 100;
} }
[n-id=pageChatSeesion]::after {
content: "";
position: sticky;
bottom: 0;
display: block;
height: var(--pseudo-height); /* 设置伪元素的高度 */
z-index: -1; /* 防止遮挡实际内容 */
}

View File

@@ -28,9 +28,9 @@
<meta name="force-rendering" content="webkit" /> <meta name="force-rendering" content="webkit" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" /> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<!-- 给老旧的设备提供支持 支持不了, 照样没法运行 --> <!-- 给老旧的设备提供支持 支持不了, 照样没法运行 不 试一下红米2可不可以-->
<!-- <script src='https://polyfill.io/v3/polyfill.min.js?features=default%2Cdom4%2Ces2015%2Ces2016%2Ces2017%2Ces2018%2Ces2019%2Ces2020%2Ces2021%2Ces2022%2Ces5%2Ces6%2Ces7'></script> <script src='https://polyfill.io/v3/polyfill.min.js?features=default%2Cdom4%2Ces2015%2Ces2016%2Ces2017%2Ces2018%2Ces2019%2Ces2020%2Ces2021%2Ces2022%2Ces5%2Ces6%2Ces7'></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> --> <!-- <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> -->
<!-- Styles --> <!-- Styles -->
<link rel="stylesheet" href="https://unpkg.com/mdui@1.0.2/dist/css/mdui.min.css" /> <link rel="stylesheet" href="https://unpkg.com/mdui@1.0.2/dist/css/mdui.min.css" />
@@ -41,7 +41,8 @@
<!-- 代替私人 fixed 并提供更好的兼容性 --> <!-- 代替私人 fixed 并提供更好的兼容性 -->
<script src="https://cdn.jsdelivr.net/gh/wilddeer/stickyfill@2.1.0/dist/stickyfill.min.js"></script> <script src="https://cdn.jsdelivr.net/gh/wilddeer/stickyfill@2.1.0/dist/stickyfill.min.js"></script>
<script src="https://unpkg.com/jquery@3.7.1/dist/jquery.min.js"></script> <script src="https://unpkg.com/jquery@3.7.1/dist/jquery.min.js"></script>
<link rel="icon" href="icon.ico" /> <script src="https://cdn.jsdelivr.net/npm/clipboard@2.0.11/dist/clipboard.min.js"></script>
<link rel="icon" href="res/icon.ico" />
<title>铃之椅</title> <title>铃之椅</title>
</head> </head>
@@ -49,23 +50,48 @@
class="mdui-theme-primary-teal mdui-theme-accent-teal mdui-drawer-body-left mdui-appbar-with-toolbar mdui-theme-layout-auto" class="mdui-theme-primary-teal mdui-theme-accent-teal mdui-drawer-body-left mdui-appbar-with-toolbar mdui-theme-layout-auto"
id="app"> id="app">
<input n-id="textCopier" class="mdui-hidden" /> <input n-id="textCopierBtn" class="mdui-hidden" />
<div id="lingchair-app" style="height: 100%;"> <div id="lingchair-app" style="height: 100%;">
<!-- 侧滑栏 --> <!-- 侧滑栏 -->
<div class="mdui-drawer" id="main-drawer"> <div class="mdui-drawer" id="main-drawer">
<ul class="mdui-list"> <ul class="mdui-list" mdui-collapse="{accordion: true}">
<li class="mdui-list-item mdui-ripple"> <li class="mdui-list-item mdui-ripple">
<div class="mdui-list-item-avatar"> <div class="mdui-list-item-avatar">
<img src="default_head.png" n-id="userHead" onerror="this.src='default_head.png'" /> <img src="default_head.png" n-id="userHead" onerror="this.src='res/default_head.png'" />
</div> </div>
<div class="mdui-list-item-content"><a n-id="helloText">早安</a>, <a n-id="userNick">Unknown</a></div> <div class="mdui-list-item-content"><a n-id="helloText">早安</a>, <a n-id="userNick">Unknown</a></div>
</li> </li>
<li class="mdui-list-item mdui-ripple" onclick="new mdui.Dialog(viewBinding.dialogSettings.get(0)).open()">
<i class="mdui-list-item-icon mdui-icon material-icons">settings</i>
<div class="mdui-list-item-content">设置</div>
</li>
<li class="mdui-list-item mdui-ripple" n-id="drawerSignOut">
<i class="mdui-list-item-icon mdui-icon material-icons">exit_to_app</i>
<div class="mdui-list-item-content">登出</div>
</li>
<div class="mdui-subheader">聊天</div>
<li class="mdui-collapse-item">
<div class="mdui-collapse-item-header mdui-list-item mdui-ripple"><i class="mdui-list-item-icon mdui-icon material-icons">contacts</i>
<div class="mdui-list-item-content">联系人</div><i
class="mdui-collapse-item-arrow mdui-icon material-icons">keyboard_arrow_down</i>
</div>
<div class="mdui-collapse-item-body mdui-list" n-id="contactsList">
</div>
</li>
</ul>
<ul class="mdui-list mdui-hidden">
<li class="mdui-subheader">个人</li> <li class="mdui-subheader">个人</li>
<li class="mdui-list-item mdui-ripple" onclick="new mdui.Dialog(viewBinding.dialogMyProfile.get(0)).open()"> <li class="mdui-list-item mdui-ripple" onclick="new mdui.Dialog(viewBinding.dialogMyProfile.get(0)).open()">
<i class="mdui-list-item-icon mdui-icon material-icons">account_circle</i> <i class="mdui-list-item-icon mdui-icon material-icons">account_circle</i>
<div class="mdui-list-item-content">资料</div> <div class="mdui-list-item-content">资料</div>
</li> </li>
<li class="mdui-list-item mdui-ripple" onclick="new mdui.Dialog(viewBinding.dialogMyProfile.get(0)).open()">
<i class="mdui-list-item-icon mdui-icon material-icons">person_add</i>
<div class="mdui-list-item-content">新的好友</div>
</li>
<li class="mdui-subheader">客户端</li> <li class="mdui-subheader">客户端</li>
<li class="mdui-list-item mdui-ripple"> <li class="mdui-list-item mdui-ripple">
<i class="mdui-list-item-icon mdui-icon material-icons">settings</i> <i class="mdui-list-item-icon mdui-icon material-icons">settings</i>
@@ -75,10 +101,6 @@
<i class="mdui-list-item-icon mdui-icon material-icons">cloud_circle</i> <i class="mdui-list-item-icon mdui-icon material-icons">cloud_circle</i>
<div class="mdui-list-item-content">更换服务器</div> <div class="mdui-list-item-content">更换服务器</div>
</li> </li>
<li class="mdui-list-item mdui-ripple" n-id="drawerSignOut">
<i class="mdui-list-item-icon mdui-icon material-icons">exit_to_app</i>
<div class="mdui-list-item-content">退出登录</div>
</li>
</ul> </ul>
</div> </div>
@@ -91,10 +113,13 @@
</a> </a>
<a class="mdui-typo-title" n-id="appTitle"></a> <a class="mdui-typo-title" n-id="appTitle"></a>
<div class="mdui-toolbar-spacer"></div> <div class="mdui-toolbar-spacer"></div>
<a onclick="ContactsList.reloadList()" n-id="contactsRefresh" class="mdui-btn mdui-btn-icon mdui-ripple"> <a onclick="refreshAll()" n-id="contactsRefresh" class="mdui-btn mdui-btn-icon mdui-ripple">
<i class="mdui-icon material-icons">refresh</i> <i class="mdui-icon material-icons">refresh</i>
</a> </a>
<a class="mdui-btn mdui-btn-icon mdui-ripple" n-id="switchNotifications" mdui-tooltip="{content:'开/关通知'}"> <a onclick="ContactsList.openAddDialog()" n-id="contactsAdd" class="mdui-btn mdui-btn-icon mdui-ripple">
<i class="mdui-icon material-icons">add</i>
</a>
<a class="mdui-btn mdui-btn-icon mdui-ripple" n-id="switchNotifications">
<i class="mdui-icon material-icons" n-id="switchNotificationsIcon">notifications_off</i> <i class="mdui-icon material-icons" n-id="switchNotificationsIcon">notifications_off</i>
</a> </a>
<a mdui-menu="{target: '#appbar-menu'}" class="mdui-btn mdui-btn-icon mdui-ripple"> <a mdui-menu="{target: '#appbar-menu'}" class="mdui-btn mdui-btn-icon mdui-ripple">
@@ -110,38 +135,26 @@
<!-- Tab 栏 --> <!-- Tab 栏 -->
<div class="mdui-tab mdui-accent-theme mdui-theme-color-auto" style="position: fixed; z-index: 114;width: 100%;" <div class="mdui-tab mdui-accent-theme mdui-theme-color-auto" style="position: fixed; z-index: 114;width: 100%;"
mdui-tab> mdui-tab n-id="chatTab">
<!-- 侧滑栏的 z-index 是2000, 在移动端会直接覆盖 --> <!-- 侧滑栏的 z-index 是2000, 在移动端会直接覆盖 -->
<a href="#page-chat-list" n-id="tabChatList" class="mdui-ripple">常用</a>
<a href="#page-contacts" n-id="tabContacts" class="mdui-ripple">通讯录</a>
<a href="#page-chat-seesion" n-id="tabChatSeesion" class="mdui-ripple" style="text-transform: none;"></a> <a href="#page-chat-seesion" n-id="tabChatSeesion" class="mdui-ripple" style="text-transform: none;"></a>
</div> </div>
<div id="page-chat-list" class="mdui-p-a-2 container">
<div class="mdui-valign content" style="margin-top: 40px;">
<span class="mdui-center">欢迎回来! (^▽^。)</span>
</div>
</div>
<div id="page-contacts" class="mdui-p-a-2">
<ul class="mdui-list" style="margin-top: 30px;">
<li class="mdui-subheader">好友</li>
<div n-id="contactsList">
</div>
</ul>
</div>
<!-- 滚动到底部咋这么难写... --> <!-- 滚动到底部咋这么难写... -->
<div id="page-chat-seesion" class="mdui-p-a-2" style="display: flex;flex-direction: column;"> <div style="display: flex;flex-direction: column;">
<!-- 写时间居中写到吐了 这样式表不能要了 --> <!-- 写时间居中写到吐了 这样式表不能要了 -->
<div <div
style="margin-top: 30px;overflow: auto;width: 100%;max-width: 100%;height: 100%;max-height: 100%;min-height: 0;margin-bottom: 40px;flex: 1 1 auto;display: flex;flex-direction: column;"> style="margin-top: 50px;overflow: auto;width: 100%;max-width: 100%;height: 100%;max-height: 100%;min-height: 0;flex: 1 1 auto;display: flex;flex-direction: column;"
n-id="chatPager">
<div class="mdui-center" style="margin: 15px;"><a href="javascript:;" onclick="ChatMsgAdapter.loadMore()" <div class="mdui-center" style="margin: 15px;"><a href="javascript:;" onclick="ChatMsgAdapter.loadMore()"
class="mdui-text-color-theme">点我</a>继续加载前面的聊天记录, 或者<a href="javascript:;" class="mdui-text-color-theme">加载更多</a> | <a href="javascript:;"
onclick="ChatMsgAdapter.scrollToBottom()" class="mdui-text-color-theme">回到底部</a></div> onclick="ChatMsgAdapter.scrollToBottom()" class="mdui-text-color-theme">回到底部</a></div>
<div n-id="pageChatSeesion" style="flex: 1 1 auto;display: flex;flex-direction: column;"></div> <div n-id="pageChatSeesion" style="flex: 1 1 auto;display: flex;flex-direction: column;position: relative;">
</div>
</div> </div>
<!-- 妈的黑化了 私人玩意这么难整 早知道 z-index 弄死它得了 浪费我时间 我就没试过这么离谱的样式表 第三方库真难写CSS 就应该先写后端的 啊啊啊啊啊啊 --> <!-- 妈的黑化了 私人玩意这么难整 早知道 z-index 弄死它得了 浪费我时间 我就没试过这么离谱的样式表 第三方库真难写CSS 就应该先写后端的 啊啊啊啊啊啊 -->
<!-- 不黑化了 因为 stickyfill --> <!-- 不黑化了 因为 stickyfill -->
<div class="mdui-toolbar mdui-theme-color-auto" <div class="mdui-toolbar mdui-theme-color-auto"
style="position: sticky;max-width: 100%;margin-bottom: -30px;bottom: 0;z-index: 101;"> style="position: sticky;max-width: 100%;margin-bottom: -30px;bottom: 0;z-index: 101;" n-id="inputToolbar">
<ul class="mdui-menu" id="msg-input-more"> <ul class="mdui-menu" id="msg-input-more">
<li class="mdui-menu-item"> <li class="mdui-menu-item">
<a class="mdui-ripple">插入图片</a> <a class="mdui-ripple">插入图片</a>
@@ -209,28 +222,20 @@
</div> </div>
</div> </div>
<!-- 资料编辑对话框 --> <!--
<div class="mdui-dialog" n-id="dialogMyProfile"> <div class="mdui-dialog" n-id="dialogMyProfile">
<div class="mdui-dialog-title"> <div class="mdui-dialog-title">
资料 资料
</div> </div>
<div class="mdui-dialog-content" style="margin-left:15px;margin-right:15px;"> <div class="mdui-dialog-content" style="margin-left:15px;margin-right:15px;">
<ul class="mdui-list"> <ul class="mdui-list">
<li class="mdui-list-item mdui-ripple" mdui-dialog-close
onclick="(async () => {viewBinding.dialogEditNickNick.val(await NickCache.getNick(localStorage.userName));new mdui.Dialog(viewBinding.dialogEditNick.get(0)).open()})()">
<i class="mdui-list-item-icon mdui-icon material-icons">edit</i>
<div class="mdui-list-item-content">修改昵称</div>
</li>
<li class="mdui-list-item mdui-ripple" onclick="User.uploadHeadImage()">
<i class="mdui-list-item-icon mdui-icon material-icons">account_circle</i>
<div class="mdui-list-item-content">上传头像</div>
</li>
</ul> </ul>
</div> </div>
<div class="mdui-dialog-actions"> <div class="mdui-dialog-actions">
<button class="mdui-btn mdui-ripple" mdui-dialog-close>关闭</button> <button class="mdui-btn mdui-ripple" mdui-dialog-close>关闭</button>
</div> </div>
</div> </div> -->
<!-- 编辑昵称对话框 --> <!-- 编辑昵称对话框 -->
<div class="mdui-dialog" n-id="dialogEditNick"> <div class="mdui-dialog" n-id="dialogEditNick">
@@ -245,11 +250,73 @@
</div> </div>
<div class="mdui-dialog-actions"> <div class="mdui-dialog-actions">
<button class="mdui-btn mdui-ripple" n-id="dialogEditNickClose" mdui-dialog-close <button class="mdui-btn mdui-ripple" n-id="dialogEditNickClose" mdui-dialog-close
onclick="new mdui.Dialog(viewBinding.dialogMyProfile.get(0)).open()">关闭</button> onclick="new mdui.Dialog(viewBinding.dialogSettings.get(0)).open()">关闭</button>
<button class="mdui-btn mdui-ripple" <button class="mdui-btn mdui-ripple"
onclick="User.setNick(viewBinding.dialogEditNickNick.val(), () => {mdui.snackbar('已保存, 刷新页面生效');viewBinding.dialogEditNickClose.click()})">保存</button> onclick="User.setNick(viewBinding.dialogEditNickNick.val(), () => {mdui.snackbar('已保存, 刷新页面生效');viewBinding.dialogEditNickClose.click()})">保存</button>
</div> </div>
</div> </div>
<!-- 懂得都懂 -->
<div class="mdui-dialog" n-id="dialogNewFriendRequest">
<div class="mdui-dialog-title">
新的好友请求
</div>
<div class="mdui-dialog-content" style="margin-left:15px;margin-right:15px;">
<div class="mdui-textfield">
<label class="mdui-textfield-label">昵称</label>
<input n-id="" class="mdui-textfield-input" maxlength="30" type="text" />
</div>
</div>
<div class="mdui-dialog-actions">
<button class="mdui-btn mdui-ripple" mdui-dialog-close>关闭</button>
</div>
</div>
<!-- 懂得都懂 -->
<div class="mdui-dialog" n-id="dialogNewContact">
<div class="mdui-dialog-title">
添加好友/群
</div>
<div class="mdui-dialog-content" style="margin-left:15px;margin-right:15px;">
<div class="mdui-textfield">
<label class="mdui-textfield-label">好友/群的ID (不是名称)</label>
<input n-id="dialogNewContactID" class="mdui-textfield-input" maxlength="30" type="text" />
</div>
<select class="mdui-select" mdui-select="{position: 'top'}" n-id="dialogNewContactType">
<option value="single">好友</option>
<option value="group">群聊</option>
</select>
</div>
<div class="mdui-dialog-actions">
<button class="mdui-btn mdui-ripple" mdui-dialog-close
onclick="ContactsList.add(viewBinding.dialogNewContactID.val(), viewBinding.dialogNewContactType.val())">确认并关闭</button>
<button class="mdui-btn mdui-ripple" mdui-dialog-close>取消</button>
</div>
</div>
<!-- 懂得都懂 -->
<div class="mdui-dialog" n-id="dialogSettings">
<div class="mdui-dialog-title">
设置
</div>
<div class="mdui-dialog-content">
<ul class="mdui-list">
<div class="mdui-subheader">我的资料</div>
<li class="mdui-list-item mdui-ripple" mdui-dialog-close
onclick="(async () => {viewBinding.dialogEditNickNick.val(await NickCache.getNick(localStorage.userName));new mdui.Dialog(viewBinding.dialogEditNick.get(0)).open()})()">
<i class="mdui-list-item-icon mdui-icon material-icons">edit</i>
<div class="mdui-list-item-content">修改昵称</div>
</li>
<li class="mdui-list-item mdui-ripple" onclick="User.uploadHeadImage()">
<i class="mdui-list-item-icon mdui-icon material-icons">account_circle</i>
<div class="mdui-list-item-content">上传头像</div>
</li>
<div class="mdui-subheader">客户端</div>
</ul>
</div>
<div class="mdui-dialog-actions">
<button class="mdui-btn mdui-ripple" mdui-dialog-close>关闭</button>
</div>
</div> </div>
<div class="mdui-hidden"> <div class="mdui-hidden">

1272
build_cache/index.js Normal file

File diff suppressed because one or more lines are too long

View File

@@ -12,9 +12,6 @@ body {
.mdui-dialog { .mdui-dialog {
border-radius: 23px; border-radius: 23px;
} }
.mdui-snackbar {
border-radius: 10px;
}
.mdui-menu { .mdui-menu {
border-radius: 10px; border-radius: 10px;
} }
@@ -34,6 +31,14 @@ body {
height: 40px; height: 40px;
border-radius: 40px; border-radius: 40px;
} }
.mdui-select-open {
border-radius: 10px;
}
@media not screen and (min-width: 768px) {
.mdui-snackbar {
border-radius: 10px;
}
}
/* 配色方案 */ /* 配色方案 */

View File

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

BIN
client_src.zip Normal file

Binary file not shown.

View File

@@ -0,0 +1,85 @@
/*
* 铃之椅 - 把选择权还给用户, 让聊天权掌握在用户手中
* Copyright 2024 满月叶
* GitHub: https://github.com/MoonLeeeaf/LingChair-Web-Client
* 本项目使用 Apache 2.0 协议开源
*
* Copyright 2024 MoonLeeeaf
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.chat-message-right {
display: flex;
justify-content: flex-end;
align-items: flex-start;
margin: 13px;
}
.chat-message-left {
display: flex;
justify-content: flex-start;
align-items: flex-start;
margin: 13px;
}
.message-content {
margin-top: 13px;
margin-bottom: 7px;
margin-left: 5px;
margin-right: 5px;
max-width: 100%;
white-space: normal;
word-break: break-all;
font-size: medium;
/* 使用了 CardView 就不需要边框了 */
/* border: 1.3px solid; */
padding: 15px;
border-radius: 15px;
/* 添加圆角样式 */
/* 设置外边距为 7px */
}
.message-content-with-nickname-right {
display: flex;
align-items: center;
margin: 7px;
flex-direction: column;
/* 垂直排列元素 */
align-items: flex-end;
/* 左对齐元素 */
}
.message-content-with-nickname-left {
display: flex;
align-items: center;
margin: 7px;
flex-direction: column;
/* 垂直排列元素 */
align-items: flex-start;
/* 左对齐元素 */
}
.chat-message-left .message-content-with-nickname-left .nickname,
.chat-message-right .message-content-with-nickname-right .nickname {
margin-right: 5px;
font-size: medium;
margin-top: 3px;
}
.chat-message-left > .avatar,
.chat-message-right > .avatar {
width: 45px;
height: 45px;
border-radius: 50%;
}

54
client_src/index.css Normal file
View File

@@ -0,0 +1,54 @@
/*
* 铃之椅 - 把选择权还给用户, 让聊天权掌握在用户手中
* Copyright 2024 满月叶
* GitHub: https://github.com/MoonLeeeaf/LingChair-Web-Client
* 本项目使用 Apache 2.0 协议开源
*
* Copyright 2024 MoonLeeeaf
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
html, body {
max-height: 100%;
margin: 0;
padding: 0;
/* overflow: hidden; */
/*font: initial;*/
}
body {
margin: 0;
padding: 0;
}
.container {
display: flex;
flex-direction: column;
overflow: auto;
}
.content {
flex: 1;
}
.menu-on-message {
margin-top: 60px;
z-index: 100;
}
[n-id=pageChatSeesion]::after {
content: "";
position: sticky;
bottom: 0;
display: block;
height: var(--pseudo-height); /* 设置伪元素的高度 */
z-index: -1; /* 防止遮挡实际内容 */
}

339
client_src/index.html Normal file
View File

@@ -0,0 +1,339 @@
<!doctype html>
<html lang="zh-cmn-Hans">
<!--
* 铃之椅 - 把选择权还给用户, 让聊天权掌握在用户手中
* Copyright 2024 满月叶
* GitHub: https://github.com/MoonLeeeaf/LingChair-Web-Client
* 本项目使用 Apache 2.0 协议开源
*
* Copyright 2024 MoonLeeeaf
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
-->
<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" />
<meta name="force-rendering" content="webkit" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<!-- 给老旧的设备提供支持 支持不了, 照样没法运行 不 试一下红米2可不可以-->
<script src='https://polyfill.io/v3/polyfill.min.js?features=default%2Cdom4%2Ces2015%2Ces2016%2Ces2017%2Ces2018%2Ces2019%2Ces2020%2Ces2021%2Ces2022%2Ces5%2Ces6%2Ces7'></script>
<!-- <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> -->
<!-- Styles -->
<link rel="stylesheet" href="https://unpkg.com/mdui@1.0.2/dist/css/mdui.min.css" />
<link rel="stylesheet" href="index.css" />
<link rel="stylesheet" href="chat-message.css" />
<link rel="stylesheet" href="mdui-prettier.css" />
<!-- 代替私人 fixed 并提供更好的兼容性 -->
<script src="https://cdn.jsdelivr.net/gh/wilddeer/stickyfill@2.1.0/dist/stickyfill.min.js"></script>
<script src="https://unpkg.com/jquery@3.7.1/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/clipboard@2.0.11/dist/clipboard.min.js"></script>
<link rel="icon" href="res/icon.ico" />
<title>铃之椅</title>
</head>
<body
class="mdui-theme-primary-teal mdui-theme-accent-teal mdui-drawer-body-left mdui-appbar-with-toolbar mdui-theme-layout-auto"
id="app">
<input n-id="textCopierBtn" class="mdui-hidden" />
<div id="lingchair-app" style="height: 100%;">
<!-- 侧滑栏 -->
<div class="mdui-drawer" id="main-drawer">
<ul class="mdui-list" mdui-collapse="{accordion: true}">
<li class="mdui-list-item mdui-ripple">
<div class="mdui-list-item-avatar">
<img src="default_head.png" n-id="userHead" onerror="this.src='res/default_head.png'" />
</div>
<div class="mdui-list-item-content"><a n-id="helloText">早安</a>, <a n-id="userNick">Unknown</a></div>
</li>
<li class="mdui-list-item mdui-ripple" onclick="new mdui.Dialog(viewBinding.dialogSettings.get(0)).open()">
<i class="mdui-list-item-icon mdui-icon material-icons">settings</i>
<div class="mdui-list-item-content">设置</div>
</li>
<li class="mdui-list-item mdui-ripple" n-id="drawerSignOut">
<i class="mdui-list-item-icon mdui-icon material-icons">exit_to_app</i>
<div class="mdui-list-item-content">登出</div>
</li>
<div class="mdui-subheader">聊天</div>
<li class="mdui-collapse-item">
<div class="mdui-collapse-item-header mdui-list-item mdui-ripple"><i class="mdui-list-item-icon mdui-icon material-icons">contacts</i>
<div class="mdui-list-item-content">联系人</div><i
class="mdui-collapse-item-arrow mdui-icon material-icons">keyboard_arrow_down</i>
</div>
<div class="mdui-collapse-item-body mdui-list" n-id="contactsList">
</div>
</li>
</ul>
<ul class="mdui-list mdui-hidden">
<li class="mdui-subheader">个人</li>
<li class="mdui-list-item mdui-ripple" onclick="new mdui.Dialog(viewBinding.dialogMyProfile.get(0)).open()">
<i class="mdui-list-item-icon mdui-icon material-icons">account_circle</i>
<div class="mdui-list-item-content">资料</div>
</li>
<li class="mdui-list-item mdui-ripple" onclick="new mdui.Dialog(viewBinding.dialogMyProfile.get(0)).open()">
<i class="mdui-list-item-icon mdui-icon material-icons">person_add</i>
<div class="mdui-list-item-content">新的好友</div>
</li>
<li class="mdui-subheader">客户端</li>
<li class="mdui-list-item mdui-ripple">
<i class="mdui-list-item-icon mdui-icon material-icons">settings</i>
<div class="mdui-list-item-content">设置</div>
</li>
<li class="mdui-list-item mdui-ripple" n-id="drawerChangeServer">
<i class="mdui-list-item-icon mdui-icon material-icons">cloud_circle</i>
<div class="mdui-list-item-content">更换服务器</div>
</li>
</ul>
</div>
<!-- 应用栏 -->
<header class="mdui-appbar mdui-appbar-fixed">
<!-- Toolbar -->
<div class="mdui-toolbar mdui-color-theme">
<a mdui-drawer="{target: '#main-drawer'}" class="mdui-btn mdui-btn-icon mdui-ripple">
<i class="mdui-icon material-icons">menu</i>
</a>
<a class="mdui-typo-title" n-id="appTitle"></a>
<div class="mdui-toolbar-spacer"></div>
<a onclick="refreshAll()" n-id="contactsRefresh" class="mdui-btn mdui-btn-icon mdui-ripple">
<i class="mdui-icon material-icons">refresh</i>
</a>
<a onclick="ContactsList.openAddDialog()" n-id="contactsAdd" class="mdui-btn mdui-btn-icon mdui-ripple">
<i class="mdui-icon material-icons">add</i>
</a>
<a class="mdui-btn mdui-btn-icon mdui-ripple" n-id="switchNotifications">
<i class="mdui-icon material-icons" n-id="switchNotificationsIcon">notifications_off</i>
</a>
<a mdui-menu="{target: '#appbar-menu'}" class="mdui-btn mdui-btn-icon mdui-ripple">
<i class="mdui-icon material-icons">more_vert</i>
</a>
<ul class="mdui-menu" id="appbar-menu">
<li class="mdui-menu-item">
<a class="mdui-ripple" n-id="menuAbout">关于</a>
</li>
</ul>
</div>
</header>
<!-- Tab 栏 -->
<div class="mdui-tab mdui-accent-theme mdui-theme-color-auto" style="position: fixed; z-index: 114;width: 100%;"
mdui-tab n-id="chatTab">
<!-- 侧滑栏的 z-index 是2000, 在移动端会直接覆盖 -->
<a href="#page-chat-seesion" n-id="tabChatSeesion" class="mdui-ripple" style="text-transform: none;"></a>
</div>
<!-- 滚动到底部咋这么难写... -->
<div style="display: flex;flex-direction: column;">
<!-- 写时间居中写到吐了 这样式表不能要了 -->
<div
style="margin-top: 50px;overflow: auto;width: 100%;max-width: 100%;height: 100%;max-height: 100%;min-height: 0;flex: 1 1 auto;display: flex;flex-direction: column;"
n-id="chatPager">
<div class="mdui-center" style="margin: 15px;"><a href="javascript:;" onclick="ChatMsgAdapter.loadMore()"
class="mdui-text-color-theme">加载更多</a> | <a href="javascript:;"
onclick="ChatMsgAdapter.scrollToBottom()" class="mdui-text-color-theme">回到底部</a></div>
<div n-id="pageChatSeesion" style="flex: 1 1 auto;display: flex;flex-direction: column;position: relative;">
</div>
</div>
<!-- 妈的黑化了 私人玩意这么难整 早知道 z-index 弄死它得了 浪费我时间 我就没试过这么离谱的样式表 第三方库真难写CSS 就应该先写后端的 啊啊啊啊啊啊 -->
<!-- 不黑化了 因为 stickyfill -->
<div class="mdui-toolbar mdui-theme-color-auto"
style="position: sticky;max-width: 100%;margin-bottom: -30px;bottom: 0;z-index: 101;" n-id="inputToolbar">
<ul class="mdui-menu" id="msg-input-more">
<li class="mdui-menu-item">
<a class="mdui-ripple">插入图片</a>
</li>
</ul>
<div class="mdui-textfield" style="width: 100%;max-width: 100%;">
<textarea class="mdui-textfield-input" type="text" placeholder="(。・ω・。)" n-id="inputMsg"></textarea>
</div>
<div class="mdui-toolbar-spacer"></div>
<a n-id="sendMsg" class="mdui-btn mdui-btn-icon mdui-ripple">
<i class="mdui-icon material-icons">send</i>
</a>
<a mdui-menu="{target: '#msg-input-more', position: 'top'}" class="mdui-btn mdui-btn-icon mdui-ripple">
<i class="mdui-icon material-icons">more_vert</i>
</a>
</div>
</div>
<!-- 登录对话框 -->
<div class="mdui-dialog" n-id="dialogSignIn">
<div class="mdui-dialog-title">
登录到 铃之椅
</div>
<div class="mdui-dialog-content" style="margin-left:15px;margin-right:15px;">
<div class="mdui-textfield" n-id="dialogSignInServerLabel">
<i class="mdui-icon material-icons">cloud_circle</i>
<label class="mdui-textfield-label">服务器地址</label>
<input n-id="dialogSignInServer" class="mdui-textfield-input" type="text" placeholder="留空以使用网页所在地址"
n-input-ls="server" n-id="dialogSignServer" onblur="setUpClient(viewBinding.dialogSignServer.val())" />
</div>
<div class="mdui-textfield">
<i class="mdui-icon material-icons">account_circle</i>
<label class="mdui-textfield-label">账号</label>
<input n-id="dialogSignInName" class="mdui-textfield-input" maxlength="25" type="text"
n-input-ls="userName" />
</div>
<div class="mdui-textfield">
<i class="mdui-icon material-icons">lock</i>
<label class="mdui-textfield-label">密码</label>
<input n-id="dialogSignInPasswd" class="mdui-textfield-input" maxlength="30" type="password" />
</div>
<span>注:使用非已知的服务提供商提供的服务器时, 请注意个人信息保护哦 o(。・ω・。)o</span>
</div>
<div class="mdui-dialog-actions">
<button class="mdui-btn mdui-ripple"
onclick="User.signUp(viewBinding.dialogSignInName.val(), viewBinding.dialogSignInPasswd.val(), () => mdui.snackbar('注册成功, 请直接点击登录即可~'))">注册</button>
<button class="mdui-btn mdui-ripple" n-id="dialogSignInEnter"
onclick="User.signInWithDialog(viewBinding.dialogSignInName.val(), viewBinding.dialogSignInPasswd.val())">登录</button>
</div>
</div>
</div>
<!-- 资料卡对话框 -->
<div class="mdui-dialog" n-id="dialogProfile">
<div class="mdui-dialog-content" style="margin-left: 15px; margin-right: 15px; height: 101px;">
<div style="display: flex;justify-content: flex-start">
<img style="width: 60px; height: 60px;" class="mdui-img-circle" n-id="dialogProfileHead">
<div n-id="dialogProfileNick" style="font-size: 22px;align-self: center;margin-left: 20px;"
class="mdui-text-color-white"></div>
</div>
<div style="margin-left: 80px;"></div>
</div>
<div class="mdui-dialog-actions">
<button class="mdui-btn mdui-ripple" mdui-dialog-close>关闭</button>
</div>
</div>
<!--
<div class="mdui-dialog" n-id="dialogMyProfile">
<div class="mdui-dialog-title">
资料
</div>
<div class="mdui-dialog-content" style="margin-left:15px;margin-right:15px;">
<ul class="mdui-list">
</ul>
</div>
<div class="mdui-dialog-actions">
<button class="mdui-btn mdui-ripple" mdui-dialog-close>关闭</button>
</div>
</div> -->
<!-- 编辑昵称对话框 -->
<div class="mdui-dialog" n-id="dialogEditNick">
<div class="mdui-dialog-title">
修改昵称
</div>
<div class="mdui-dialog-content" style="margin-left:15px;margin-right:15px;">
<div class="mdui-textfield">
<label class="mdui-textfield-label">昵称</label>
<input n-id="dialogEditNickNick" class="mdui-textfield-input" maxlength="30" type="text" />
</div>
</div>
<div class="mdui-dialog-actions">
<button class="mdui-btn mdui-ripple" n-id="dialogEditNickClose" mdui-dialog-close
onclick="new mdui.Dialog(viewBinding.dialogSettings.get(0)).open()">关闭</button>
<button class="mdui-btn mdui-ripple"
onclick="User.setNick(viewBinding.dialogEditNickNick.val(), () => {mdui.snackbar('已保存, 刷新页面生效');viewBinding.dialogEditNickClose.click()})">保存</button>
</div>
</div>
<!-- 懂得都懂 -->
<div class="mdui-dialog" n-id="dialogNewFriendRequest">
<div class="mdui-dialog-title">
新的好友请求
</div>
<div class="mdui-dialog-content" style="margin-left:15px;margin-right:15px;">
<div class="mdui-textfield">
<label class="mdui-textfield-label">昵称</label>
<input n-id="" class="mdui-textfield-input" maxlength="30" type="text" />
</div>
</div>
<div class="mdui-dialog-actions">
<button class="mdui-btn mdui-ripple" mdui-dialog-close>关闭</button>
</div>
</div>
<!-- 懂得都懂 -->
<div class="mdui-dialog" n-id="dialogNewContact">
<div class="mdui-dialog-title">
添加好友/群
</div>
<div class="mdui-dialog-content" style="margin-left:15px;margin-right:15px;">
<div class="mdui-textfield">
<label class="mdui-textfield-label">好友/群的ID (不是名称)</label>
<input n-id="dialogNewContactID" class="mdui-textfield-input" maxlength="30" type="text" />
</div>
<select class="mdui-select" mdui-select="{position: 'top'}" n-id="dialogNewContactType">
<option value="single">好友</option>
<option value="group">群聊</option>
</select>
</div>
<div class="mdui-dialog-actions">
<button class="mdui-btn mdui-ripple" mdui-dialog-close
onclick="ContactsList.add(viewBinding.dialogNewContactID.val(), viewBinding.dialogNewContactType.val())">确认并关闭</button>
<button class="mdui-btn mdui-ripple" mdui-dialog-close>取消</button>
</div>
</div>
<!-- 懂得都懂 -->
<div class="mdui-dialog" n-id="dialogSettings">
<div class="mdui-dialog-title">
设置
</div>
<div class="mdui-dialog-content">
<ul class="mdui-list">
<div class="mdui-subheader">我的资料</div>
<li class="mdui-list-item mdui-ripple" mdui-dialog-close
onclick="(async () => {viewBinding.dialogEditNickNick.val(await NickCache.getNick(localStorage.userName));new mdui.Dialog(viewBinding.dialogEditNick.get(0)).open()})()">
<i class="mdui-list-item-icon mdui-icon material-icons">edit</i>
<div class="mdui-list-item-content">修改昵称</div>
</li>
<li class="mdui-list-item mdui-ripple" onclick="User.uploadHeadImage()">
<i class="mdui-list-item-icon mdui-icon material-icons">account_circle</i>
<div class="mdui-list-item-content">上传头像</div>
</li>
<div class="mdui-subheader">客户端</div>
</ul>
</div>
<div class="mdui-dialog-actions">
<button class="mdui-btn mdui-ripple" mdui-dialog-close>关闭</button>
</div>
</div>
<div class="mdui-hidden">
<input type="file" n-id="uploadHeadImage" name="选择头像" onchange="User.uploadHeadImageCallback(this)"
accept="image/png, image/jpeg" />
</div>
<!-- Scripts -->
<script src="https://unpkg.com/crypto-js@4.2.0/crypto-js.js"></script>
<script src="https://unpkg.com/socket.io-client@4.7.4/dist/socket.io.min.js"></script>
<script src="https://unpkg.com/mdui@1.0.2/dist/js/mdui.min.js"></script>
<!-- 加了babel也许能解决下浏览器兼容问题 -->
<!-- <script type="module" src="index.js"></script>
<script nomodule type="text/babel" src="index.js"></script> -->
<!-- 就算弄语法兼容其他老旧设备照样用不了, 比如我的 iPad4 -->
<script src="index.js"></script>
</body>
</html>

View File

@@ -21,6 +21,11 @@
const UrlArgs = new URL(location.href).searchParams const UrlArgs = new URL(location.href).searchParams
// https://www.ruanyifeng.com/blog/2021/09/detecting-mobile-browser.html
function isMobile() {
return ('ontouchstart' in document.documentElement);
}
function setOnRightClick(e, cb) { function setOnRightClick(e, cb) {
if (!(e instanceof jQuery)) if (!(e instanceof jQuery))
e = $(e) e = $(e)
@@ -112,7 +117,7 @@ class NData {
let viewBinding = NData.mount($("#app").get(0)) let viewBinding = NData.mount($("#app").get(0))
$.ajax({ $.ajax({
url: "config.json", url: "res/config.json",
dataType: "json", dataType: "json",
success: (c) => { success: (c) => {
viewBinding.appTitle.text(c.appTitle) viewBinding.appTitle.text(c.appTitle)
@@ -123,22 +128,26 @@ $.ajax({
}, },
}) })
// Toolbar 快捷按钮绑定 /* // Toolbar 快捷按钮绑定
viewBinding.contactsRefresh.hide() viewBinding.contactsRefresh.hide()
viewBinding.contactsAdd.hide()
viewBinding.tabChatList.on("show.mdui.tab", () => { viewBinding.tabChatList.on("show.mdui.tab", () => {
viewBinding.contactsRefresh.hide() viewBinding.contactsRefresh.hide()
viewBinding.contactsAdd.hide()
}) })
viewBinding.tabContacts.on("show.mdui.tab", () => { viewBinding.tabContacts.on("show.mdui.tab", () => {
viewBinding.contactsRefresh.show() viewBinding.contactsRefresh.show()
viewBinding.contactsAdd.show()
}) })
viewBinding.tabChatSeesion.on("show.mdui.tab", () => { viewBinding.tabChatSeesion.on("show.mdui.tab", () => {
viewBinding.contactsRefresh.hide() viewBinding.contactsRefresh.hide()
}) viewBinding.contactsAdd.hide()
}) */
viewBinding.tabChatSeesion.hide() /* viewBinding.tabChatSeesion.hide() */
// 关于页面 // 关于页面
viewBinding.menuAbout.click(() => mdui.alert('GitHub: MoonLeeeaf<br/><br/>欢迎各位大佬访问我们的<a class="mdui-text-color-theme-accent" href="https://github.com/LingChair/LingChair">项目主页</a>', '关于 铃之椅', () => { }, { confirmText: "关闭" })) viewBinding.menuAbout.click(() => mdui.alert('这是一个开源项目<br/>作者: MoonLeeeaf<br/>欢迎访问我们的<a class="mdui-text-color-theme-accent" href="https://github.com/LingChair/LingChair">项目主页</a>', '关于 铃之椅', () => { }, { confirmText: "关闭" }))
viewBinding.drawerChangeServer.click(() => { viewBinding.drawerChangeServer.click(() => {
mdui.prompt('输入服务器地址...(为空则使用当前页面地址)', (value) => { mdui.prompt('输入服务器地址...(为空则使用当前页面地址)', (value) => {
@@ -189,11 +198,12 @@ if (localStorage.useNotifications == "true")
// https://www.runoob.com/w3cnote/javascript-copy-clipboard.html // https://www.runoob.com/w3cnote/javascript-copy-clipboard.html
function copyText(t) { function copyText(t) {
let cp = viewBinding.textCopier.get(0) let btn = viewBinding.textCopierBtn
cp.value = t btn.attr("data-clipboard-text", t)
cp.select() new ClipboardJS(btn.get(0)).on('success', (e) => {
cp.setSelectionRange(0, 99999) e.clearSelection()
navigator.clipboard.writeText(cp.value) })
btn.click()
} }
// https://zhuanlan.zhihu.com/p/162910462 // https://zhuanlan.zhihu.com/p/162910462
@@ -310,7 +320,7 @@ class ContactsList {
/*client.emit("user.getNick", { name: localStorage.userName }, (re) => { /*client.emit("user.getNick", { name: localStorage.userName }, (re) => {
let nick = re.data == null ? re.data.nick : null let nick = re.data == null ? re.data.nick : null
let name = ls[index]*/ let name = ls[index]*/
$($.parseHTML(`<li class="mdui-list-item mdui-ripple"><div class="mdui-list-item-avatar"><img src="` + User.getUserHeadUrl(name) + `" onerror="this.src='default_head.png'" /></div><div class="mdui-list-item-content">` + dick + `</div></li>`)).appendTo(viewBinding.contactsList).click(() => { $($.parseHTML(`<li class="mdui-list-item mdui-ripple"><div class="mdui-list-item-avatar"><img src="` + User.getUserHeadUrl(name) + `" onerror="this.src='res/default_head.png'" /></div><div class="mdui-list-item-content">` + dick + `</div></li>`)).appendTo(viewBinding.contactsList).click(() => {
ChatMsgAdapter.switchTo(name, "single") ChatMsgAdapter.switchTo(name, "single")
}) })
//}) //})
@@ -318,10 +328,32 @@ class ContactsList {
}) })
} }
// 添加联系人,好友或者群聊
static add(name, type) {
if (type == "single") {
}
}
static openAddDialog() {
new mdui.Dialog(viewBinding.dialogNewContact.get(0)).open()
}
} }
// 第一次写前端的消息加载, 代码很乱, 还请原谅~ // 第一次写前端的消息加载, 代码很乱, 还请原谅~
// v0.7.0 大改UI 畏惧了 太庞大了
class ChatPage {
static cached = {}
constructor(name, type) {
}
static switchTo(name, type) {
if (!this.cached[name])
this.cached[name] = new ChatPage(name, type)
}
}
class ChatMsgAdapter { class ChatMsgAdapter {
static type static type
static target static target
@@ -329,6 +361,7 @@ class ChatMsgAdapter {
static minMsgId static minMsgId
static time static time
static bbn static bbn
static resizeDick
// 切换聊天对象 // 切换聊天对象
static async switchTo(name, type) { static async switchTo(name, type) {
viewBinding.tabChatSeesion.show() viewBinding.tabChatSeesion.show()
@@ -407,8 +440,10 @@ class ChatMsgAdapter {
static addSystemMsg(m, re) { static addSystemMsg(m, re) {
let e let e
if (re) if (re)
// 加到头部
e = $($.parseHTML(m)).prependTo(viewBinding.pageChatSeesion) e = $($.parseHTML(m)).prependTo(viewBinding.pageChatSeesion)
else else
// 加到尾部
e = $($.parseHTML(m)).appendTo(viewBinding.pageChatSeesion) e = $($.parseHTML(m)).appendTo(viewBinding.pageChatSeesion)
return e return e
} }
@@ -416,7 +451,8 @@ class ChatMsgAdapter {
let elementRect = viewBinding.pageChatSeesion.get(0).getBoundingClientRect() let elementRect = viewBinding.pageChatSeesion.get(0).getBoundingClientRect()
return (elementRect.bottom <= window.innerHeight) return (elementRect.bottom <= window.innerHeight)
} }
// 不会压栈 只添加消息 返回消息的JQ对象 // 添加消息 返回消息的JQ对象
// name: 用户id m: 消息 t: 时间戳 re: 默认加到尾部 msgid: 消息id
static async addMsg(name, m, t, re, msgid) { static async addMsg(name, m, t, re, msgid) {
let nick = await NickCache.getNick(name) // re.data == null ? name : re.data.nick let nick = await NickCache.getNick(name) // re.data == null ? name : re.data.nick
@@ -432,11 +468,11 @@ class ChatMsgAdapter {
<span id="msg-content">` + msg + `</span> <span id="msg-content">` + msg + `</span>
</div> </div>
</div> </div>
<img class="avatar" src="` + User.getUserHeadUrl(name) + `" onerror="this.src='default_head.png'" /> <img class="avatar" src="` + User.getUserHeadUrl(name) + `" onerror="this.src='res/default_head.png'" />
</div>` </div>`
else else
temp = `<div class="chat-message-left"> temp = `<div class="chat-message-left">
<img class="avatar" src="` + User.getUserHeadUrl(name) + `" onerror="this.src='default_head.png'" /> <img class="avatar" src="` + User.getUserHeadUrl(name) + `" onerror="this.src='res/default_head.png'" />
<div class="message-content-with-nickname-left"> <div class="message-content-with-nickname-left">
<span class="nickname">` + nick + `</span> <span class="nickname">` + nick + `</span>
<div class="message-content mdui-card" id="msgid_` + msgid + `"> <div class="message-content mdui-card" id="msgid_` + msgid + `">
@@ -488,10 +524,10 @@ class ChatMsgAdapter {
} */ } */
static scrollToBottom() { static scrollToBottom() {
// 吐了啊 原来这样就行了 我何必在子element去整啊 // 吐了啊 原来这样就行了 我何必在子element去整啊
window.scrollBy({ viewBinding.chatPager.get(0).scrollBy({
top: 1145141919810, top: 1145141919810,
behavior: 'smooth' behavior: 'smooth'
}); })
} }
// 从本地加载 // 从本地加载
/*static loadMsgsFromLocal(target) { /*static loadMsgsFromLocal(target) {
@@ -505,6 +541,26 @@ class ChatMsgAdapter {
static saveToLocal() { static saveToLocal() {
localStorage["chat_msg_" + this.target] = JSON.stringify(this.msgList) localStorage["chat_msg_" + this.target] = JSON.stringify(this.msgList)
}*/ }*/
// 自动调整使输入框置底 CSS真tm靠不住啊
static initInputResizer() {
// 实验表面移动端切出输入法时会触发1-2次resize事件
// 可以利用这个特性来实现自动滚动文本
let resize = () => {
viewBinding.pageChatSeesion.height(window.innerHeight - viewBinding.inputToolbar.height() - $("header.mdui-appbar").height() - viewBinding.chatTab.height() - 50)
let ledi = this.resizeDick - window.innerHeight
if (isMobile()) viewBinding.chatPager.get(0).scrollBy({
// 5.19晚1056分调配出来的秘方
// < 0 为窗口变大
// cnm的调试十万次就你tm检测不到底是吧就你语法天天错误是吧
// 欺负我现在用不了电脑
top: -(ledi) * ( (ledi < 0 && this.isAtBottom()) ? 6 : -1 ), // (ledi < 0 ? 6 : 6),
behavior: 'smooth'
})
this.resizeDick = window.innerHeight
}
window.addEventListener("resize", resize)
resize()
}
// 为消息设置长按/右键事件 // 为消息设置长按/右键事件
static initMsgElementEvents() { static initMsgElementEvents() {
let listeners = {} let listeners = {}
@@ -716,7 +772,7 @@ else
// 登录到账号 // 登录到账号
let dialogSignIn let dialogSignIn
// 谨防 localStorage 字符串数据大坑 // 谨防 localStorage 字符串数据大坑
if (localStorage.isSignIn === "false") if (localStorage.isSignIn == "false")
dialogSignIn = new mdui.Dialog(viewBinding.dialogSignIn.get(0), { dialogSignIn = new mdui.Dialog(viewBinding.dialogSignIn.get(0), {
modal: true, modal: true,
closeOnEsc: false, closeOnEsc: false,
@@ -744,3 +800,11 @@ else {
Stickyfill.add($("*").filter((a, b) => $(b).css('position') === 'sticky')) Stickyfill.add($("*").filter((a, b) => $(b).css('position') === 'sticky'))
ChatMsgAdapter.initMsgElementEvents() ChatMsgAdapter.initMsgElementEvents()
ChatMsgAdapter.initInputResizer()
function refreshAll() {
ContactsList.reloadList()
delete NickCache.data
NickCache.data = {}
}

View File

@@ -0,0 +1,53 @@
/*
* ©2024 满月叶
* GitHub: MoonLeeeaf
* 是 UI 美化,好耶!
*/
/* 美化UI */
body {
font-family: -apple-system, system-ui, -webkit-system-font;
}
.mdui-dialog {
border-radius: 23px;
}
.mdui-menu {
border-radius: 10px;
}
.mdui-menu-item > a {
padding-right: 3px;
}
.mdui-btn:not(.mdui-btn-icon, .mdui-dialog-actions button, .mdui-dialog-actions a) {
padding-left: 20px;
padding-right: 20px;
height: 40px;
border-radius: 10px;
}
.mdui-dialog-actions a,
.mdui-dialog-actions button {
padding-left: 20px;
padding-right: 20px;
height: 40px;
border-radius: 40px;
}
.mdui-select-open {
border-radius: 10px;
}
@media not screen and (min-width: 768px) {
.mdui-snackbar {
border-radius: 10px;
}
}
/* 配色方案 */
.mdui-theme-color-auto {
background-color: #fff;
}
@media (prefers-color-scheme: dark) {
.mdui-theme-color-auto {
background-color: #303030;
}
}

View File

@@ -0,0 +1,4 @@
{
"appTitle": "",
"canChangeServer": true
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

BIN
client_src/res/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@@ -0,0 +1,13 @@
Copyright 2024 MoonLeeeaf
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -3,19 +3,21 @@
欢迎来到铃之椅! 这是一个即时通讯项目, 为通讯提供更多的选择, 为人民服务 欢迎来到铃之椅! 这是一个即时通讯项目, 为通讯提供更多的选择, 为人民服务
> [!NOTE] > [!NOTE]
> 本项目仍在实验阶段, 为防止源代码丢失, 先留存代码... > 本项目仍在实验阶段, [点我](final.md)可查看进展
> >
> 如果发现有任何疑问, 欢迎提问 > 如果有任何问题,欢迎你提出来,我会不定时查看
> >
> 另外 Android 客户端也在开发, 但因为从头开始写且生地中考临近, 因此暂时搁置 > 另外 Android 客户端也在开发, 但进展缓慢
### 使用 ### 使用
服务端: 服务端:
1. 克隆本仓库源代码到本地 0. 确保安装了 Node.js
2. 运行 run.sh 或 run.bat 1. 克隆本仓库源代码到本地,并运行 run_build.sh 构建网页
2. 运行 run.sh
网页端: 网页端:
@@ -23,7 +25,9 @@
* 克隆本仓库到本地并运行本地 HTTP 服务端 * 克隆本仓库到本地并运行本地 HTTP 服务端
* 使用本仓库提供的网页 (不推荐, 有跨域问题) * 静态网页 (不推荐)
### [Q&A](.github/QA.md)
### 鸣谢 ### 鸣谢

11
run_build.sh Normal file
View File

@@ -0,0 +1,11 @@
# 复制资源文件,这些文件不需要编译
mkdir -p ling_chair_http
cp -r client_src/* build_cache/
# 向前兼容脚本
cd babel_lib
node node_modules/@babel/cli/bin/babel ../client_src --out-dir ../build_cache --presets=@babel/env
# 打包以减小负载
cd ../webpack_lib
node node_modules/webpack-cli/bin/cli.js --config webpack_config.js

View File

@@ -148,6 +148,21 @@ let api = {
cb({ msg: msg, code: 0, data: { friends: friends } }) cb({ msg: msg, code: 0, data: { friends: friends } })
}, },
// 添加好友
// {name: 账号, accessToken: 访问令牌} 返回 {friends: []}
// WIP
"user.addFriend": (a, cb) => {
if (checkEmpty([a.name, a.accessToken]))
return cb({ msg: "参数缺失", code: -1 })
let { msg, code, friends } = users.getFriends(a.name, a.accessToken)
if (code !== 0)
return cb({ msg: msg, code: code })
cb({ msg: msg, code: 0, data: { friends: friends } })
},
"user.getNick": (a, cb) => { "user.getNick": (a, cb) => {
if (checkEmpty([a.name])) if (checkEmpty([a.name]))
return cb({ msg: "参数缺失", code: -1 }) return cb({ msg: "参数缺失", code: -1 })

1
webpack_lib/.gitignore vendored Normal file
View File

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

3
webpack_lib/readme.md Normal file
View File

@@ -0,0 +1,3 @@
## Webpack
请从本仓库根目录的 .github 文件夹内找到对应的zip文件并把 node_modules 解压在此,方可使用

View File

@@ -0,0 +1,20 @@
const path = require('path')
const { BannerPlugin } = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
entry: '../build_cache/index.js',
output: {
filename: 'index.js',
path: path.resolve(__dirname, '../ling_chair_http'),
},
module: {
rules: [
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
],
},
}