mirror of
https://github.com/LingChair/LingChair-V0.git
synced 2025-12-10 02:55:49 +08:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a5afc8ad0 | ||
|
|
4bdfad340f | ||
|
|
6ac1b460bb | ||
|
|
29e224f87a | ||
|
|
a39973bb5c | ||
|
|
47afacbba3 | ||
|
|
3a4d733c13 | ||
|
|
89263e6e2a |
@@ -83,3 +83,9 @@
|
|||||||
height: 50px;
|
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,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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()
|
|
||||||
@@ -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, {
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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"))
|
||||||
|
|||||||
Reference in New Issue
Block a user