mirror of
https://github.com/LingChair/LingChair-V0.git
synced 2025-12-08 10:05:49 +08:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a5afc8ad0 | ||
|
|
4bdfad340f | ||
|
|
6ac1b460bb | ||
|
|
29e224f87a | ||
|
|
a39973bb5c | ||
|
|
47afacbba3 | ||
|
|
3a4d733c13 | ||
|
|
89263e6e2a | ||
|
|
b2c8c86689 | ||
|
|
6654141c18 | ||
|
|
822a4ad4da | ||
|
|
9f456b95c1 | ||
|
|
7d2798d4fd | ||
|
|
0ccee91b3e | ||
|
|
48bad65df5 | ||
|
|
5b55ca77ec | ||
|
|
71b5b5b2df | ||
|
|
9c1dc4c540 | ||
|
|
f3bf5f88b5 | ||
|
|
971b8aff85 | ||
|
|
ddfbe547e1 | ||
|
|
b4bbe08ff2 | ||
|
|
6ec0005122 | ||
|
|
541f043531 | ||
|
|
7253f0a4ec | ||
|
|
f19c3c793a |
39
.github/QA.md
vendored
Normal file
39
.github/QA.md
vendored
Normal 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
BIN
.github/模块化_Babel.zip
vendored
Normal file
Binary file not shown.
BIN
.github/模块化_Webpack.zip
vendored
Normal file
BIN
.github/模块化_Webpack.zip
vendored
Normal file
Binary file not shown.
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,2 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
ling_chair_data/
|
ling_chair_data/
|
||||||
ling_chair_config/
|
|
||||||
|
|||||||
@@ -79,7 +79,13 @@
|
|||||||
|
|
||||||
.chat-message-left > .avatar,
|
.chat-message-left > .avatar,
|
||||||
.chat-message-right > .avatar {
|
.chat-message-right > .avatar {
|
||||||
width: 45px;
|
width: 50px;
|
||||||
height: 45px;
|
height: 50px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message-image {
|
||||||
|
max-width: 40%;
|
||||||
|
max-height: 40%;
|
||||||
|
border-radius: 15px;
|
||||||
|
}
|
||||||
|
|||||||
97
ling_chair_http/finally.js
Normal file
97
ling_chair_http/finally.js
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
/*
|
||||||
|
* ©2024 满月叶
|
||||||
|
* Github: MoonLeeeaf
|
||||||
|
* 最终执行的杂项
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 感觉 window.attr 比那一堆 import 好用多了
|
||||||
|
|
||||||
|
// ================================
|
||||||
|
// 正文开始
|
||||||
|
// ================================
|
||||||
|
|
||||||
|
// 没有刷新令牌需要重新登录 或者初始化
|
||||||
|
if (!localStorage.refreshToken || localStorage.refreshToken === "")
|
||||||
|
localStorage.isSignIn = false
|
||||||
|
|
||||||
|
if (!localStorage.server || localStorage.server === "")
|
||||||
|
setUpClient()
|
||||||
|
else
|
||||||
|
setUpClient(localStorage.server)
|
||||||
|
|
||||||
|
// 登录到账号
|
||||||
|
let dialogSignIn
|
||||||
|
// 谨防 localStorage 字符串数据大坑
|
||||||
|
if (localStorage.isSignIn == "false")
|
||||||
|
dialogSignIn = new mdui.Dialog(viewBinding.dialogSignIn.get(0), {
|
||||||
|
modal: true,
|
||||||
|
closeOnEsc: false,
|
||||||
|
history: false,
|
||||||
|
}).open()
|
||||||
|
else {
|
||||||
|
(async () => viewBinding.userNick.text(await NickCache.getNick(localStorage.userName)))()
|
||||||
|
let hello
|
||||||
|
let nowHour = new Date().getHours()
|
||||||
|
if (nowHour >= 6 && nowHour <= 11) hello = "早安"
|
||||||
|
else if (nowHour == 12) hello = "中午好"
|
||||||
|
else if (nowHour >= 13 && nowHour <= 18) hello = "下午好"
|
||||||
|
else if (nowHour >= 19 && nowHour < 22) hello = "晚上好"
|
||||||
|
else hello = "晚安"
|
||||||
|
viewBinding.helloText.text(hello)
|
||||||
|
|
||||||
|
viewBinding.userHead.attr("src", CurrentUser.getUserHeadUrl(localStorage.userName))
|
||||||
|
|
||||||
|
ContactsList.reloadList()
|
||||||
|
|
||||||
|
CurrentUser.registerCallback()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 感谢AI的力量
|
||||||
|
Stickyfill.add($("*").filter((a, b) => $(b).css('position') === 'sticky'))
|
||||||
|
|
||||||
|
ChatMsgAdapter.initMsgElementEvents()
|
||||||
|
|
||||||
|
ChatMsgAdapter.initInputResizer()
|
||||||
|
|
||||||
|
const showLinkDialog = (link) => mdui.alert(decodeURI(link) + "<br/>如果你确认此链接是安全的, 那么请<a class=\"mdui-text-color-theme-accent\" href=\"" + link + "\">点我</a>", '链接', () => { }, { confirmText: "关闭" })
|
||||||
|
|
||||||
|
const showImageDialog = (link, id, alt) => mdui.alert(`此图片链接来源未知: ${decodeURI(link)}<br/>如果你希望加载, 请<a class="mdui-text-color-theme-accent" mdui-dialog-close onclick="$('#${id}').html('<img src=\\'${link}\\' alt=\\'${decodeURI(alt)}\\' class=\\'message-image\\'></img>')">点我</a>`, '外部图片', () => { }, { confirmText: "关闭" })
|
||||||
|
|
||||||
|
const showCodeDialog = (code) => mdui.alert(`<pre><code>${decodeURI(code)}</code></pre>`, '代码块', () => { }, { confirmText: "关闭" })
|
||||||
|
|
||||||
|
const renderer = {
|
||||||
|
heading(text, level) {
|
||||||
|
return text
|
||||||
|
},
|
||||||
|
paragraph(text) {
|
||||||
|
return text
|
||||||
|
},
|
||||||
|
blockquote(text) {
|
||||||
|
return text
|
||||||
|
},
|
||||||
|
link(href, title, text) {
|
||||||
|
return `<a class="mdui-text-color-theme-accent" onclick="showLinkDialog('${encodeURI(href)}')">[链接] ${text}</a>`
|
||||||
|
},
|
||||||
|
image(href, title, text) {
|
||||||
|
let h = Hash.sha256(href)
|
||||||
|
let out = true
|
||||||
|
try {
|
||||||
|
out = new URL(href).hostname === new URL(location.href)
|
||||||
|
} catch(e) {}
|
||||||
|
if (out)
|
||||||
|
return `<img src="${encodeURI(href)}" alt="${text}" class="message-image"></img>`
|
||||||
|
else
|
||||||
|
return `<div id="${h}"><a class="mdui-text-color-theme-accent" onclick="showImageDialog('${encodeURI(href)}', '${h}', '${encodeURI(text)}')">[外部图片] ${text}</a></div>`
|
||||||
|
},
|
||||||
|
code(src) {
|
||||||
|
return `<a class="mdui-text-color-theme-accent" onclick="showCodeDialog(\`${encodeURI(src)}\`)">[代码块]</a>`
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
marked.use({
|
||||||
|
gfm: true,
|
||||||
|
renderer: renderer,
|
||||||
|
async: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
573
ling_chair_http/handler.js
Normal file
573
ling_chair_http/handler.js
Normal file
@@ -0,0 +1,573 @@
|
|||||||
|
/*
|
||||||
|
* ©2024 满月叶
|
||||||
|
* Github: MoonLeeeaf
|
||||||
|
* 业务逻辑
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ================================
|
||||||
|
// 当前用户
|
||||||
|
// ================================
|
||||||
|
|
||||||
|
class CurrentUser {
|
||||||
|
static myAccessToken
|
||||||
|
/**
|
||||||
|
* 登录账号
|
||||||
|
* @param {String} name
|
||||||
|
* @param {String} passwd
|
||||||
|
* @param {Function} callback
|
||||||
|
*/
|
||||||
|
static signIn(name, passwd, cb) {
|
||||||
|
client.emit("user.signIn", {
|
||||||
|
name: name,
|
||||||
|
passwd: Hash.sha256(passwd) + Hash.md5(passwd),
|
||||||
|
}, (re) => {
|
||||||
|
if (re.code !== 0)
|
||||||
|
return mdui.snackbar(re.msg)
|
||||||
|
|
||||||
|
cb(re)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 注册账号
|
||||||
|
* @param {String} name
|
||||||
|
* @param {String} passwd
|
||||||
|
* @param {Function} callback
|
||||||
|
*/
|
||||||
|
static signUp(name, passwd, cb) {
|
||||||
|
client.emit("user.signUp", {
|
||||||
|
name: name,
|
||||||
|
passwd: Hash.sha256(passwd) + Hash.md5(passwd),
|
||||||
|
}, (re) => {
|
||||||
|
if (re.code !== 0)
|
||||||
|
return mdui.snackbar(re.msg)
|
||||||
|
|
||||||
|
cb(re)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 登录对话框中的登录逻辑
|
||||||
|
* @param {String} name
|
||||||
|
* @param {String} passwd
|
||||||
|
*/
|
||||||
|
static signInWithDialog(name, passwd) {
|
||||||
|
this.signIn(name, passwd, (re) => {
|
||||||
|
localStorage.refreshToken = re.data.refreshToken
|
||||||
|
localStorage.isSignIn = true
|
||||||
|
|
||||||
|
location.reload()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 设置昵称
|
||||||
|
* @param {String} nick
|
||||||
|
* @param {Function} callback
|
||||||
|
*/
|
||||||
|
static async setNick(nick, cb) {
|
||||||
|
client.emit("user.setNick", {
|
||||||
|
name: localStorage.userName,
|
||||||
|
accessToken: await this.getAccessToken(),
|
||||||
|
nick: nick,
|
||||||
|
}, (re) => {
|
||||||
|
if (re.code !== 0)
|
||||||
|
return mdui.snackbar(re.msg)
|
||||||
|
if (cb) cb()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取用户头像的链接
|
||||||
|
* @param {String} name
|
||||||
|
* @returns {String} headImageUrl
|
||||||
|
*/
|
||||||
|
static getUserHeadUrl(name) {
|
||||||
|
return client.io.uri + "/users_head/" + name + ".png"
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取访问密钥
|
||||||
|
* @param {String} name
|
||||||
|
* @returns {Promise<String>} accessToken
|
||||||
|
*/
|
||||||
|
static async getAccessToken(er) {
|
||||||
|
if (this.myAccessToken == null)
|
||||||
|
this.myAccessToken = await new Promise((res) => {
|
||||||
|
client.emit("user.getAccessToken", { name: localStorage.userName, refreshToken: localStorage.refreshToken }, (r) => {
|
||||||
|
if (r.data != null) res(r.data.accessToken)
|
||||||
|
if (er != null) er(r.msg)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return this.myAccessToken
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 请求上传头像
|
||||||
|
*/
|
||||||
|
static uploadHeadImage() {
|
||||||
|
viewBinding.uploadHeadImage.click()
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 上传头像回调事件
|
||||||
|
* @param {Element} element
|
||||||
|
*/
|
||||||
|
static async uploadHeadImageCallback(self) {
|
||||||
|
let img = self.files[0]
|
||||||
|
client.emit("user.setHeadImage", {
|
||||||
|
name: localStorage.userName,
|
||||||
|
accessToken: await CurrentUser.getAccessToken(),
|
||||||
|
headImage: img,
|
||||||
|
}, (re) => mdui.snackbar(re.msg))
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 验证用户
|
||||||
|
*/
|
||||||
|
static auth() {
|
||||||
|
client.emit("user.auth", { name: localStorage.userName, refreshToken: localStorage.refreshToken }, (re) => {
|
||||||
|
if (re.code !== 0) {
|
||||||
|
console.error(re)
|
||||||
|
if (!re.invalid)
|
||||||
|
return mdui.snackbar("验证用户失败!")
|
||||||
|
|
||||||
|
mdui.alert("账号刷新令牌已过期, 请重新登录哦", "提示", () => CurrentUser.signOutAndReload(), {
|
||||||
|
confirmText: "确定",
|
||||||
|
closeOnConfirm: false,
|
||||||
|
closeOnEsc: false,
|
||||||
|
modal: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 登出并重载页面
|
||||||
|
*/
|
||||||
|
static signOutAndReload() {
|
||||||
|
localStorage.refreshToken = ""
|
||||||
|
localStorage.isSignIn = false
|
||||||
|
|
||||||
|
setTimeout(() => location.reload(), 300)
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 注册客户端回调事件
|
||||||
|
*/
|
||||||
|
static registerCallback() {
|
||||||
|
client.on("msg.receive", async (a) => {
|
||||||
|
if (checkEmpty([a.target, a.msg, a.type]))
|
||||||
|
return
|
||||||
|
|
||||||
|
if ((ChatMsgAdapter.target === a.target) && (ChatMsgAdapter.type === a.type)) {
|
||||||
|
let i = ChatMsgAdapter.isAtBottom()
|
||||||
|
await ChatMsgAdapter.addMsg(a.target, a.msg.msg, a.msg.time)
|
||||||
|
if (i) ChatMsgAdapter.scrollToBottom()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ChatMsgAdapter.target !== localStorage.userName) {
|
||||||
|
let n = new 通知().setTitle("" + await NickCache.getNick(a.target)).setMessage(a.msg.msg).setIcon(CurrentUser.getUserHeadUrl(a.target)).show(async () => {
|
||||||
|
await ChatMsgAdapter.switchTo(a.target, a.type)
|
||||||
|
location.replace("#msgid_" + a.msg.msgid)
|
||||||
|
n.close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 打开资料卡
|
||||||
|
* @param {String} name
|
||||||
|
*/
|
||||||
|
static async openProfileDialog(name) {
|
||||||
|
viewBinding.dialogProfileHead.attr("src", CurrentUser.getUserHeadUrl(name))
|
||||||
|
viewBinding.dialogProfileNick.text(await NickCache.getNick(name))
|
||||||
|
new mdui.Dialog(viewBinding.dialogProfile).open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================================
|
||||||
|
// 昵称缓存
|
||||||
|
// ================================
|
||||||
|
|
||||||
|
class NickCache {
|
||||||
|
static data = {}
|
||||||
|
/**
|
||||||
|
* 获取昵称
|
||||||
|
* @param {String} name
|
||||||
|
* @returns {String} nick
|
||||||
|
*/
|
||||||
|
static async getNick(name) {
|
||||||
|
return await new Promise((res, rej) => {
|
||||||
|
// 这个this别摆着不放啊 不然两下就会去世
|
||||||
|
let nick = this.data[name]
|
||||||
|
if (nick == null)
|
||||||
|
client.emit("user.getNick", { name: localStorage.userName }, (re) => {
|
||||||
|
let nk = re.data != null ? re.data.nick : name
|
||||||
|
if (nk == null) nk = name
|
||||||
|
this.data[name] = nk
|
||||||
|
res(nk)
|
||||||
|
})
|
||||||
|
else
|
||||||
|
res(nick)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ContactsList {
|
||||||
|
/**
|
||||||
|
* 重载联系人列表
|
||||||
|
*/
|
||||||
|
static async reloadList() {
|
||||||
|
client.emit("user.getFriends", {
|
||||||
|
name: localStorage.userName,
|
||||||
|
accessToken: await CurrentUser.getAccessToken(),
|
||||||
|
}, async (re) => {
|
||||||
|
if (re.code !== 0)
|
||||||
|
return mdui.snackbar(re.msg)
|
||||||
|
|
||||||
|
viewBinding.contactsList.empty()
|
||||||
|
let ls = re.data.friends
|
||||||
|
for (let index in ls) {
|
||||||
|
let name = ls[index]
|
||||||
|
let dick = await NickCache.getNick(name)
|
||||||
|
$($.parseHTML(`<li class="mdui-list-item mdui-ripple" mdui-drawer-close><div class="mdui-list-item-avatar"><img src="` + CurrentUser.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")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 添加联系人/群峦
|
||||||
|
* @param {String} nameOrId
|
||||||
|
*/
|
||||||
|
static add(name, type) {
|
||||||
|
if (type == "single") {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 打开添加联系人的对话框
|
||||||
|
*/
|
||||||
|
static openAddDialog() {
|
||||||
|
new mdui.Dialog(viewBinding.dialogNewContact.get(0)).open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 消息核心
|
||||||
|
|
||||||
|
class ChatPage {
|
||||||
|
static cached = {}
|
||||||
|
constructor(name, type) {
|
||||||
|
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 切换到某一个聊天对象
|
||||||
|
* @param {String} name
|
||||||
|
* @param {String} type
|
||||||
|
*/
|
||||||
|
static switchTo(name, type) {
|
||||||
|
if (!this.cached[name])
|
||||||
|
this.cached[name] = new ChatPage(name, type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChatMsgAdapter {
|
||||||
|
static type
|
||||||
|
static target
|
||||||
|
static minMsgId
|
||||||
|
static time
|
||||||
|
static minutesCache
|
||||||
|
static resizeDick
|
||||||
|
/**
|
||||||
|
* 切换到某一个聊天对象
|
||||||
|
* @param {String} name
|
||||||
|
* @param {String} type
|
||||||
|
*/
|
||||||
|
static async switchTo(name, type) {
|
||||||
|
viewBinding.tabChatSeesion.show()
|
||||||
|
viewBinding.tabChatSeesion.text(await NickCache.getNick(name))
|
||||||
|
viewBinding.tabChatSeesion.get(0).click()
|
||||||
|
|
||||||
|
this.type = type
|
||||||
|
this.target = name
|
||||||
|
this.minMsgId = null
|
||||||
|
|
||||||
|
viewBinding.pageChatSeesion.empty()
|
||||||
|
|
||||||
|
await this.loadMore()
|
||||||
|
this.scrollToBottom()
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 发送消息
|
||||||
|
* @param {String} msg
|
||||||
|
*/
|
||||||
|
static async send(msg) {
|
||||||
|
client.emit("user.sendSingleMsg", {
|
||||||
|
name: localStorage.userName,
|
||||||
|
target: this.target,
|
||||||
|
msg: msg,
|
||||||
|
accessToken: await CurrentUser.getAccessToken(),
|
||||||
|
}, async (re) => {
|
||||||
|
if (re.code !== 0)
|
||||||
|
return mdui.snackbar(re.msg)
|
||||||
|
|
||||||
|
viewBinding.inputMsg.val("")
|
||||||
|
|
||||||
|
// 微机课闲的没事干玩玩 发现私聊会多发一个(一个是本地的, 另一个是发送成功的) 选择一个关掉就好了
|
||||||
|
// 这里我选择服务端不发送回调, 不然多设备同步会吵死
|
||||||
|
// 错了 应该是客户端少发条才对 不然不能多设备同步
|
||||||
|
if ((ChatMsgAdapter.target !== localStorage.userName) && ChatMsgAdapter.type === "single") {
|
||||||
|
let i = ChatMsgAdapter.isAtBottom()
|
||||||
|
await ChatMsgAdapter.addMsg(localStorage.userName, msg, re.data.time, re.data.msgid)
|
||||||
|
if (i) ChatMsgAdapter.scrollToBottom()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取聊天消息记录
|
||||||
|
* @param {int} 起始点
|
||||||
|
* @param {int} 获取数量
|
||||||
|
*/
|
||||||
|
static async getHistroy(start, limit) {
|
||||||
|
return new Promise(async (res, rej) => {
|
||||||
|
client.emit("user.getSingleChatHistroy", {
|
||||||
|
name: localStorage.userName,
|
||||||
|
target: this.target,
|
||||||
|
limit: limit,
|
||||||
|
accessToken: await CurrentUser.getAccessToken(),
|
||||||
|
startId: start,
|
||||||
|
}, (re) => {
|
||||||
|
if (re.code !== 0)
|
||||||
|
return mdui.snackbar(re.msg)
|
||||||
|
res(re.data.histroy)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 加载更多聊天记录
|
||||||
|
* @param {int}} 加载数量
|
||||||
|
*/
|
||||||
|
static async loadMore(limit) {
|
||||||
|
let histroy = await this.getHistroy(this.minMsgId, limit == null ? 13 : limit)
|
||||||
|
|
||||||
|
if (histroy.length == 0)
|
||||||
|
return mdui.snackbar("已经加载完了~")
|
||||||
|
|
||||||
|
let re = this.minMsgId != null
|
||||||
|
this.minMsgId = histroy[0].msgid - 1
|
||||||
|
let sc = 0
|
||||||
|
if (re) histroy = histroy.reverse()
|
||||||
|
for (let index in histroy) {
|
||||||
|
let i = histroy[index]
|
||||||
|
let e = await this.addMsg(i.name, i.msg, i.time, re, i.msgid)
|
||||||
|
// 因为某些因素直接DEBUG到吐血 断点继续都不报错 原因不明
|
||||||
|
sc = sc + (e == null ? 25 : e.get(0).offsetTop)
|
||||||
|
}
|
||||||
|
window.scrollBy({
|
||||||
|
top: sc,
|
||||||
|
behavior: 'smooth'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 添加系统消息
|
||||||
|
* @param {String} 消息
|
||||||
|
* @param {String} 是否加到顶部
|
||||||
|
* @returns {jQuery} 消息元素
|
||||||
|
*/
|
||||||
|
static addSystemMsg(m, re) {
|
||||||
|
let e
|
||||||
|
if (re)
|
||||||
|
// 加到头部
|
||||||
|
e = $($.parseHTML(m)).prependTo(viewBinding.pageChatSeesion)
|
||||||
|
else
|
||||||
|
// 加到尾部
|
||||||
|
e = $($.parseHTML(m)).appendTo(viewBinding.pageChatSeesion)
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 是否在底部
|
||||||
|
* @returns {Boolean} 是否在底部
|
||||||
|
*/
|
||||||
|
static isAtBottom() {
|
||||||
|
let elementRect = viewBinding.pageChatSeesion.get(0).getBoundingClientRect()
|
||||||
|
return (elementRect.bottom <= window.innerHeight)
|
||||||
|
}
|
||||||
|
// 添加消息 返回消息的JQ对象
|
||||||
|
// name: 用户id m: 消息 t: 时间戳 re: 默认加到尾部 msgid: 消息id
|
||||||
|
/**
|
||||||
|
* 添加聊天记录
|
||||||
|
* @param {String} name
|
||||||
|
* @param {String} msg
|
||||||
|
* @param {String} type
|
||||||
|
* @param {String} 是否加到头部
|
||||||
|
* @param {String || int} 消息id
|
||||||
|
* @returns {jQuery} 消息元素
|
||||||
|
*/
|
||||||
|
static async addMsg(name, preMsg, time, addToTop, msgid) {
|
||||||
|
|
||||||
|
let nick = await NickCache.getNick(name) // re.data == null ? name : re.data.nick
|
||||||
|
|
||||||
|
let msg
|
||||||
|
|
||||||
|
try {
|
||||||
|
msg = await marked.parse(preMsg)
|
||||||
|
} catch(error) {
|
||||||
|
console.log("解析消息失败: " + error)
|
||||||
|
msg = escapeHTML(preMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
let temp
|
||||||
|
if (name === localStorage.userName)
|
||||||
|
temp = `<div class="chat-message-right">
|
||||||
|
<div class="message-content-with-nickname-right">
|
||||||
|
<span class="nickname">${ nick }</span>
|
||||||
|
<div class="message-content mdui-card" tag="msg-card" id="msgid_${ msgid }">
|
||||||
|
<span id="msg-content">${ msg }</span>
|
||||||
|
<pre class="mdui-hidden" id="raw-msg-content">${ preMsg }</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<img class="avatar" src="${ CurrentUser.getUserHeadUrl(name) }" onerror="this.src='res/default_head.png'" />
|
||||||
|
</div>`
|
||||||
|
else
|
||||||
|
temp = `<div class="chat-message-left">
|
||||||
|
<img class="avatar" src="${ CurrentUser.getUserHeadUrl(name) }" onerror="this.src='res/default_head.png'" />
|
||||||
|
<div class="message-content-with-nickname-left">
|
||||||
|
<span class="nickname">${ nick }</span>
|
||||||
|
<div class="message-content mdui-card" tag="msg-card" id="msgid_${ msgid }">
|
||||||
|
<span id="msg-content">${ msg }</span>
|
||||||
|
<pre class="mdui-hidden" id="raw-msg-content">${ preMsg }</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
|
||||||
|
let nowMinutes = new Date(time).getMinutes()
|
||||||
|
let msgElement
|
||||||
|
if (addToTop) {
|
||||||
|
this.addSystemMsg(temp, addToTop)
|
||||||
|
if (this.minutesCache != nowMinutes) {
|
||||||
|
msgElement = this.addSystemMsg(`<div class="mdui-center">` + new Date().format(time == null ? Date.parse("1000-1-1 00:00:00") : time, "yyyy年MM月dd日 hh:mm:ss") + `</div>`, addToTop)
|
||||||
|
this.time = nowMinutes
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.minutesCache != nowMinutes) {
|
||||||
|
msgElement = this.addSystemMsg(`<div class="mdui-center">` + new Date().format(time == null ? Date.parse("1000-1-1 00:00:00") : time, "yyyy年MM月dd日 hh:mm:ss") + `</div>`, addToTop)
|
||||||
|
this.time = nowMinutes
|
||||||
|
}
|
||||||
|
this.addSystemMsg(temp, addToTop)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.minutesCache = new Date(time).getMinutes()
|
||||||
|
|
||||||
|
return msgElement
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 从服务器加载一些聊天记录
|
||||||
|
* @param {int} 数量
|
||||||
|
*/
|
||||||
|
static async loadMsgs(limit) {
|
||||||
|
let histroy = await this.getHistroy(this.msgList[0] == null ? null : this.msgList[0].msgid - 1, limit == null ? 13 : limit)
|
||||||
|
this.msgList = histroy
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 滑到底部
|
||||||
|
*/
|
||||||
|
static scrollToBottom() {
|
||||||
|
// 吐了啊 原来这样就行了 我何必在子element去整啊
|
||||||
|
viewBinding.chatPager.get(0).scrollBy({
|
||||||
|
top: 1145141919810,
|
||||||
|
behavior: 'smooth'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 初始化输入框位置调整器
|
||||||
|
*/
|
||||||
|
static initInputResizer() {
|
||||||
|
// 实验表面移动端切出输入法时会触发1-2次resize事件
|
||||||
|
// 可以利用这个特性来实现自动滚动文本
|
||||||
|
let resize = () => {
|
||||||
|
// CSS 牵一发而动全身 因此这个减少的数值是每天都要更改的
|
||||||
|
viewBinding.pageChatSeesion.height(window.innerHeight - viewBinding.inputToolbar.height() - $("header.mdui-appbar").height() - viewBinding.chatTab.height() - 65)
|
||||||
|
let ledi = this.resizeDick - window.innerHeight
|
||||||
|
if (isMobile()) viewBinding.chatPager.get(0).scrollBy({
|
||||||
|
// 5.19晚10:56分调配出来的秘方
|
||||||
|
// < 0 为窗口变大
|
||||||
|
// cnm的,调试十万次就你tm检测不到底是吧,就你语法天天错误是吧
|
||||||
|
// 欺负我现在用不了电脑
|
||||||
|
top: -(ledi) * ((ledi < 0 && this.isAtBottom()) ? 6 : -1), // (ledi < 0 ? 6 : 6),
|
||||||
|
behavior: 'smooth'
|
||||||
|
})
|
||||||
|
this.resizeDick = window.innerHeight
|
||||||
|
}
|
||||||
|
window.initInputResizerResize = resize
|
||||||
|
window.addEventListener("resize", resize)
|
||||||
|
resize()
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 初始化消息框右击事件
|
||||||
|
*/
|
||||||
|
static initMsgElementEvents() {
|
||||||
|
let listeners = {}
|
||||||
|
let menu
|
||||||
|
let callback = (e) => {
|
||||||
|
if (menu) menu.close()
|
||||||
|
// 切到 div.message-content
|
||||||
|
let ele = e.get(0)
|
||||||
|
while ($(ele).attr("tag") != "msg-card")
|
||||||
|
ele = ele.parentNode
|
||||||
|
e = $(ele)
|
||||||
|
let menuHtml = $.parseHTML(`<ul class="mdui-menu menu-on-message">
|
||||||
|
<li class="mdui-menu-item">
|
||||||
|
<a onclick="copyText(\`${ e.find("#msg-content").text() }\`)" class="mdui-ripple">复制</a>
|
||||||
|
</li>
|
||||||
|
<li class="mdui-menu-item">
|
||||||
|
<a onclick="mdui.alert(\`${ e.find("#raw-msg-content").text() }\`, '消息原文', () => { }, { confirmText: '关闭' })" class="mdui-ripple">原文</a>
|
||||||
|
</li>
|
||||||
|
<li class="mdui-menu-item">
|
||||||
|
<a onclick="mdui.alert('未制作功能', '提示', () => { }, { confirmText: '关闭' })" class="mdui-ripple">转发</a>
|
||||||
|
</li>
|
||||||
|
</ul>`)
|
||||||
|
let $menu = $(menuHtml)
|
||||||
|
e.before($menu)
|
||||||
|
menu = new mdui.Menu(e.get(0), menuHtml, {
|
||||||
|
position: "bottom",
|
||||||
|
align: "right",
|
||||||
|
// covered: true,
|
||||||
|
})
|
||||||
|
$menu.on('closed.mdui.menu', () => {
|
||||||
|
$(menuHtml).remove()
|
||||||
|
})
|
||||||
|
menu.open()
|
||||||
|
}
|
||||||
|
viewBinding.pageChatSeesion.on('contextmenu mousedown mouseup', '.message-content', (e) => {
|
||||||
|
let eventType = e.type
|
||||||
|
let self = $(e.target)
|
||||||
|
|
||||||
|
// 根据事件类型执行不同操作
|
||||||
|
switch (eventType) {
|
||||||
|
case 'contextmenu':
|
||||||
|
e.preventDefault() // 阻止默认行为
|
||||||
|
callback(self)
|
||||||
|
break
|
||||||
|
case 'mousedown':
|
||||||
|
if (!isMobile()) return
|
||||||
|
listeners[self + ""] = setTimeout(() => {
|
||||||
|
callback(self)
|
||||||
|
}, 300) // 300颗够吗 应该够吧
|
||||||
|
break
|
||||||
|
case 'mouseup':
|
||||||
|
if (!isMobile()) return
|
||||||
|
clearTimeout(listeners[self + ""])
|
||||||
|
listeners[self + ""] = null
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新联系人列表以及昵称缓存
|
||||||
|
*/
|
||||||
|
function refreshAll() {
|
||||||
|
ContactsList.reloadList()
|
||||||
|
delete NickCache.data
|
||||||
|
NickCache.data = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.User = CurrentUser
|
||||||
|
window.ContactsList = ContactsList
|
||||||
|
window.NickCache = NickCache
|
||||||
|
window.ChatPage = ChatPage
|
||||||
|
window.ChatMsgAdapter = ChatMsgAdapter
|
||||||
|
window.refreshAll = refreshAll
|
||||||
@@ -1,29 +1,14 @@
|
|||||||
/*
|
/*
|
||||||
* 铃之椅 - 把选择权还给用户, 让聊天权掌握在用户手中
|
* ©2024 满月叶
|
||||||
* Copyright 2024 满月叶
|
* Github: MoonLeeeaf
|
||||||
* 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 {
|
html, body {
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
/* overflow: hidden; */
|
overflow: hidden;
|
||||||
/*font: initial;*/
|
/*font: initial;*/
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
@@ -43,3 +28,30 @@ body {
|
|||||||
margin-top: 60px;
|
margin-top: 60px;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[n-id=pageChatSeesion]::after {
|
||||||
|
content: "";
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-seesion {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* https://segmentfault.com/q/1010000010391524 */
|
||||||
|
img {
|
||||||
|
image-rendering: -moz-crisp-edges; /* Firefox */
|
||||||
|
image-rendering: -o-crisp-edges; /* Opera */
|
||||||
|
image-rendering: -webkit-optimize-contrast; /* Webkit (non-standard naming) */
|
||||||
|
image-rendering: crisp-edges;
|
||||||
|
-ms-interpolation-mode: nearest-neighbor; /* IE (non-standard property) */
|
||||||
|
}
|
||||||
|
|
||||||
|
img.round {
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,26 +1,10 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="zh-cmn-Hans">
|
<html lang="zh-cmn-Hans">
|
||||||
<!--
|
<!--
|
||||||
* 铃之椅 - 把选择权还给用户, 让聊天权掌握在用户手中
|
* ©2024 满月叶
|
||||||
* Copyright 2024 满月叶
|
* Github: MoonLeeeaf
|
||||||
* 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>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, shrink-to-fit=no" />
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, shrink-to-fit=no" />
|
||||||
@@ -28,9 +12,8 @@
|
|||||||
<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" />
|
||||||
|
|
||||||
<!-- 给老旧的设备提供支持 支持不了, 照样没法运行 -->
|
<!-- Maybe it can run :D -->
|
||||||
<!-- <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> -->
|
|
||||||
|
|
||||||
<!-- 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" />
|
||||||
@@ -38,10 +21,12 @@
|
|||||||
<link rel="stylesheet" href="chat-message.css" />
|
<link rel="stylesheet" href="chat-message.css" />
|
||||||
<link rel="stylesheet" href="mdui-prettier.css" />
|
<link rel="stylesheet" href="mdui-prettier.css" />
|
||||||
|
|
||||||
<!-- 代替私人 fixed 并提供更好的兼容性 -->
|
<!-- Scripts -->
|
||||||
<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>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||||
|
<link rel="icon" href="res/icon.ico" />
|
||||||
<title>铃之椅</title>
|
<title>铃之椅</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@@ -49,23 +34,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 +85,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 +97,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 +119,27 @@
|
|||||||
|
|
||||||
<!-- 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" class="chat-seesion">
|
||||||
|
</div>
|
||||||
|
<!-- 输入框和聊天消息重叠的原因就是死人 scrollbar, 把自动调整的距离调小, margin调大就行了 -->
|
||||||
</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-top: 1px;bottom: 0;z-index: 101;padding-top: 7px;" 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>
|
||||||
@@ -187,9 +185,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mdui-dialog-actions">
|
<div class="mdui-dialog-actions">
|
||||||
<button class="mdui-btn mdui-ripple"
|
<button class="mdui-btn mdui-ripple"
|
||||||
onclick="User.signUp(viewBinding.dialogSignInName.val(), viewBinding.dialogSignInPasswd.val(), () => mdui.snackbar('注册成功, 请直接点击登录即可~'))">注册</button>
|
onclick="CurrentUser.signUp(viewBinding.dialogSignInName.val(), viewBinding.dialogSignInPasswd.val(), () => mdui.snackbar('注册成功, 请直接点击登录即可~'))">注册</button>
|
||||||
<button class="mdui-btn mdui-ripple" n-id="dialogSignInEnter"
|
<button class="mdui-btn mdui-ripple" n-id="dialogSignInEnter"
|
||||||
onclick="User.signInWithDialog(viewBinding.dialogSignInName.val(), viewBinding.dialogSignInPasswd.val())">登录</button>
|
onclick="CurrentUser.signInWithDialog(viewBinding.dialogSignInName.val(), viewBinding.dialogSignInPasswd.val())">登录</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -209,29 +207,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</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">
|
|
||||||
<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>
|
|
||||||
</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" n-id="dialogEditNick">
|
||||||
<div class="mdui-dialog-title">
|
<div class="mdui-dialog-title">
|
||||||
@@ -245,15 +220,77 @@
|
|||||||
</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="CurrentUser.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="CurrentUser.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">
|
||||||
<input type="file" n-id="uploadHeadImage" name="选择头像" onchange="User.uploadHeadImageCallback(this)"
|
<input type="file" n-id="uploadHeadImage" name="选择头像" onchange="CurrentUser.uploadHeadImageCallback(this)"
|
||||||
accept="image/png, image/jpeg" />
|
accept="image/png, image/jpeg" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -261,12 +298,12 @@
|
|||||||
<script src="https://unpkg.com/crypto-js@4.2.0/crypto-js.js"></script>
|
<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/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>
|
<script src="https://unpkg.com/mdui@1.0.2/dist/js/mdui.min.js"></script>
|
||||||
<!-- 加了babel也许能解决下浏览器兼容问题 -->
|
<!-- 核心脚本部分 -->
|
||||||
<!-- <script type="module" src="index.js"></script>
|
<script src="utils.js"></script>
|
||||||
<script nomodule type="text/babel" src="index.js"></script> -->
|
<script src="manager.js"></script>
|
||||||
|
<script src="ui.js"></script>
|
||||||
<!-- 就算弄语法兼容其他老旧设备照样用不了, 比如我的 iPad4 -->
|
<script src="handler.js"></script>
|
||||||
<script src="index.js"></script>
|
<script src="finally.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -1,746 +0,0 @@
|
|||||||
/*
|
|
||||||
* 铃之椅 - 把选择权还给用户, 让聊天权掌握在用户手中
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const UrlArgs = new URL(location.href).searchParams
|
|
||||||
|
|
||||||
function setOnRightClick(e, cb) {
|
|
||||||
if (!(e instanceof jQuery))
|
|
||||||
e = $(e)
|
|
||||||
|
|
||||||
let longPressTimer
|
|
||||||
if (!cb) throw new Error("定义回调!!!!")
|
|
||||||
e.on('contextmenu', function (e) {
|
|
||||||
e.preventDefault() // 阻止默认右键菜单
|
|
||||||
cb()
|
|
||||||
})
|
|
||||||
|
|
||||||
e.on('mousedown', function () {
|
|
||||||
longPressTimer = setTimeout(function () {
|
|
||||||
cb()
|
|
||||||
}, 1000)
|
|
||||||
})
|
|
||||||
|
|
||||||
e.on('mouseup', function () {
|
|
||||||
clearTimeout(longPressTimer)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (UrlArgs.get("debug")) {
|
|
||||||
let script = document.createElement('script')
|
|
||||||
script.src = "//cdn.jsdelivr.net/npm/eruda"
|
|
||||||
document.body.appendChild(script)
|
|
||||||
script.onload = () => eruda.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 经常会因为这个指定ID为位置导致一些莫名BUG
|
|
||||||
if (location.href.includes("#")) location.replace(location.href.substring(0, location.href.indexOf("#")))
|
|
||||||
|
|
||||||
const mdui_snackbar = mdui.snackbar
|
|
||||||
mdui.snackbar = (m) => {
|
|
||||||
let t = m
|
|
||||||
if (m instanceof Object)
|
|
||||||
t = JSON.stringify(m)
|
|
||||||
mdui_snackbar(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkEmpty = (i) => {
|
|
||||||
if (i instanceof Array) {
|
|
||||||
for (let k of i) {
|
|
||||||
if (checkEmpty(k)) return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (i == null) || ("" === i) || (0 === i)
|
|
||||||
}
|
|
||||||
|
|
||||||
function escapeHTML(str) {
|
|
||||||
return str.replace(/[<>&"']/g, function (match) {
|
|
||||||
switch (match) {
|
|
||||||
case '<':
|
|
||||||
return '<'
|
|
||||||
case '>':
|
|
||||||
return '>'
|
|
||||||
case '&':
|
|
||||||
return '&'
|
|
||||||
case '"':
|
|
||||||
return '"'
|
|
||||||
case "'":
|
|
||||||
return '''
|
|
||||||
default:
|
|
||||||
return match
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
class NData {
|
|
||||||
static mount(node) {
|
|
||||||
// 便捷获得指定组件
|
|
||||||
let es = node.querySelectorAll("[n-id]")
|
|
||||||
let ls = {}
|
|
||||||
es.forEach((i) => ls[$(i).attr("n-id")] = $(i))
|
|
||||||
|
|
||||||
// input 组件与 localStorage 绑定
|
|
||||||
es = node.querySelectorAll("[n-input-ls]")
|
|
||||||
es.forEach((e) => {
|
|
||||||
let j = $(e)
|
|
||||||
j.val(localStorage.getItem(j.attr("n-input-ls")))
|
|
||||||
j.blur(() => localStorage.setItem(j.attr("n-input-ls"), j.val()))
|
|
||||||
})
|
|
||||||
return ls
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 快捷获取指定视图
|
|
||||||
let viewBinding = NData.mount($("#app").get(0))
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: "config.json",
|
|
||||||
dataType: "json",
|
|
||||||
success: (c) => {
|
|
||||||
viewBinding.appTitle.text(c.appTitle)
|
|
||||||
if (!c.canChangeServer) {
|
|
||||||
viewBinding.dialogSignInServerLabel.hide()
|
|
||||||
viewBinding.drawerChangeServer.hide()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// Toolbar 快捷按钮绑定
|
|
||||||
viewBinding.contactsRefresh.hide()
|
|
||||||
viewBinding.tabChatList.on("show.mdui.tab", () => {
|
|
||||||
viewBinding.contactsRefresh.hide()
|
|
||||||
})
|
|
||||||
viewBinding.tabContacts.on("show.mdui.tab", () => {
|
|
||||||
viewBinding.contactsRefresh.show()
|
|
||||||
})
|
|
||||||
viewBinding.tabChatSeesion.on("show.mdui.tab", () => {
|
|
||||||
viewBinding.contactsRefresh.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.drawerChangeServer.click(() => {
|
|
||||||
mdui.prompt('输入服务器地址...(为空则使用当前页面地址)', (value) => {
|
|
||||||
localStorage.server = value
|
|
||||||
mdui.snackbar("更新成功, 刷新页面生效")
|
|
||||||
}, () => { }, {
|
|
||||||
confirmText: "确定",
|
|
||||||
cancelText: "取消"
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
viewBinding.drawerSignOut.click(() => {
|
|
||||||
mdui.confirm('确定要登出账号吗', () => {
|
|
||||||
User.signOutAndReload()
|
|
||||||
}, () => { }, {
|
|
||||||
confirmText: "确定",
|
|
||||||
cancelText: "取消"
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
viewBinding.sendMsg.click((a) => {
|
|
||||||
let text = viewBinding.inputMsg.val()
|
|
||||||
if (text.trim() !== "")
|
|
||||||
ChatMsgAdapter.send(text)
|
|
||||||
})
|
|
||||||
|
|
||||||
viewBinding.inputMsg.keydown((e) => {
|
|
||||||
if (e.ctrlKey && e.keyCode === 13)
|
|
||||||
viewBinding.sendMsg.click()
|
|
||||||
})
|
|
||||||
|
|
||||||
viewBinding.dialogSignInPasswd.keydown((e) => {
|
|
||||||
if (e.keyCode === 13)
|
|
||||||
viewBinding.dialogSignInEnter.click()
|
|
||||||
})
|
|
||||||
|
|
||||||
viewBinding.switchNotifications.click((a) => {
|
|
||||||
if ((localStorage.useNotifications == "true" || localStorage.useNotifications != null) && localStorage.useNotifications != "false") {
|
|
||||||
localStorage.useNotifications = "false"
|
|
||||||
viewBinding.switchNotificationsIcon.text("notifications_off")
|
|
||||||
} else {
|
|
||||||
localStorage.useNotifications = "true"
|
|
||||||
viewBinding.switchNotificationsIcon.text("notifications")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (localStorage.useNotifications == "true")
|
|
||||||
viewBinding.switchNotificationsIcon.text("notifications")
|
|
||||||
|
|
||||||
// https://www.runoob.com/w3cnote/javascript-copy-clipboard.html
|
|
||||||
function copyText(t) {
|
|
||||||
let cp = viewBinding.textCopier.get(0)
|
|
||||||
cp.value = t
|
|
||||||
cp.select()
|
|
||||||
cp.setSelectionRange(0, 99999)
|
|
||||||
navigator.clipboard.writeText(cp.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://zhuanlan.zhihu.com/p/162910462
|
|
||||||
Date.prototype.format = function (tms, format) {
|
|
||||||
let tmd = new Date(tms)
|
|
||||||
/*
|
|
||||||
* 例子: format="YYYY-MM-dd hh:mm:ss";
|
|
||||||
*/
|
|
||||||
var o = {
|
|
||||||
"M+": tmd.getMonth() + 1, // month
|
|
||||||
"d+": tmd.getDate(), // day
|
|
||||||
"h+": tmd.getHours(), // hour
|
|
||||||
"m+": tmd.getMinutes(), // minute
|
|
||||||
"s+": tmd.getSeconds(), // second
|
|
||||||
"q+": Math.floor((tmd.getMonth() + 3) / 3), // quarter
|
|
||||||
"S": tmd.getMilliseconds()
|
|
||||||
// millisecond
|
|
||||||
}
|
|
||||||
if (/(y+)/.test(format)) {
|
|
||||||
format = format.replace(RegExp.$1, (tmd.getFullYear() + "")
|
|
||||||
.substr(4 - RegExp.$1.length));
|
|
||||||
}
|
|
||||||
for (var k in o) {
|
|
||||||
if (new RegExp("(" + k + ")").test(format)) {
|
|
||||||
format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k]
|
|
||||||
: ("00" + o[k]).substr(("" + o[k]).length));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return format;
|
|
||||||
}
|
|
||||||
|
|
||||||
// new mdui.Drawer('#main-drawer').close()
|
|
||||||
|
|
||||||
class NickCache {
|
|
||||||
static data = {}
|
|
||||||
static async getNick(name) {
|
|
||||||
return await new Promise((res, rej) => {
|
|
||||||
// 这个this别摆着不放啊 不然两下就会去世
|
|
||||||
let nick = this.data[name]
|
|
||||||
if (nick == null)
|
|
||||||
client.emit("user.getNick", { name: localStorage.userName }, (re) => {
|
|
||||||
let nk = re.data != null ? re.data.nick : name
|
|
||||||
if (nk == null) nk = name
|
|
||||||
this.data[name] = nk
|
|
||||||
res(nk)
|
|
||||||
})
|
|
||||||
else
|
|
||||||
res(nick)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 既然已经有 Notification 了, 那用回中文也不过分吧 :)
|
|
||||||
class 通知 {
|
|
||||||
constructor() {
|
|
||||||
this.args = {}
|
|
||||||
this.title = ""
|
|
||||||
}
|
|
||||||
static checkAvailable() {
|
|
||||||
return ("Notification" in window)
|
|
||||||
}
|
|
||||||
static async request() {
|
|
||||||
if (!this.checkAvailable()) return false
|
|
||||||
return (await Notification.requestPermission())
|
|
||||||
}
|
|
||||||
setId(id) {
|
|
||||||
this.args.tag = id
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
setTitle(t) {
|
|
||||||
this.title = t
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
setMessage(m) {
|
|
||||||
this.args.body = m
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
setIcon(i) {
|
|
||||||
this.args.icon = i
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
setImage(i) {
|
|
||||||
this.args.image = i
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
setData(data) {
|
|
||||||
this.args.data = data
|
|
||||||
}
|
|
||||||
show(onclick/*, onclose*/) {
|
|
||||||
if (!通知.checkAvailable()) return
|
|
||||||
if (localStorage.useNotifications !== "true") return
|
|
||||||
let n = new Notification(this.title, this.args)
|
|
||||||
n.onclick = onclick == null ? () => n.close() : (n) => onclick(n)
|
|
||||||
// n.onclose = onclose
|
|
||||||
// n.close()
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ContactsList {
|
|
||||||
static async reloadList() {
|
|
||||||
client.emit("user.getFriends", {
|
|
||||||
name: localStorage.userName,
|
|
||||||
accessToken: await User.getAccessToken(),
|
|
||||||
}, async (re) => {
|
|
||||||
if (re.code !== 0)
|
|
||||||
return mdui.snackbar(re.msg)
|
|
||||||
|
|
||||||
viewBinding.contactsList.empty()
|
|
||||||
let ls = re.data.friends
|
|
||||||
for (let index in ls) {
|
|
||||||
let name = ls[index]
|
|
||||||
let dick = await NickCache.getNick(name)
|
|
||||||
/*client.emit("user.getNick", { name: localStorage.userName }, (re) => {
|
|
||||||
let nick = re.data == null ? re.data.nick : null
|
|
||||||
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(() => {
|
|
||||||
ChatMsgAdapter.switchTo(name, "single")
|
|
||||||
})
|
|
||||||
//})
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 第一次写前端的消息加载, 代码很乱, 还请原谅~
|
|
||||||
|
|
||||||
class ChatMsgAdapter {
|
|
||||||
static type
|
|
||||||
static target
|
|
||||||
// static msgList
|
|
||||||
static minMsgId
|
|
||||||
static time
|
|
||||||
static bbn
|
|
||||||
// 切换聊天对象
|
|
||||||
static async switchTo(name, type) {
|
|
||||||
viewBinding.tabChatSeesion.show()
|
|
||||||
viewBinding.tabChatSeesion.text(await NickCache.getNick(name))
|
|
||||||
viewBinding.tabChatSeesion.get(0).click()
|
|
||||||
|
|
||||||
this.type = type
|
|
||||||
this.target = name
|
|
||||||
// this.msgList = []
|
|
||||||
this.minMsgId = null
|
|
||||||
|
|
||||||
viewBinding.pageChatSeesion.empty()
|
|
||||||
|
|
||||||
await this.loadMore()
|
|
||||||
this.scrollToBottom()
|
|
||||||
}
|
|
||||||
// 发送消息
|
|
||||||
static async send(msg) {
|
|
||||||
client.emit("user.sendSingleMsg", {
|
|
||||||
name: localStorage.userName,
|
|
||||||
target: this.target,
|
|
||||||
msg: msg,
|
|
||||||
accessToken: await User.getAccessToken(),
|
|
||||||
}, async (re) => {
|
|
||||||
if (re.code !== 0)
|
|
||||||
return mdui.snackbar(re.msg)
|
|
||||||
|
|
||||||
viewBinding.inputMsg.val("")
|
|
||||||
|
|
||||||
// 微机课闲的没事干玩玩 发现私聊会多发一个(一个是本地的, 另一个是发送成功的) 选择一个关掉就好了
|
|
||||||
// 这里我选择服务端不发送回调, 不然多设备同步会吵死
|
|
||||||
// 错了 应该是客户端少发条才对 不然不能多设备同步
|
|
||||||
if (ChatMsgAdapter.target !== localStorage.userName && ChatMsgAdapter.type === "single") {
|
|
||||||
let i = ChatMsgAdapter.isAtBottom()
|
|
||||||
await ChatMsgAdapter.addMsg(localStorage.userName, msg, re.data.time, re.data.msgid)
|
|
||||||
if (i) ChatMsgAdapter.scrollToBottom()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
static async getHistroy(start, limit) {
|
|
||||||
return new Promise(async (res, rej) => {
|
|
||||||
client.emit("user.getSingleChatHistroy", {
|
|
||||||
name: localStorage.userName,
|
|
||||||
target: this.target,
|
|
||||||
limit: limit,
|
|
||||||
accessToken: await User.getAccessToken(),
|
|
||||||
startId: start,
|
|
||||||
}, (re) => {
|
|
||||||
if (re.code !== 0)
|
|
||||||
return mdui.snackbar(re.msg)
|
|
||||||
res(re.data.histroy)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
static async loadMore(limit) {
|
|
||||||
let histroy = await this.getHistroy(this.minMsgId, limit == null ? 13 : limit)
|
|
||||||
|
|
||||||
if (histroy.length == 0)
|
|
||||||
return mdui.snackbar("已经加载完了~")
|
|
||||||
|
|
||||||
let re = this.minMsgId != null
|
|
||||||
this.minMsgId = histroy[0].msgid - 1
|
|
||||||
let sc = 0
|
|
||||||
if (re) histroy = histroy.reverse()
|
|
||||||
for (let index in histroy) {
|
|
||||||
let i = histroy[index]
|
|
||||||
let e = await this.addMsg(i.name, i.msg, i.time, re, i.msgid)
|
|
||||||
// 因为某些因素直接DEBUG到吐血 断点继续都不报错 原因不明
|
|
||||||
sc = sc + (e == null ? 25 : e.get(0).offsetTop)
|
|
||||||
}
|
|
||||||
window.scrollBy({
|
|
||||||
top: sc,
|
|
||||||
behavior: 'smooth'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
static addSystemMsg(m, re) {
|
|
||||||
let e
|
|
||||||
if (re)
|
|
||||||
e = $($.parseHTML(m)).prependTo(viewBinding.pageChatSeesion)
|
|
||||||
else
|
|
||||||
e = $($.parseHTML(m)).appendTo(viewBinding.pageChatSeesion)
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
static isAtBottom() {
|
|
||||||
let elementRect = viewBinding.pageChatSeesion.get(0).getBoundingClientRect()
|
|
||||||
return (elementRect.bottom <= window.innerHeight)
|
|
||||||
}
|
|
||||||
// 不会压栈 只添加消息 返回消息的JQ对象
|
|
||||||
static async addMsg(name, m, t, re, msgid) {
|
|
||||||
|
|
||||||
let nick = await NickCache.getNick(name) // re.data == null ? name : re.data.nick
|
|
||||||
|
|
||||||
let msg = escapeHTML(m)
|
|
||||||
|
|
||||||
let temp
|
|
||||||
if (name === localStorage.userName)
|
|
||||||
temp = `<div class="chat-message-right">
|
|
||||||
<div class="message-content-with-nickname-right">
|
|
||||||
<span class="nickname">` + nick + `</span>
|
|
||||||
<div class="message-content mdui-card" id="msgid_` + msgid + `">
|
|
||||||
<span id="msg-content">` + msg + `</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<img class="avatar" src="` + User.getUserHeadUrl(name) + `" onerror="this.src='default_head.png'" />
|
|
||||||
</div>`
|
|
||||||
else
|
|
||||||
temp = `<div class="chat-message-left">
|
|
||||||
<img class="avatar" src="` + User.getUserHeadUrl(name) + `" onerror="this.src='default_head.png'" />
|
|
||||||
<div class="message-content-with-nickname-left">
|
|
||||||
<span class="nickname">` + nick + `</span>
|
|
||||||
<div class="message-content mdui-card" id="msgid_` + msgid + `">
|
|
||||||
<span id="msg-content">` + msg + `</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>`
|
|
||||||
|
|
||||||
let bn = new Date(t).getMinutes()
|
|
||||||
let e
|
|
||||||
if (re) {
|
|
||||||
this.addSystemMsg(temp, re)
|
|
||||||
if (this.bbn != bn) {
|
|
||||||
e = this.addSystemMsg(`<div class="mdui-center">` + new Date().format(t == null ? Date.parse("1000-1-1 00:00:00") : t, "yyyy年MM月dd日 hh:mm:ss") + `</div>`, re)
|
|
||||||
this.time = bn
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (this.bbn != bn) {
|
|
||||||
e = this.addSystemMsg(`<div class="mdui-center">` + new Date().format(t == null ? Date.parse("1000-1-1 00:00:00") : t, "yyyy年MM月dd日 hh:mm:ss") + `</div>`, re)
|
|
||||||
this.time = bn
|
|
||||||
}
|
|
||||||
this.addSystemMsg(temp, re)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.bbn = new Date(t).getMinutes()
|
|
||||||
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
// 添加消息记录 作用在 UI 和 msgList
|
|
||||||
/* static async addMsgLocal(name, m, t, msgid) {
|
|
||||||
this.msgList.push({
|
|
||||||
name: name,
|
|
||||||
msg: m,
|
|
||||||
msgid: msgid,
|
|
||||||
})
|
|
||||||
|
|
||||||
this.addMsg(name, m, t)
|
|
||||||
} */
|
|
||||||
// 从服务器加载一些聊天记录, limit默认=13
|
|
||||||
static async loadMsgs(limit) {
|
|
||||||
let histroy = await this.getHistroy(this.msgList[0] == null ? null : this.msgList[0].msgid - 1, limit == null ? 13 : limit)
|
|
||||||
this.msgList = histroy
|
|
||||||
}
|
|
||||||
/* static async loadMsgsFromList(lst) {
|
|
||||||
for (let index in lst) {
|
|
||||||
let i = lst[index]
|
|
||||||
await this.addMsg(i.name, i.msg, i.time)
|
|
||||||
}
|
|
||||||
} */
|
|
||||||
static scrollToBottom() {
|
|
||||||
// 吐了啊 原来这样就行了 我何必在子element去整啊
|
|
||||||
window.scrollBy({
|
|
||||||
top: 1145141919810,
|
|
||||||
behavior: 'smooth'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// 从本地加载
|
|
||||||
/*static loadMsgsFromLocal(target) {
|
|
||||||
let data = localStorage["chat_msg_" + target]
|
|
||||||
if (data == null || data === "[]")
|
|
||||||
return []
|
|
||||||
|
|
||||||
return JSON.parse(data)
|
|
||||||
}
|
|
||||||
// 把当前聊天记录储存到本地
|
|
||||||
static saveToLocal() {
|
|
||||||
localStorage["chat_msg_" + this.target] = JSON.stringify(this.msgList)
|
|
||||||
}*/
|
|
||||||
// 为消息设置长按/右键事件
|
|
||||||
static initMsgElementEvents() {
|
|
||||||
let listeners = {}
|
|
||||||
let menu
|
|
||||||
let callback = (e) => {
|
|
||||||
if (menu) menu.close()
|
|
||||||
// 从 span 切到 div
|
|
||||||
if (e.get(0).tagName.toLowerCase() != "div") e = $(e.get(0).parentNode)
|
|
||||||
// 从 消息框 切到 更上层
|
|
||||||
e = $(e.get(0).parentNode)
|
|
||||||
let menuHtml = $.parseHTML(`<ul class="mdui-menu menu-on-message">
|
|
||||||
<li class="mdui-menu-item">
|
|
||||||
<a onclick="copyText(\`` + e.find("#msg-content").text() + `\`)" class="mdui-ripple">复制</a>
|
|
||||||
</li>
|
|
||||||
<li class="mdui-menu-item">
|
|
||||||
<a onclick="mdui.alert('未制作功能', '提示', () => { }, { confirmText: '关闭' })" class="mdui-ripple">转发</a>
|
|
||||||
</li>
|
|
||||||
</ul>`)
|
|
||||||
let $menu = $(menuHtml)
|
|
||||||
e.before($menu)
|
|
||||||
menu = new mdui.Menu(e.get(0), menuHtml, {
|
|
||||||
position: "bottom",
|
|
||||||
align: "right",
|
|
||||||
// covered: true,
|
|
||||||
})
|
|
||||||
$menu.on('closed.mdui.menu', () => {
|
|
||||||
$(menuHtml).remove()
|
|
||||||
})
|
|
||||||
menu.open()
|
|
||||||
}
|
|
||||||
viewBinding.pageChatSeesion.on('contextmenu mousedown mouseup', '.message-content', (e) => {
|
|
||||||
let eventType = e.type
|
|
||||||
let self = $(e.target)
|
|
||||||
|
|
||||||
// 根据事件类型执行不同操作
|
|
||||||
switch (eventType) {
|
|
||||||
case 'contextmenu':
|
|
||||||
e.preventDefault() // 阻止默认行为
|
|
||||||
callback(self)
|
|
||||||
break
|
|
||||||
case 'mousedown':
|
|
||||||
listeners[self + ""] = setTimeout(() => {
|
|
||||||
callback(self)
|
|
||||||
}, 300) // 300颗够吗 应该够吧
|
|
||||||
break
|
|
||||||
case 'mouseup':
|
|
||||||
clearTimeout(listeners[self + ""])
|
|
||||||
listeners[self + ""] = null
|
|
||||||
break
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Hash {
|
|
||||||
static md5(data) {
|
|
||||||
return CryptoJS.MD5(data).toString(CryptoJS.enc.Base64)
|
|
||||||
}
|
|
||||||
static sha256(data) {
|
|
||||||
return CryptoJS.SHA256(data).toString(CryptoJS.enc.Base64)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class User {
|
|
||||||
static myAccessToken
|
|
||||||
// 登录账号 通过回调函数返回刷新令牌
|
|
||||||
static signIn(name, passwd, cb) {
|
|
||||||
client.emit("user.signIn", {
|
|
||||||
name: name,
|
|
||||||
passwd: Hash.sha256(passwd) + Hash.md5(passwd),
|
|
||||||
}, (re) => {
|
|
||||||
if (re.code !== 0)
|
|
||||||
return mdui.snackbar(re.msg)
|
|
||||||
|
|
||||||
cb(re)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
static signUp(name, passwd, cb) {
|
|
||||||
client.emit("user.signUp", {
|
|
||||||
name: name,
|
|
||||||
passwd: Hash.sha256(passwd) + Hash.md5(passwd),
|
|
||||||
}, (re) => {
|
|
||||||
if (re.code !== 0)
|
|
||||||
return mdui.snackbar(re.msg)
|
|
||||||
|
|
||||||
cb(re)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// 为登录对话框编写的
|
|
||||||
static signInWithDialog(name, passwd) {
|
|
||||||
this.signIn(name, passwd, (re) => {
|
|
||||||
localStorage.refreshToken = re.data.refreshToken
|
|
||||||
localStorage.isSignIn = true
|
|
||||||
|
|
||||||
location.reload()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
static async setNick(nick, cb) {
|
|
||||||
client.emit("user.setNick", {
|
|
||||||
name: localStorage.userName,
|
|
||||||
accessToken: await this.getAccessToken(),
|
|
||||||
nick: nick,
|
|
||||||
}, (re) => {
|
|
||||||
if (re.code !== 0)
|
|
||||||
return mdui.snackbar(re.msg)
|
|
||||||
if (cb) cb()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// 获取头像链接
|
|
||||||
static getUserHeadUrl(name) {
|
|
||||||
return client.io.uri + "/users_head/" + name + ".png"
|
|
||||||
}
|
|
||||||
static async getAccessToken(er) {
|
|
||||||
if (this.myAccessToken == null)
|
|
||||||
this.myAccessToken = await new Promise((res) => {
|
|
||||||
client.emit("user.getAccessToken", { name: localStorage.userName, refreshToken: localStorage.refreshToken }, (r) => {
|
|
||||||
if (r.data != null) res(r.data.accessToken)
|
|
||||||
if (er != null) er(r.msg)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
return this.myAccessToken
|
|
||||||
}
|
|
||||||
static uploadHeadImage() {
|
|
||||||
viewBinding.uploadHeadImage.click()
|
|
||||||
}
|
|
||||||
static async uploadHeadImageCallback(self) {
|
|
||||||
let img = self.files[0]
|
|
||||||
client.emit("user.setHeadImage", {
|
|
||||||
name: localStorage.userName,
|
|
||||||
accessToken: await User.getAccessToken(),
|
|
||||||
headImage: img,
|
|
||||||
}, (re) => mdui.snackbar(re.msg))
|
|
||||||
}
|
|
||||||
static auth() {
|
|
||||||
client.emit("user.auth", { name: localStorage.userName, refreshToken: localStorage.refreshToken }, (re) => {
|
|
||||||
if (re.code !== 0) {
|
|
||||||
console.error(re)
|
|
||||||
if (!re.invalid)
|
|
||||||
return mdui.snackbar("验证用户失败!")
|
|
||||||
|
|
||||||
mdui.alert("账号刷新令牌已过期, 请重新登录哦", "提示", () => User.signOutAndReload(), {
|
|
||||||
confirmText: "确定",
|
|
||||||
closeOnConfirm: false,
|
|
||||||
closeOnEsc: false,
|
|
||||||
modal: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
static signOutAndReload() {
|
|
||||||
localStorage.refreshToken = ""
|
|
||||||
localStorage.isSignIn = false
|
|
||||||
|
|
||||||
setTimeout(() => location.reload(), 300)
|
|
||||||
}
|
|
||||||
static registerCallback() {
|
|
||||||
client.on("msg.receive", async (a) => {
|
|
||||||
if (checkEmpty([a.target, a.msg, a.type]))
|
|
||||||
return
|
|
||||||
|
|
||||||
if ((ChatMsgAdapter.target === a.target) && (ChatMsgAdapter.type === a.type)) {
|
|
||||||
let i = ChatMsgAdapter.isAtBottom()
|
|
||||||
await ChatMsgAdapter.addMsg(a.target, a.msg.msg, a.msg.time)
|
|
||||||
if (i) ChatMsgAdapter.scrollToBottom()
|
|
||||||
}
|
|
||||||
|
|
||||||
let n = new 通知().setTitle("新消息 - " + await NickCache.getNick(a.target)).setMessage(a.msg.msg).setIcon(User.getUserHeadUrl(a.target)).show(async () => {
|
|
||||||
await ChatMsgAdapter.switchTo(a.target, a.type)
|
|
||||||
location.replace("#msgid_" + a.msg.msgid)
|
|
||||||
n.close()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
static async openProfileDialog(name) {
|
|
||||||
viewBinding.dialogProfileHead.attr("src", User.getUserHeadUrl(name))
|
|
||||||
viewBinding.dialogProfileNick.text(await NickCache.getNick(name))
|
|
||||||
new mdui.Dialog(viewBinding.dialogProfile).open()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 没有刷新令牌需要重新登录 或者初始化
|
|
||||||
if (!localStorage.refreshToken || localStorage.refreshToken === "")
|
|
||||||
localStorage.isSignIn = false
|
|
||||||
|
|
||||||
let client
|
|
||||||
function setUpClient(server) {
|
|
||||||
if (server && server !== "")
|
|
||||||
client = new io(server, {
|
|
||||||
auth: {
|
|
||||||
name: localStorage.isSignIn === "false" ? null : localStorage.userName
|
|
||||||
}
|
|
||||||
})
|
|
||||||
else
|
|
||||||
client = new io({
|
|
||||||
auth: {
|
|
||||||
name: localStorage.isSignIn === "false" ? null : localStorage.userName
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
client.on("connect", () => {
|
|
||||||
User.auth()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (!localStorage.server || localStorage.server === "")
|
|
||||||
setUpClient()
|
|
||||||
else
|
|
||||||
setUpClient(localStorage.server)
|
|
||||||
|
|
||||||
// 登录到账号
|
|
||||||
let dialogSignIn
|
|
||||||
// 谨防 localStorage 字符串数据大坑
|
|
||||||
if (localStorage.isSignIn === "false")
|
|
||||||
dialogSignIn = new mdui.Dialog(viewBinding.dialogSignIn.get(0), {
|
|
||||||
modal: true,
|
|
||||||
closeOnEsc: false,
|
|
||||||
history: false,
|
|
||||||
}).open()
|
|
||||||
else {
|
|
||||||
(async () => viewBinding.userNick.text(await NickCache.getNick(localStorage.userName)))()
|
|
||||||
let hello
|
|
||||||
let nowHour = new Date().getHours()
|
|
||||||
if (nowHour >= 6 && nowHour <= 11) hello = "早安"
|
|
||||||
else if (nowHour == 12) hello = "中午好"
|
|
||||||
else if (nowHour >= 13 && nowHour <= 18) hello = "下午好"
|
|
||||||
else if (nowHour >= 19 && nowHour < 22) hello = "晚上好"
|
|
||||||
else hello = "晚安"
|
|
||||||
viewBinding.helloText.text(hello)
|
|
||||||
|
|
||||||
viewBinding.userHead.attr("src", User.getUserHeadUrl(localStorage.userName))
|
|
||||||
|
|
||||||
ContactsList.reloadList()
|
|
||||||
|
|
||||||
User.registerCallback()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 感谢AI的力量
|
|
||||||
Stickyfill.add($("*").filter((a, b) => $(b).css('position') === 'sticky'))
|
|
||||||
|
|
||||||
ChatMsgAdapter.initMsgElementEvents()
|
|
||||||
36
ling_chair_http/manager.js
Normal file
36
ling_chair_http/manager.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* ©2024 满月叶
|
||||||
|
* Github: MoonLeeeaf
|
||||||
|
* 资源类
|
||||||
|
*/
|
||||||
|
|
||||||
|
const viewBinding = NData.mount($("#app").get(0))
|
||||||
|
|
||||||
|
let client
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化客户端
|
||||||
|
* @param {String} 服务器地址
|
||||||
|
*/
|
||||||
|
function setUpClient(server) {
|
||||||
|
if (server && server !== "")
|
||||||
|
client = new io(server, {
|
||||||
|
auth: {
|
||||||
|
name: localStorage.isSignIn === "false" ? null : localStorage.userName
|
||||||
|
}
|
||||||
|
})
|
||||||
|
else
|
||||||
|
client = new io({
|
||||||
|
auth: {
|
||||||
|
name: localStorage.isSignIn === "false" ? null : localStorage.userName
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
client.on("connect", () => {
|
||||||
|
User.auth()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
window.viewBinding = viewBinding
|
||||||
|
window.setUpClient = setUpClient
|
||||||
|
window.client = client
|
||||||
@@ -6,15 +6,15 @@
|
|||||||
|
|
||||||
/* 美化UI */
|
/* 美化UI */
|
||||||
|
|
||||||
|
/* 恢复系统字体 */
|
||||||
body {
|
body {
|
||||||
font-family: -apple-system, system-ui, -webkit-system-font;
|
font-family: -apple-system, system-ui, -webkit-system-font;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 圆角化 */
|
||||||
.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,13 +34,25 @@ body {
|
|||||||
height: 40px;
|
height: 40px;
|
||||||
border-radius: 40px;
|
border-radius: 40px;
|
||||||
}
|
}
|
||||||
|
.mdui-select-open {
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
@media screen and (min-width: 768px) {
|
||||||
|
.mdui-snackbar {
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* 配色方案 */
|
/* 配色方案 */
|
||||||
|
|
||||||
.mdui-theme-color-auto {
|
.mdui-list-item-avatar {
|
||||||
background-color: #fff;
|
background-color: rgba(0, 0, 0, 0) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 背景底色 */
|
||||||
|
.mdui-theme-color-auto {
|
||||||
|
background-color: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
.mdui-theme-color-auto {
|
.mdui-theme-color-auto {
|
||||||
background-color: #303030;
|
background-color: #303030;
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
71
ling_chair_http/ui.js
Normal file
71
ling_chair_http/ui.js
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* ©2024 满月叶
|
||||||
|
* Github: MoonLeeeaf
|
||||||
|
* 界面逻辑
|
||||||
|
*/
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: "res/config.json",
|
||||||
|
dataType: "json",
|
||||||
|
success: (c) => {
|
||||||
|
viewBinding.appTitle.text(c.appTitle)
|
||||||
|
if (!c.canChangeServer) {
|
||||||
|
viewBinding.dialogSignInServerLabel.hide()
|
||||||
|
viewBinding.drawerChangeServer.hide()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// 关于页面
|
||||||
|
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(() => {
|
||||||
|
mdui.prompt('输入服务器地址...(为空则使用当前页面地址)', (value) => {
|
||||||
|
localStorage.server = value
|
||||||
|
mdui.snackbar("更新成功, 刷新页面生效")
|
||||||
|
}, () => { }, {
|
||||||
|
confirmText: "确定",
|
||||||
|
cancelText: "取消"
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
viewBinding.drawerSignOut.click(() => {
|
||||||
|
mdui.confirm('确定要登出账号吗', () => {
|
||||||
|
User.signOutAndReload()
|
||||||
|
}, () => { }, {
|
||||||
|
confirmText: "确定",
|
||||||
|
cancelText: "取消"
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
viewBinding.sendMsg.click((a) => {
|
||||||
|
let text = viewBinding.inputMsg.val()
|
||||||
|
if (text.trim() !== "")
|
||||||
|
ChatMsgAdapter.send(text)
|
||||||
|
})
|
||||||
|
|
||||||
|
viewBinding.inputMsg.keydown((e) => {
|
||||||
|
if (e.ctrlKey && e.keyCode === 13)
|
||||||
|
viewBinding.sendMsg.click()
|
||||||
|
})
|
||||||
|
|
||||||
|
viewBinding.dialogSignInPasswd.keydown((e) => {
|
||||||
|
if (e.keyCode === 13)
|
||||||
|
viewBinding.dialogSignInEnter.click()
|
||||||
|
})
|
||||||
|
|
||||||
|
viewBinding.switchNotifications.click((a) => {
|
||||||
|
if ((localStorage.useNotifications == "true" || localStorage.useNotifications != null) && localStorage.useNotifications != "false") {
|
||||||
|
localStorage.useNotifications = "false"
|
||||||
|
viewBinding.switchNotificationsIcon.text("notifications_off")
|
||||||
|
} else {
|
||||||
|
localStorage.useNotifications = "true"
|
||||||
|
viewBinding.switchNotificationsIcon.text("notifications")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (localStorage.useNotifications == "true")
|
||||||
|
viewBinding.switchNotificationsIcon.text("notifications")
|
||||||
|
|
||||||
|
viewBinding.inputMsg.blur(() => {
|
||||||
|
window.initInputResizerResize()
|
||||||
|
})
|
||||||
208
ling_chair_http/utils.js
Normal file
208
ling_chair_http/utils.js
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
/*
|
||||||
|
* ©2024 满月叶
|
||||||
|
* Github: MoonLeeeaf
|
||||||
|
* 辅助添加
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 2024.5.28 睡着了
|
||||||
|
const sleep = (t) => new Promise((res) => setTimeout(res, t))
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (UrlArgs.get("debug")) {
|
||||||
|
let script = document.createElement('script')
|
||||||
|
script.src = "//cdn.jsdelivr.net/npm/eruda"
|
||||||
|
document.body.appendChild(script)
|
||||||
|
script.onload = () => eruda.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 经常会因为这个指定ID为位置导致一些莫名BUG
|
||||||
|
if (location.href.includes("#")) location.replace(location.href.substring(0, location.href.indexOf("#")))
|
||||||
|
|
||||||
|
const mdui_snackbar = mdui.snackbar
|
||||||
|
mdui.snackbar = (m) => {
|
||||||
|
let t = m
|
||||||
|
if (m instanceof Object)
|
||||||
|
t = JSON.stringify(m)
|
||||||
|
mdui_snackbar(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkEmpty = (i) => {
|
||||||
|
if (i instanceof Array) {
|
||||||
|
for (let k of i) {
|
||||||
|
if (checkEmpty(k)) return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (i == null) || ("" === i) || (0 === i)
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeHTML(str) {
|
||||||
|
return str.replace(/[<>&"']/g, function (match) {
|
||||||
|
switch (match) {
|
||||||
|
case '<':
|
||||||
|
return '<'
|
||||||
|
case '>':
|
||||||
|
return '>'
|
||||||
|
case '&':
|
||||||
|
return '&'
|
||||||
|
case '"':
|
||||||
|
return '"'
|
||||||
|
case "'":
|
||||||
|
return '''
|
||||||
|
default:
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
class NData {
|
||||||
|
/**
|
||||||
|
* 获取 MD5sum
|
||||||
|
* @param {String} 数据
|
||||||
|
*/
|
||||||
|
static mount(node) {
|
||||||
|
// 便捷获得指定组件
|
||||||
|
let es = node.querySelectorAll("[n-id]")
|
||||||
|
let ls = {}
|
||||||
|
es.forEach((i) => ls[$(i).attr("n-id")] = $(i))
|
||||||
|
|
||||||
|
// input 组件与 localStorage 绑定
|
||||||
|
es = node.querySelectorAll("[n-input-ls]")
|
||||||
|
es.forEach((e) => {
|
||||||
|
let j = $(e)
|
||||||
|
j.val(localStorage.getItem(j.attr("n-input-ls")))
|
||||||
|
j.blur(() => localStorage.setItem(j.attr("n-input-ls"), j.val()))
|
||||||
|
})
|
||||||
|
return ls
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://www.runoob.com/w3cnote/javascript-copy-clipboard.html
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 复制文字
|
||||||
|
* @param {String} 欲复制的文本
|
||||||
|
*/
|
||||||
|
function copyText(t) {
|
||||||
|
let btn = $("[n-id=textCopierBtn]")
|
||||||
|
btn.attr("data-clipboard-text", t)
|
||||||
|
new ClipboardJS(btn.get(0)).on('success', (e) => {
|
||||||
|
e.clearSelection()
|
||||||
|
})
|
||||||
|
btn.click()
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://zhuanlan.zhihu.com/p/162910462
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化日期
|
||||||
|
* @param {int} 时间戳
|
||||||
|
* @param {String} 欲格式化的文本
|
||||||
|
* @returns {String} 格式后的文本
|
||||||
|
*/
|
||||||
|
Date.prototype.format = function (tms, format) {
|
||||||
|
let tmd = new Date(tms)
|
||||||
|
/*
|
||||||
|
* 例子: format="YYYY-MM-dd hh:mm:ss";
|
||||||
|
*/
|
||||||
|
var o = {
|
||||||
|
"M+": tmd.getMonth() + 1, // month
|
||||||
|
"d+": tmd.getDate(), // day
|
||||||
|
"h+": tmd.getHours(), // hour
|
||||||
|
"m+": tmd.getMinutes(), // minute
|
||||||
|
"s+": tmd.getSeconds(), // second
|
||||||
|
"q+": Math.floor((tmd.getMonth() + 3) / 3), // quarter
|
||||||
|
"S": tmd.getMilliseconds()
|
||||||
|
// millisecond
|
||||||
|
}
|
||||||
|
if (/(y+)/.test(format)) {
|
||||||
|
format = format.replace(RegExp.$1, (tmd.getFullYear() + "")
|
||||||
|
.substr(4 - RegExp.$1.length));
|
||||||
|
}
|
||||||
|
for (var k in o) {
|
||||||
|
if (new RegExp("(" + k + ")").test(format)) {
|
||||||
|
format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k]
|
||||||
|
: ("00" + o[k]).substr(("" + o[k]).length));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return format;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 既然已经有 Notification 了, 那用回中文也不过分吧 :)
|
||||||
|
class 通知 {
|
||||||
|
constructor() {
|
||||||
|
this.args = {}
|
||||||
|
this.title = ""
|
||||||
|
}
|
||||||
|
static checkAvailable() {
|
||||||
|
return ("Notification" in window)
|
||||||
|
}
|
||||||
|
static async request() {
|
||||||
|
if (!this.checkAvailable()) return false
|
||||||
|
return (await Notification.requestPermission())
|
||||||
|
}
|
||||||
|
setId(id) {
|
||||||
|
this.args.tag = id
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
setTitle(t) {
|
||||||
|
this.title = t
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
setMessage(m) {
|
||||||
|
this.args.body = m
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
setIcon(i) {
|
||||||
|
this.args.icon = i
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
setImage(i) {
|
||||||
|
this.args.image = i
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
setData(data) {
|
||||||
|
this.args.data = data
|
||||||
|
}
|
||||||
|
show(onclick/*, onclose*/) {
|
||||||
|
if (!通知.checkAvailable()) return
|
||||||
|
if (localStorage.useNotifications !== "true") return
|
||||||
|
let n = new Notification(this.title, this.args)
|
||||||
|
n.onclick = onclick == null ? () => n.close() : (n) => onclick(n)
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Hash {
|
||||||
|
/**
|
||||||
|
* 获取 MD5sum
|
||||||
|
* @param {String} 数据
|
||||||
|
* @returns {String} Hex化的哈希值
|
||||||
|
*/
|
||||||
|
static md5(data) {
|
||||||
|
return CryptoJS.MD5(data).toString(CryptoJS.enc.Hex)
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取 SHA256sum
|
||||||
|
* @param {String} 数据
|
||||||
|
* @returns {String} Hex化的哈希值
|
||||||
|
*/
|
||||||
|
static sha256(data) {
|
||||||
|
return CryptoJS.SHA256(data).toString(CryptoJS.enc.Hex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.copyText = copyText
|
||||||
|
window.NData = NData
|
||||||
|
window.escapeHTML = escapeHTML
|
||||||
|
window.isMobile = isMobile
|
||||||
|
window.checkEmpty = checkEmpty
|
||||||
|
window.sleep = sleep
|
||||||
|
window.Hash = Hash
|
||||||
|
window.通知 = 通知
|
||||||
797
package-lock.json
generated
797
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "lingchair",
|
"name": "lingchair",
|
||||||
"description": "A simple, lightweight and powerful Instant Messaging application.",
|
"description": "A simple, lightweight and powerful Instant Messaging application.",
|
||||||
"version": "0.6.1",
|
"version": "0.7.0",
|
||||||
"license": "Apache License 2.0",
|
"license": "Apache License 2.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "MoonLeeeaf",
|
"name": "MoonLeeeaf",
|
||||||
@@ -11,7 +11,8 @@
|
|||||||
"start": "node ./server_src/main.js"
|
"start": "node ./server_src/main.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"mime": "^4.0.1",
|
"express": "^4.19.2",
|
||||||
|
"sharp": "^0.33.4",
|
||||||
"socket.io": "^4.7.5"
|
"socket.io": "^4.7.5"
|
||||||
},
|
},
|
||||||
"type": "commonjs"
|
"type": "commonjs"
|
||||||
|
|||||||
16
readme.md
16
readme.md
@@ -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)
|
||||||
|
|
||||||
### 鸣谢
|
### 鸣谢
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const users = require("./api-users")
|
|||||||
let getSameHashedValue = (a, b) => {
|
let getSameHashedValue = (a, b) => {
|
||||||
let _a = [hash.md5(a) + hash.sha256(a), hash.md5(b) + hash.sha256(b)].sort()
|
let _a = [hash.md5(a) + hash.sha256(a), hash.md5(b) + hash.sha256(b)].sort()
|
||||||
let [_1, _2] = _a
|
let [_1, _2] = _a
|
||||||
return hash.sha256hex(hash.sha256hex(_1) + hash.sha256hex(_2))
|
return hash.sha256(hash.sha256(_1) + hash.sha256(_2))
|
||||||
}
|
}
|
||||||
|
|
||||||
let getSingleChatDir = (a, b) => {
|
let getSingleChatDir = (a, b) => {
|
||||||
|
|||||||
@@ -7,10 +7,8 @@
|
|||||||
const crypto = require("crypto")
|
const crypto = require("crypto")
|
||||||
|
|
||||||
let apis = {
|
let apis = {
|
||||||
sha256: (data) => crypto.createHash("sha256").update(data).digest("base64"),
|
sha256: (data) => crypto.createHash("sha256").update(data).digest("hex"),
|
||||||
md5: (data) => crypto.createHash("md5").update(data).digest("base64"),
|
md5: (data) => crypto.createHash("md5").update(data).digest("hex"),
|
||||||
sha256hex: (data) => crypto.createHash("sha256").update(data).digest("hex"),
|
|
||||||
md5hex: (data) => crypto.createHash("md5").update(data).digest("hex"),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = apis
|
module.exports = apis
|
||||||
|
|||||||
@@ -19,10 +19,10 @@ const vals = require("./val")
|
|||||||
const color = require("./color")
|
const color = require("./color")
|
||||||
|
|
||||||
//定义 Http 服务器回调
|
//定义 Http 服务器回调
|
||||||
let httpServerCallback = require("./httpApi")
|
let httpServerCallback = require("./http-api")
|
||||||
|
|
||||||
// 定义 Socket.io 服务器回调
|
// 定义 Socket.io 服务器回调
|
||||||
let wsServerCallback = require("./wsApi")
|
let wsServerCallback = require("./ws-api")
|
||||||
|
|
||||||
let httpServer
|
let httpServer
|
||||||
if (vals.LINGCHAIR_SERVER_CONFIG.useHttps)
|
if (vals.LINGCHAIR_SERVER_CONFIG.useHttps)
|
||||||
|
|||||||
@@ -48,7 +48,8 @@ if (!io.exists(vals.LINGCHAIR_SERVER_CONFIG_FILE)) io.open(vals.LINGCHAIR_SERVER
|
|||||||
cert: "",
|
cert: "",
|
||||||
},
|
},
|
||||||
})).close()
|
})).close()
|
||||||
if (!io.exists(vals.LINGCHAIR_USERS_COUNT_FILE)) io.open(vals.LINGCHAIR_USERS_COUNT_FILE, "w").write("10000").close()
|
if (!io.exists(vals.LINGCHAIR_USERS_COUNT_FILE))
|
||||||
|
io.open(vals.LINGCHAIR_USERS_COUNT_FILE, "w").write("10000").close()
|
||||||
|
|
||||||
// 加载服务端配置文件
|
// 加载服务端配置文件
|
||||||
vals.LINGCHAIR_SERVER_CONFIG = JSON.parse(io.open(vals.LINGCHAIR_SERVER_CONFIG_FILE, "r").read("*a"))
|
vals.LINGCHAIR_SERVER_CONFIG = JSON.parse(io.open(vals.LINGCHAIR_SERVER_CONFIG_FILE, "r").read("*a"))
|
||||||
|
|||||||
@@ -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 })
|
||||||
Reference in New Issue
Block a user