From 48bad65df5b882080f5f7c12c8b9d3f0d6c47ab4 Mon Sep 17 00:00:00 2001 From: MoonLeeeaf <150461955+MoonLeeeaf@users.noreply.github.com> Date: Sat, 25 May 2024 16:41:27 +0800 Subject: [PATCH] rebase --- client_src/chat-message.css | 85 ---- client_src/index.css | 54 --- client_src/index.html | 339 ------------- client_src/index.js | 810 -------------------------------- client_src/mdui-prettier.css | 53 --- client_src/res/config.json | 4 - client_src/res/default_head.png | Bin 7722 -> 0 bytes client_src/res/icon.ico | Bin 29488 -> 0 bytes client_src/res/license.txt | 13 - 9 files changed, 1358 deletions(-) delete mode 100644 client_src/chat-message.css delete mode 100644 client_src/index.css delete mode 100644 client_src/index.html delete mode 100644 client_src/index.js delete mode 100644 client_src/mdui-prettier.css delete mode 100644 client_src/res/config.json delete mode 100644 client_src/res/default_head.png delete mode 100644 client_src/res/icon.ico delete mode 100644 client_src/res/license.txt diff --git a/client_src/chat-message.css b/client_src/chat-message.css deleted file mode 100644 index 46e0dcf..0000000 --- a/client_src/chat-message.css +++ /dev/null @@ -1,85 +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. - */ - -.chat-message-right { - display: flex; - justify-content: flex-end; - align-items: flex-start; - margin: 13px; -} - -.chat-message-left { - display: flex; - justify-content: flex-start; - align-items: flex-start; - margin: 13px; -} - -.message-content { - margin-top: 13px; - margin-bottom: 7px; - margin-left: 5px; - margin-right: 5px; - max-width: 100%; - white-space: normal; - word-break: break-all; - font-size: medium; - /* 使用了 CardView 就不需要边框了 */ - /* border: 1.3px solid; */ - padding: 15px; - border-radius: 15px; - /* 添加圆角样式 */ - /* 设置外边距为 7px */ -} - -.message-content-with-nickname-right { - display: flex; - align-items: center; - margin: 7px; - flex-direction: column; - /* 垂直排列元素 */ - align-items: flex-end; - /* 左对齐元素 */ -} - -.message-content-with-nickname-left { - display: flex; - align-items: center; - margin: 7px; - flex-direction: column; - /* 垂直排列元素 */ - align-items: flex-start; - /* 左对齐元素 */ -} - -.chat-message-left .message-content-with-nickname-left .nickname, -.chat-message-right .message-content-with-nickname-right .nickname { - margin-right: 5px; - font-size: medium; - margin-top: 3px; -} - -.chat-message-left > .avatar, -.chat-message-right > .avatar { - width: 45px; - height: 45px; - border-radius: 50%; -} \ No newline at end of file diff --git a/client_src/index.css b/client_src/index.css deleted file mode 100644 index debc8f2..0000000 --- a/client_src/index.css +++ /dev/null @@ -1,54 +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. - */ - - html, body { - max-height: 100%; - margin: 0; - padding: 0; - /* overflow: hidden; */ - /*font: initial;*/ -} -body { - margin: 0; - padding: 0; -} -.container { - display: flex; - flex-direction: column; - overflow: auto; -} -.content { - flex: 1; -} - -.menu-on-message { - margin-top: 60px; - z-index: 100; -} - -[n-id=pageChatSeesion]::after { - content: ""; - position: sticky; - bottom: 0; - display: block; - height: var(--pseudo-height); /* 设置伪元素的高度 */ - z-index: -1; /* 防止遮挡实际内容 */ - } diff --git a/client_src/index.html b/client_src/index.html deleted file mode 100644 index 8682440..0000000 --- a/client_src/index.html +++ /dev/null @@ -1,339 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - 铃之椅 - - - - - - -
- -
- - - -
- - -
- - -
- - -
- - -
- -
- -
- -
-
-
- - - -
- - -
-
- 登录到 铃之椅 -
-
-
- cloud_circle - - -
-
- account_circle - - -
-
- lock - - -
- 注:使用非已知的服务提供商提供的服务器时, 请注意个人信息保护哦 o(。・ω・。)o -
-
- - -
-
-
- - -
-
-
- -
-
-
-
-
- -
-
- - - - -
-
- 修改昵称 -
-
-
- - -
-
-
- - -
-
- - -
-
- 新的好友请求 -
-
-
- - -
-
-
- -
-
- - -
-
- 添加好友/群 -
-
-
- - -
- -
-
- - -
-
- - -
-
- 设置 -
-
- -
-
- -
-
- -
- -
- - - - - - - - - - - - - \ No newline at end of file diff --git a/client_src/index.js b/client_src/index.js deleted file mode 100644 index e46a5ed..0000000 --- a/client_src/index.js +++ /dev/null @@ -1,810 +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 - -// https://www.ruanyifeng.com/blog/2021/09/detecting-mobile-browser.html -function isMobile() { - return ('ontouchstart' in document.documentElement); -} - -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: "res/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.contactsAdd.hide() -viewBinding.tabChatList.on("show.mdui.tab", () => { - viewBinding.contactsRefresh.hide() - viewBinding.contactsAdd.hide() -}) -viewBinding.tabContacts.on("show.mdui.tab", () => { - viewBinding.contactsRefresh.show() - viewBinding.contactsAdd.show() -}) -viewBinding.tabChatSeesion.on("show.mdui.tab", () => { - viewBinding.contactsRefresh.hide() - viewBinding.contactsAdd.hide() -}) */ - -/* viewBinding.tabChatSeesion.hide() */ - -// 关于页面 -viewBinding.menuAbout.click(() => mdui.alert('这是一个开源项目
作者: MoonLeeeaf
欢迎访问我们的项目主页', '关于 铃之椅', () => { }, { 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 btn = viewBinding.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 -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(`
  • ` + dick + `
  • `)).appendTo(viewBinding.contactsList).click(() => { - ChatMsgAdapter.switchTo(name, "single") - }) - //}) - } - - }) - } - // 添加联系人,好友或者群聊 - static add(name, type) { - if (type == "single") { - - } - } - static openAddDialog() { - new mdui.Dialog(viewBinding.dialogNewContact.get(0)).open() - } -} - -// 第一次写前端的消息加载, 代码很乱, 还请原谅~ - -// v0.7.0 大改UI 畏惧了 太庞大了 - -class ChatPage { - static cached = {} - constructor(name, type) { - - } - static switchTo(name, type) { - if (!this.cached[name]) - this.cached[name] = new ChatPage(name, type) - } -} - -class ChatMsgAdapter { - static type - static target - // static msgList - static minMsgId - static time - static bbn - static resizeDick - // 切换聊天对象 - 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对象 - // name: 用户id m: 消息 t: 时间戳 re: 默认加到尾部 msgid: 消息id - 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 = `
    -
    - ` + nick + ` -
    - ` + msg + ` -
    -
    - -
    ` - else - temp = `
    - -
    - ` + nick + ` -
    - ` + msg + ` -
    -
    -
    ` - - let bn = new Date(t).getMinutes() - let e - if (re) { - this.addSystemMsg(temp, re) - if (this.bbn != bn) { - e = this.addSystemMsg(`
    ` + new Date().format(t == null ? Date.parse("1000-1-1 00:00:00") : t, "yyyy年MM月dd日 hh:mm:ss") + `
    `, re) - this.time = bn - } - } else { - if (this.bbn != bn) { - e = this.addSystemMsg(`
    ` + new Date().format(t == null ? Date.parse("1000-1-1 00:00:00") : t, "yyyy年MM月dd日 hh:mm:ss") + `
    `, 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去整啊 - viewBinding.chatPager.get(0).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) - }*/ - // 自动调整使输入框置底 CSS真tm靠不住啊 - static initInputResizer() { - // 实验表面移动端切出输入法时会触发1-2次resize事件 - // 可以利用这个特性来实现自动滚动文本 - let resize = () => { - viewBinding.pageChatSeesion.height(window.innerHeight - viewBinding.inputToolbar.height() - $("header.mdui-appbar").height() - viewBinding.chatTab.height() - 50) - let ledi = this.resizeDick - window.innerHeight - if (isMobile()) viewBinding.chatPager.get(0).scrollBy({ - // 5.19晚10:56分调配出来的秘方 - // < 0 为窗口变大 - // cnm的,调试十万次就你tm检测不到底是吧,就你语法天天错误是吧 - // 欺负我现在用不了电脑 - top: -(ledi) * ( (ledi < 0 && this.isAtBottom()) ? 6 : -1 ), // (ledi < 0 ? 6 : 6), - behavior: 'smooth' - }) - this.resizeDick = window.innerHeight - } - window.addEventListener("resize", resize) - resize() - } - // 为消息设置长按/右键事件 - static initMsgElementEvents() { - 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(``) - 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() - -ChatMsgAdapter.initInputResizer() - -function refreshAll() { - ContactsList.reloadList() - delete NickCache.data - NickCache.data = {} -} diff --git a/client_src/mdui-prettier.css b/client_src/mdui-prettier.css deleted file mode 100644 index 4b86b3b..0000000 --- a/client_src/mdui-prettier.css +++ /dev/null @@ -1,53 +0,0 @@ -/* - * ©2024 满月叶 - * GitHub: MoonLeeeaf - * 是 UI 美化,好耶! - */ - -/* 美化UI */ - -body { - font-family: -apple-system, system-ui, -webkit-system-font; -} -.mdui-dialog { - border-radius: 23px; -} -.mdui-menu { - border-radius: 10px; -} -.mdui-menu-item > a { - padding-right: 3px; -} -.mdui-btn:not(.mdui-btn-icon, .mdui-dialog-actions button, .mdui-dialog-actions a) { - padding-left: 20px; - padding-right: 20px; - height: 40px; - border-radius: 10px; -} -.mdui-dialog-actions a, -.mdui-dialog-actions button { - padding-left: 20px; - padding-right: 20px; - height: 40px; - border-radius: 40px; -} -.mdui-select-open { - border-radius: 10px; -} -@media not screen and (min-width: 768px) { - .mdui-snackbar { - border-radius: 10px; - } -} - -/* 配色方案 */ - -.mdui-theme-color-auto { - background-color: #fff; -} - -@media (prefers-color-scheme: dark) { - .mdui-theme-color-auto { - background-color: #303030; - } -} diff --git a/client_src/res/config.json b/client_src/res/config.json deleted file mode 100644 index d8ada1e..0000000 --- a/client_src/res/config.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "appTitle": "", - "canChangeServer": true -} \ No newline at end of file diff --git a/client_src/res/default_head.png b/client_src/res/default_head.png deleted file mode 100644 index 4f6f226fef17df30635ce72b79b0fd7c6bda4cfa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7722 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

    x>t0*LRBWY;mk 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 mC(dQF=HfT+O%)nkD)&xqUK9