8 Commits

Author SHA1 Message Date
MoonLeeeaf
1a5afc8ad0 chore: rename symbols, add "原文" menu 2024-06-14 19:31:42 +08:00
MoonLeeeaf
4bdfad340f fix: image 2024-06-12 22:07:50 +08:00
MoonLeeeaf
6ac1b460bb feat: Markdown Code Dialog 2024-06-12 21:48:39 +08:00
MoonLeeeaf
29e224f87a test 2024-06-12 21:05:24 +08:00
MoonLeeeaf
a39973bb5c feat: markdown support 2024-06-11 22:03:41 +08:00
MoonLeeeaf
47afacbba3 chore: make limit happy 2024-06-01 14:33:40 +08:00
MoonLeeeaf
3a4d733c13 chore: 修改哈希输出(Base64改为Hex) 2024-06-01 14:33:26 +08:00
MoonLeeeaf
89263e6e2a chore: 命名 2024-06-01 14:09:45 +08:00
8 changed files with 331 additions and 108 deletions

View File

@@ -82,4 +82,10 @@
width: 50px; width: 50px;
height: 50px; height: 50px;
border-radius: 50%; border-radius: 50%;
} }
.message-image {
max-width: 40%;
max-height: 40%;
border-radius: 15px;
}

View 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,
})

View File

@@ -3,9 +3,19 @@
* Github: MoonLeeeaf * Github: MoonLeeeaf
* 业务逻辑 * 业务逻辑
*/ */
class User {
// ================================
// 当前用户
// ================================
class CurrentUser {
static myAccessToken static myAccessToken
// 登录账号 通过回调函数返回刷新令牌 /**
* 登录账号
* @param {String} name
* @param {String} passwd
* @param {Function} callback
*/
static signIn(name, passwd, cb) { static signIn(name, passwd, cb) {
client.emit("user.signIn", { client.emit("user.signIn", {
name: name, name: name,
@@ -17,6 +27,12 @@ class User {
cb(re) cb(re)
}) })
} }
/**
* 注册账号
* @param {String} name
* @param {String} passwd
* @param {Function} callback
*/
static signUp(name, passwd, cb) { static signUp(name, passwd, cb) {
client.emit("user.signUp", { client.emit("user.signUp", {
name: name, name: name,
@@ -28,7 +44,11 @@ class User {
cb(re) cb(re)
}) })
} }
// 为登录对话框编写的 /**
* 登录对话框中的登录逻辑
* @param {String} name
* @param {String} passwd
*/
static signInWithDialog(name, passwd) { static signInWithDialog(name, passwd) {
this.signIn(name, passwd, (re) => { this.signIn(name, passwd, (re) => {
localStorage.refreshToken = re.data.refreshToken localStorage.refreshToken = re.data.refreshToken
@@ -37,6 +57,11 @@ class User {
location.reload() location.reload()
}) })
} }
/**
* 设置昵称
* @param {String} nick
* @param {Function} callback
*/
static async setNick(nick, cb) { static async setNick(nick, cb) {
client.emit("user.setNick", { client.emit("user.setNick", {
name: localStorage.userName, name: localStorage.userName,
@@ -48,10 +73,19 @@ class User {
if (cb) cb() if (cb) cb()
}) })
} }
// 获取头像链接 /**
* 获取用户头像的链接
* @param {String} name
* @returns {String} headImageUrl
*/
static getUserHeadUrl(name) { static getUserHeadUrl(name) {
return client.io.uri + "/users_head/" + name + ".png" return client.io.uri + "/users_head/" + name + ".png"
} }
/**
* 获取访问密钥
* @param {String} name
* @returns {Promise<String>} accessToken
*/
static async getAccessToken(er) { static async getAccessToken(er) {
if (this.myAccessToken == null) if (this.myAccessToken == null)
this.myAccessToken = await new Promise((res) => { this.myAccessToken = await new Promise((res) => {
@@ -62,17 +96,27 @@ class User {
}) })
return this.myAccessToken return this.myAccessToken
} }
/**
* 请求上传头像
*/
static uploadHeadImage() { static uploadHeadImage() {
viewBinding.uploadHeadImage.click() viewBinding.uploadHeadImage.click()
} }
/**
* 上传头像回调事件
* @param {Element} element
*/
static async uploadHeadImageCallback(self) { static async uploadHeadImageCallback(self) {
let img = self.files[0] let img = self.files[0]
client.emit("user.setHeadImage", { client.emit("user.setHeadImage", {
name: localStorage.userName, name: localStorage.userName,
accessToken: await User.getAccessToken(), accessToken: await CurrentUser.getAccessToken(),
headImage: img, headImage: img,
}, (re) => mdui.snackbar(re.msg)) }, (re) => mdui.snackbar(re.msg))
} }
/**
* 验证用户
*/
static auth() { static auth() {
client.emit("user.auth", { name: localStorage.userName, refreshToken: localStorage.refreshToken }, (re) => { client.emit("user.auth", { name: localStorage.userName, refreshToken: localStorage.refreshToken }, (re) => {
if (re.code !== 0) { if (re.code !== 0) {
@@ -80,7 +124,7 @@ class User {
if (!re.invalid) if (!re.invalid)
return mdui.snackbar("验证用户失败!") return mdui.snackbar("验证用户失败!")
mdui.alert("账号刷新令牌已过期, 请重新登录哦", "提示", () => User.signOutAndReload(), { mdui.alert("账号刷新令牌已过期, 请重新登录哦", "提示", () => CurrentUser.signOutAndReload(), {
confirmText: "确定", confirmText: "确定",
closeOnConfirm: false, closeOnConfirm: false,
closeOnEsc: false, closeOnEsc: false,
@@ -89,12 +133,18 @@ class User {
} }
}) })
} }
/**
* 登出并重载页面
*/
static signOutAndReload() { static signOutAndReload() {
localStorage.refreshToken = "" localStorage.refreshToken = ""
localStorage.isSignIn = false localStorage.isSignIn = false
setTimeout(() => location.reload(), 300) setTimeout(() => location.reload(), 300)
} }
/**
* 注册客户端回调事件
*/
static registerCallback() { static registerCallback() {
client.on("msg.receive", async (a) => { client.on("msg.receive", async (a) => {
if (checkEmpty([a.target, a.msg, a.type])) if (checkEmpty([a.target, a.msg, a.type]))
@@ -107,7 +157,7 @@ class User {
} }
if (ChatMsgAdapter.target !== localStorage.userName) { if (ChatMsgAdapter.target !== localStorage.userName) {
let n = new 通知().setTitle("" + await NickCache.getNick(a.target)).setMessage(a.msg.msg).setIcon(User.getUserHeadUrl(a.target)).show(async () => { 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) await ChatMsgAdapter.switchTo(a.target, a.type)
location.replace("#msgid_" + a.msg.msgid) location.replace("#msgid_" + a.msg.msgid)
n.close() n.close()
@@ -115,15 +165,28 @@ class User {
} }
}) })
} }
/**
* 打开资料卡
* @param {String} name
*/
static async openProfileDialog(name) { static async openProfileDialog(name) {
viewBinding.dialogProfileHead.attr("src", User.getUserHeadUrl(name)) viewBinding.dialogProfileHead.attr("src", CurrentUser.getUserHeadUrl(name))
viewBinding.dialogProfileNick.text(await NickCache.getNick(name)) viewBinding.dialogProfileNick.text(await NickCache.getNick(name))
new mdui.Dialog(viewBinding.dialogProfile).open() new mdui.Dialog(viewBinding.dialogProfile).open()
} }
} }
// ================================
// 昵称缓存
// ================================
class NickCache { class NickCache {
static data = {} static data = {}
/**
* 获取昵称
* @param {String} name
* @returns {String} nick
*/
static async getNick(name) { static async getNick(name) {
return await new Promise((res, rej) => { return await new Promise((res, rej) => {
// 这个this别摆着不放啊 不然两下就会去世 // 这个this别摆着不放啊 不然两下就会去世
@@ -142,10 +205,13 @@ class NickCache {
} }
class ContactsList { class ContactsList {
/**
* 重载联系人列表
*/
static async reloadList() { static async reloadList() {
client.emit("user.getFriends", { client.emit("user.getFriends", {
name: localStorage.userName, name: localStorage.userName,
accessToken: await User.getAccessToken(), accessToken: await CurrentUser.getAccessToken(),
}, async (re) => { }, async (re) => {
if (re.code !== 0) if (re.code !== 0)
return mdui.snackbar(re.msg) return mdui.snackbar(re.msg)
@@ -155,19 +221,25 @@ class ContactsList {
for (let index in ls) { for (let index in ls) {
let name = ls[index] let name = ls[index]
let dick = await NickCache.getNick(name) 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="` + User.getUserHeadUrl(name) + `" onerror="this.src='res/default_head.png'" /></div><div class="mdui-list-item-content">` + dick + `</div></li>`)).appendTo(viewBinding.contactsList).click(() => { $($.parseHTML(`<li class="mdui-list-item mdui-ripple" 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") ChatMsgAdapter.switchTo(name, "single")
}) })
} }
}) })
} }
// 添加联系人,好友或者群聊 /**
* 添加联系人/群峦
* @param {String} nameOrId
*/
static add(name, type) { static add(name, type) {
if (type == "single") { if (type == "single") {
} }
} }
/**
* 打开添加联系人的对话框
*/
static openAddDialog() { static openAddDialog() {
new mdui.Dialog(viewBinding.dialogNewContact.get(0)).open() new mdui.Dialog(viewBinding.dialogNewContact.get(0)).open()
} }
@@ -180,6 +252,11 @@ class ChatPage {
constructor(name, type) { constructor(name, type) {
} }
/**
* 切换到某一个聊天对象
* @param {String} name
* @param {String} type
*/
static switchTo(name, type) { static switchTo(name, type) {
if (!this.cached[name]) if (!this.cached[name])
this.cached[name] = new ChatPage(name, type) this.cached[name] = new ChatPage(name, type)
@@ -191,9 +268,13 @@ class ChatMsgAdapter {
static target static target
static minMsgId static minMsgId
static time static time
static bbn static minutesCache
static resizeDick static resizeDick
// 切换聊天对象 /**
* 切换到某一个聊天对象
* @param {String} name
* @param {String} type
*/
static async switchTo(name, type) { static async switchTo(name, type) {
viewBinding.tabChatSeesion.show() viewBinding.tabChatSeesion.show()
viewBinding.tabChatSeesion.text(await NickCache.getNick(name)) viewBinding.tabChatSeesion.text(await NickCache.getNick(name))
@@ -208,13 +289,16 @@ class ChatMsgAdapter {
await this.loadMore() await this.loadMore()
this.scrollToBottom() this.scrollToBottom()
} }
// 发送消息 /**
* 发送消息
* @param {String} msg
*/
static async send(msg) { static async send(msg) {
client.emit("user.sendSingleMsg", { client.emit("user.sendSingleMsg", {
name: localStorage.userName, name: localStorage.userName,
target: this.target, target: this.target,
msg: msg, msg: msg,
accessToken: await User.getAccessToken(), accessToken: await CurrentUser.getAccessToken(),
}, async (re) => { }, async (re) => {
if (re.code !== 0) if (re.code !== 0)
return mdui.snackbar(re.msg) return mdui.snackbar(re.msg)
@@ -231,13 +315,18 @@ class ChatMsgAdapter {
} }
}) })
} }
/**
* 获取聊天消息记录
* @param {int} 起始点
* @param {int} 获取数量
*/
static async getHistroy(start, limit) { static async getHistroy(start, limit) {
return new Promise(async (res, rej) => { return new Promise(async (res, rej) => {
client.emit("user.getSingleChatHistroy", { client.emit("user.getSingleChatHistroy", {
name: localStorage.userName, name: localStorage.userName,
target: this.target, target: this.target,
limit: limit, limit: limit,
accessToken: await User.getAccessToken(), accessToken: await CurrentUser.getAccessToken(),
startId: start, startId: start,
}, (re) => { }, (re) => {
if (re.code !== 0) if (re.code !== 0)
@@ -246,6 +335,10 @@ class ChatMsgAdapter {
}) })
}) })
} }
/**
* 加载更多聊天记录
* @param {int}} 加载数量
*/
static async loadMore(limit) { static async loadMore(limit) {
let histroy = await this.getHistroy(this.minMsgId, limit == null ? 13 : limit) let histroy = await this.getHistroy(this.minMsgId, limit == null ? 13 : limit)
@@ -267,6 +360,12 @@ class ChatMsgAdapter {
behavior: 'smooth' behavior: 'smooth'
}) })
} }
/**
* 添加系统消息
* @param {String} 消息
* @param {String} 是否加到顶部
* @returns {jQuery} 消息元素
*/
static addSystemMsg(m, re) { static addSystemMsg(m, re) {
let e let e
if (re) if (re)
@@ -277,65 +376,93 @@ class ChatMsgAdapter {
e = $($.parseHTML(m)).appendTo(viewBinding.pageChatSeesion) e = $($.parseHTML(m)).appendTo(viewBinding.pageChatSeesion)
return e return e
} }
/**
* 是否在底部
* @returns {Boolean} 是否在底部
*/
static isAtBottom() { static isAtBottom() {
let elementRect = viewBinding.pageChatSeesion.get(0).getBoundingClientRect() let elementRect = viewBinding.pageChatSeesion.get(0).getBoundingClientRect()
return (elementRect.bottom <= window.innerHeight) return (elementRect.bottom <= window.innerHeight)
} }
// 添加消息 返回消息的JQ对象 // 添加消息 返回消息的JQ对象
// name: 用户id m: 消息 t: 时间戳 re: 默认加到尾部 msgid: 消息id // name: 用户id m: 消息 t: 时间戳 re: 默认加到尾部 msgid: 消息id
static async addMsg(name, m, t, re, msgid) { /**
* 添加聊天记录
* @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 nick = await NickCache.getNick(name) // re.data == null ? name : re.data.nick
let msg = escapeHTML(m) let msg
try {
msg = await marked.parse(preMsg)
} catch(error) {
console.log("解析消息失败: " + error)
msg = escapeHTML(preMsg)
}
let temp let temp
if (name === localStorage.userName) if (name === localStorage.userName)
temp = `<div class="chat-message-right"> temp = `<div class="chat-message-right">
<div class="message-content-with-nickname-right"> <div class="message-content-with-nickname-right">
<span class="nickname">` + nick + `</span> <span class="nickname">${ nick }</span>
<div class="message-content mdui-card" id="msgid_` + msgid + `"> <div class="message-content mdui-card" tag="msg-card" id="msgid_${ msgid }">
<span id="msg-content">` + msg + `</span> <span id="msg-content">${ msg }</span>
<pre class="mdui-hidden" id="raw-msg-content">${ preMsg }</pre>
</div> </div>
</div> </div>
<img class="avatar" src="` + User.getUserHeadUrl(name) + `" onerror="this.src='res/default_head.png'" /> <img class="avatar" src="${ CurrentUser.getUserHeadUrl(name) }" onerror="this.src='res/default_head.png'" />
</div>` </div>`
else else
temp = `<div class="chat-message-left"> temp = `<div class="chat-message-left">
<img class="avatar" src="` + User.getUserHeadUrl(name) + `" onerror="this.src='res/default_head.png'" /> <img class="avatar" src="${ CurrentUser.getUserHeadUrl(name) }" onerror="this.src='res/default_head.png'" />
<div class="message-content-with-nickname-left"> <div class="message-content-with-nickname-left">
<span class="nickname">` + nick + `</span> <span class="nickname">${ nick }</span>
<div class="message-content mdui-card" id="msgid_` + msgid + `"> <div class="message-content mdui-card" tag="msg-card" id="msgid_${ msgid }">
<span id="msg-content">` + msg + `</span> <span id="msg-content">${ msg }</span>
<pre class="mdui-hidden" id="raw-msg-content">${ preMsg }</pre>
</div> </div>
</div> </div>
</div>` </div>`
let bn = new Date(t).getMinutes() let nowMinutes = new Date(time).getMinutes()
let e let msgElement
if (re) { if (addToTop) {
this.addSystemMsg(temp, re) this.addSystemMsg(temp, addToTop)
if (this.bbn != bn) { if (this.minutesCache != nowMinutes) {
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) 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 = bn this.time = nowMinutes
} }
} else { } else {
if (this.bbn != bn) { if (this.minutesCache != nowMinutes) {
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) 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 = bn this.time = nowMinutes
} }
this.addSystemMsg(temp, re) this.addSystemMsg(temp, addToTop)
} }
this.bbn = new Date(t).getMinutes() this.minutesCache = new Date(time).getMinutes()
return e return msgElement
} }
// 从服务器加载一些聊天记录, limit默认=13 /**
* 从服务器加载一些聊天记录
* @param {int} 数量
*/
static async loadMsgs(limit) { static async loadMsgs(limit) {
let histroy = await this.getHistroy(this.msgList[0] == null ? null : this.msgList[0].msgid - 1, limit == null ? 13 : limit) let histroy = await this.getHistroy(this.msgList[0] == null ? null : this.msgList[0].msgid - 1, limit == null ? 13 : limit)
this.msgList = histroy this.msgList = histroy
} }
/**
* 滑到底部
*/
static scrollToBottom() { static scrollToBottom() {
// 吐了啊 原来这样就行了 我何必在子element去整啊 // 吐了啊 原来这样就行了 我何必在子element去整啊
viewBinding.chatPager.get(0).scrollBy({ viewBinding.chatPager.get(0).scrollBy({
@@ -343,7 +470,9 @@ class ChatMsgAdapter {
behavior: 'smooth' behavior: 'smooth'
}) })
} }
// 自动调整使输入框置底 CSS真tm靠不住啊 /**
* 初始化输入框位置调整器
*/
static initInputResizer() { static initInputResizer() {
// 实验表面移动端切出输入法时会触发1-2次resize事件 // 实验表面移动端切出输入法时会触发1-2次resize事件
// 可以利用这个特性来实现自动滚动文本 // 可以利用这个特性来实现自动滚动文本
@@ -365,19 +494,25 @@ class ChatMsgAdapter {
window.addEventListener("resize", resize) window.addEventListener("resize", resize)
resize() resize()
} }
// 为消息设置长按/右键事件 /**
* 初始化消息框右击事件
*/
static initMsgElementEvents() { static initMsgElementEvents() {
let listeners = {} let listeners = {}
let menu let menu
let callback = (e) => { let callback = (e) => {
if (menu) menu.close() if (menu) menu.close()
// 从 span 切到 div // 切到 div.message-content
if (e.get(0).tagName.toLowerCase() != "div") e = $(e.get(0).parentNode) let ele = e.get(0)
// 从 消息框 切到 更上层 while ($(ele).attr("tag") != "msg-card")
e = $(e.get(0).parentNode) ele = ele.parentNode
e = $(ele)
let menuHtml = $.parseHTML(`<ul class="mdui-menu menu-on-message"> let menuHtml = $.parseHTML(`<ul class="mdui-menu menu-on-message">
<li class="mdui-menu-item"> <li class="mdui-menu-item">
<a onclick="copyText(\`` + e.find("#msg-content").text() + `\`)" class="mdui-ripple">复制</a> <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>
<li class="mdui-menu-item"> <li class="mdui-menu-item">
<a onclick="mdui.alert('未制作功能', '提示', () => { }, { confirmText: '关闭' })" class="mdui-ripple">转发</a> <a onclick="mdui.alert('未制作功能', '提示', () => { }, { confirmText: '关闭' })" class="mdui-ripple">转发</a>
@@ -421,13 +556,16 @@ class ChatMsgAdapter {
} }
} }
/**
* 刷新联系人列表以及昵称缓存
*/
function refreshAll() { function refreshAll() {
ContactsList.reloadList() ContactsList.reloadList()
delete NickCache.data delete NickCache.data
NickCache.data = {} NickCache.data = {}
} }
window.User = User window.User = CurrentUser
window.ContactsList = ContactsList window.ContactsList = ContactsList
window.NickCache = NickCache window.NickCache = NickCache
window.ChatPage = ChatPage window.ChatPage = ChatPage

View File

@@ -25,6 +25,7 @@
<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>
<script src="https://cdn.jsdelivr.net/npm/clipboard@2.0.11/dist/clipboard.min.js"></script> <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" /> <link rel="icon" href="res/icon.ico" />
<title>铃之椅</title> <title>铃之椅</title>
</head> </head>
@@ -184,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>
@@ -221,7 +222,7 @@
<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.dialogSettings.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>
@@ -276,7 +277,7 @@
<i class="mdui-list-item-icon mdui-icon material-icons">edit</i> <i class="mdui-list-item-icon mdui-icon material-icons">edit</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="User.uploadHeadImage()"> <li class="mdui-list-item mdui-ripple" onclick="CurrentUser.uploadHeadImage()">
<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>
@@ -289,7 +290,7 @@
</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>
@@ -302,7 +303,7 @@
<script src="manager.js"></script> <script src="manager.js"></script>
<script src="ui.js"></script> <script src="ui.js"></script>
<script src="handler.js"></script> <script src="handler.js"></script>
<script src="index.js"></script> <script src="finally.js"></script>
</body> </body>
</html> </html>

View File

@@ -1,50 +0,0 @@
/*
* ©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", User.getUserHeadUrl(localStorage.userName))
ContactsList.reloadList()
User.registerCallback()
}
// 感谢AI的力量
Stickyfill.add($("*").filter((a, b) => $(b).css('position') === 'sticky'))
ChatMsgAdapter.initMsgElementEvents()
ChatMsgAdapter.initInputResizer()

View File

@@ -8,6 +8,10 @@ const viewBinding = NData.mount($("#app").get(0))
let client let client
/**
* 初始化客户端
* @param {String} 服务器地址
*/
function setUpClient(server) { function setUpClient(server) {
if (server && server !== "") if (server && server !== "")
client = new io(server, { client = new io(server, {

View File

@@ -62,6 +62,10 @@ function escapeHTML(str) {
} }
class NData { class NData {
/**
* 获取 MD5sum
* @param {String} 数据
*/
static mount(node) { static mount(node) {
// 便捷获得指定组件 // 便捷获得指定组件
let es = node.querySelectorAll("[n-id]") let es = node.querySelectorAll("[n-id]")
@@ -80,6 +84,11 @@ class NData {
} }
// https://www.runoob.com/w3cnote/javascript-copy-clipboard.html // https://www.runoob.com/w3cnote/javascript-copy-clipboard.html
/**
* 复制文字
* @param {String} 欲复制的文本
*/
function copyText(t) { function copyText(t) {
let btn = $("[n-id=textCopierBtn]") let btn = $("[n-id=textCopierBtn]")
btn.attr("data-clipboard-text", t) btn.attr("data-clipboard-text", t)
@@ -90,6 +99,13 @@ function copyText(t) {
} }
// https://zhuanlan.zhihu.com/p/162910462 // https://zhuanlan.zhihu.com/p/162910462
/**
* 格式化日期
* @param {int} 时间戳
* @param {String} 欲格式化的文本
* @returns {String} 格式后的文本
*/
Date.prototype.format = function (tms, format) { Date.prototype.format = function (tms, format) {
let tmd = new Date(tms) let tmd = new Date(tms)
/* /*
@@ -164,11 +180,21 @@ class 通知 {
} }
class Hash { class Hash {
/**
* 获取 MD5sum
* @param {String} 数据
* @returns {String} Hex化的哈希值
*/
static md5(data) { static md5(data) {
return CryptoJS.MD5(data).toString(CryptoJS.enc.Base64) return CryptoJS.MD5(data).toString(CryptoJS.enc.Hex)
} }
/**
* 获取 SHA256sum
* @param {String} 数据
* @returns {String} Hex化的哈希值
*/
static sha256(data) { static sha256(data) {
return CryptoJS.SHA256(data).toString(CryptoJS.enc.Base64) return CryptoJS.SHA256(data).toString(CryptoJS.enc.Hex)
} }
} }

View File

@@ -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"))