mirror of
https://github.com/LingChair/LingChair-V0.git
synced 2025-12-08 01:55:50 +08:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a5afc8ad0 | ||
|
|
4bdfad340f | ||
|
|
6ac1b460bb | ||
|
|
29e224f87a | ||
|
|
a39973bb5c | ||
|
|
47afacbba3 | ||
|
|
3a4d733c13 | ||
|
|
89263e6e2a | ||
|
|
b2c8c86689 |
@@ -82,4 +82,10 @@
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
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,
|
||||
})
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -25,6 +25,7 @@
|
||||
<script src="https://cdn.jsdelivr.net/gh/wilddeer/stickyfill@2.1.0/dist/stickyfill.min.js"></script>
|
||||
<script src="https://unpkg.com/jquery@3.7.1/dist/jquery.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/clipboard@2.0.11/dist/clipboard.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
<link rel="icon" href="res/icon.ico" />
|
||||
<title>铃之椅</title>
|
||||
</head>
|
||||
@@ -184,9 +185,9 @@
|
||||
</div>
|
||||
<div class="mdui-dialog-actions">
|
||||
<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"
|
||||
onclick="User.signInWithDialog(viewBinding.dialogSignInName.val(), viewBinding.dialogSignInPasswd.val())">登录</button>
|
||||
onclick="CurrentUser.signInWithDialog(viewBinding.dialogSignInName.val(), viewBinding.dialogSignInPasswd.val())">登录</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -221,7 +222,7 @@
|
||||
<button class="mdui-btn mdui-ripple" n-id="dialogEditNickClose" mdui-dialog-close
|
||||
onclick="new mdui.Dialog(viewBinding.dialogSettings.get(0)).open()">关闭</button>
|
||||
<button class="mdui-btn mdui-ripple"
|
||||
onclick="User.setNick(viewBinding.dialogEditNickNick.val(), () => {mdui.snackbar('已保存, 刷新页面生效');viewBinding.dialogEditNickClose.click()})">保存</button>
|
||||
onclick="CurrentUser.setNick(viewBinding.dialogEditNickNick.val(), () => {mdui.snackbar('已保存, 刷新页面生效');viewBinding.dialogEditNickClose.click()})">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -276,7 +277,7 @@
|
||||
<i class="mdui-list-item-icon mdui-icon material-icons">edit</i>
|
||||
<div class="mdui-list-item-content">修改昵称</div>
|
||||
</li>
|
||||
<li class="mdui-list-item mdui-ripple" onclick="User.uploadHeadImage()">
|
||||
<li class="mdui-list-item mdui-ripple" onclick="CurrentUser.uploadHeadImage()">
|
||||
<i class="mdui-list-item-icon mdui-icon material-icons">account_circle</i>
|
||||
<div class="mdui-list-item-content">上传头像</div>
|
||||
</li>
|
||||
@@ -289,7 +290,7 @@
|
||||
</div>
|
||||
|
||||
<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" />
|
||||
</div>
|
||||
|
||||
@@ -297,7 +298,12 @@
|
||||
<script src="https://unpkg.com/crypto-js@4.2.0/crypto-js.js"></script>
|
||||
<script src="https://unpkg.com/socket.io-client@4.7.4/dist/socket.io.min.js"></script>
|
||||
<script src="https://unpkg.com/mdui@1.0.2/dist/js/mdui.min.js"></script>
|
||||
<script src="index.js"></script>
|
||||
<!-- 核心脚本部分 -->
|
||||
<script src="utils.js"></script>
|
||||
<script src="manager.js"></script>
|
||||
<script src="ui.js"></script>
|
||||
<script src="handler.js"></script>
|
||||
<script src="finally.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
36
ling_chair_http/manager.js
Normal file
36
ling_chair_http/manager.js
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* ©2024 满月叶
|
||||
* Github: MoonLeeeaf
|
||||
* 资源类
|
||||
*/
|
||||
|
||||
const viewBinding = NData.mount($("#app").get(0))
|
||||
|
||||
let client
|
||||
|
||||
/**
|
||||
* 初始化客户端
|
||||
* @param {String} 服务器地址
|
||||
*/
|
||||
function setUpClient(server) {
|
||||
if (server && server !== "")
|
||||
client = new io(server, {
|
||||
auth: {
|
||||
name: localStorage.isSignIn === "false" ? null : localStorage.userName
|
||||
}
|
||||
})
|
||||
else
|
||||
client = new io({
|
||||
auth: {
|
||||
name: localStorage.isSignIn === "false" ? null : localStorage.userName
|
||||
}
|
||||
})
|
||||
|
||||
client.on("connect", () => {
|
||||
User.auth()
|
||||
})
|
||||
}
|
||||
|
||||
window.viewBinding = viewBinding
|
||||
window.setUpClient = setUpClient
|
||||
window.client = client
|
||||
71
ling_chair_http/ui.js
Normal file
71
ling_chair_http/ui.js
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* ©2024 满月叶
|
||||
* Github: MoonLeeeaf
|
||||
* 界面逻辑
|
||||
*/
|
||||
|
||||
$.ajax({
|
||||
url: "res/config.json",
|
||||
dataType: "json",
|
||||
success: (c) => {
|
||||
viewBinding.appTitle.text(c.appTitle)
|
||||
if (!c.canChangeServer) {
|
||||
viewBinding.dialogSignInServerLabel.hide()
|
||||
viewBinding.drawerChangeServer.hide()
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
// 关于页面
|
||||
viewBinding.menuAbout.click(() => mdui.alert('这是一个开源项目<br/>作者: MoonLeeeaf<br/>欢迎访问我们的<a class="mdui-text-color-theme-accent" href="https://github.com/LingChair/LingChair">项目主页</a>', '关于 铃之椅', () => { }, { confirmText: "关闭" }))
|
||||
|
||||
viewBinding.drawerChangeServer.click(() => {
|
||||
mdui.prompt('输入服务器地址...(为空则使用当前页面地址)', (value) => {
|
||||
localStorage.server = value
|
||||
mdui.snackbar("更新成功, 刷新页面生效")
|
||||
}, () => { }, {
|
||||
confirmText: "确定",
|
||||
cancelText: "取消"
|
||||
})
|
||||
})
|
||||
|
||||
viewBinding.drawerSignOut.click(() => {
|
||||
mdui.confirm('确定要登出账号吗', () => {
|
||||
User.signOutAndReload()
|
||||
}, () => { }, {
|
||||
confirmText: "确定",
|
||||
cancelText: "取消"
|
||||
})
|
||||
})
|
||||
|
||||
viewBinding.sendMsg.click((a) => {
|
||||
let text = viewBinding.inputMsg.val()
|
||||
if (text.trim() !== "")
|
||||
ChatMsgAdapter.send(text)
|
||||
})
|
||||
|
||||
viewBinding.inputMsg.keydown((e) => {
|
||||
if (e.ctrlKey && e.keyCode === 13)
|
||||
viewBinding.sendMsg.click()
|
||||
})
|
||||
|
||||
viewBinding.dialogSignInPasswd.keydown((e) => {
|
||||
if (e.keyCode === 13)
|
||||
viewBinding.dialogSignInEnter.click()
|
||||
})
|
||||
|
||||
viewBinding.switchNotifications.click((a) => {
|
||||
if ((localStorage.useNotifications == "true" || localStorage.useNotifications != null) && localStorage.useNotifications != "false") {
|
||||
localStorage.useNotifications = "false"
|
||||
viewBinding.switchNotificationsIcon.text("notifications_off")
|
||||
} else {
|
||||
localStorage.useNotifications = "true"
|
||||
viewBinding.switchNotificationsIcon.text("notifications")
|
||||
}
|
||||
})
|
||||
if (localStorage.useNotifications == "true")
|
||||
viewBinding.switchNotificationsIcon.text("notifications")
|
||||
|
||||
viewBinding.inputMsg.blur(() => {
|
||||
window.initInputResizerResize()
|
||||
})
|
||||
208
ling_chair_http/utils.js
Normal file
208
ling_chair_http/utils.js
Normal file
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* ©2024 满月叶
|
||||
* Github: MoonLeeeaf
|
||||
* 辅助添加
|
||||
*/
|
||||
|
||||
// 2024.5.28 睡着了
|
||||
const sleep = (t) => new Promise((res) => setTimeout(res, t))
|
||||
|
||||
const UrlArgs = new URL(location.href).searchParams
|
||||
|
||||
// https://www.ruanyifeng.com/blog/2021/09/detecting-mobile-browser.html
|
||||
function isMobile() {
|
||||
return ('ontouchstart' in document.documentElement);
|
||||
}
|
||||
|
||||
if (UrlArgs.get("debug")) {
|
||||
let script = document.createElement('script')
|
||||
script.src = "//cdn.jsdelivr.net/npm/eruda"
|
||||
document.body.appendChild(script)
|
||||
script.onload = () => eruda.init()
|
||||
}
|
||||
|
||||
// 经常会因为这个指定ID为位置导致一些莫名BUG
|
||||
if (location.href.includes("#")) location.replace(location.href.substring(0, location.href.indexOf("#")))
|
||||
|
||||
const mdui_snackbar = mdui.snackbar
|
||||
mdui.snackbar = (m) => {
|
||||
let t = m
|
||||
if (m instanceof Object)
|
||||
t = JSON.stringify(m)
|
||||
mdui_snackbar(t)
|
||||
}
|
||||
|
||||
const checkEmpty = (i) => {
|
||||
if (i instanceof Array) {
|
||||
for (let k of i) {
|
||||
if (checkEmpty(k)) return true
|
||||
}
|
||||
}
|
||||
|
||||
return (i == null) || ("" === i) || (0 === i)
|
||||
}
|
||||
|
||||
function escapeHTML(str) {
|
||||
return str.replace(/[<>&"']/g, function (match) {
|
||||
switch (match) {
|
||||
case '<':
|
||||
return '<'
|
||||
case '>':
|
||||
return '>'
|
||||
case '&':
|
||||
return '&'
|
||||
case '"':
|
||||
return '"'
|
||||
case "'":
|
||||
return '''
|
||||
default:
|
||||
return match
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
class NData {
|
||||
/**
|
||||
* 获取 MD5sum
|
||||
* @param {String} 数据
|
||||
*/
|
||||
static mount(node) {
|
||||
// 便捷获得指定组件
|
||||
let es = node.querySelectorAll("[n-id]")
|
||||
let ls = {}
|
||||
es.forEach((i) => ls[$(i).attr("n-id")] = $(i))
|
||||
|
||||
// input 组件与 localStorage 绑定
|
||||
es = node.querySelectorAll("[n-input-ls]")
|
||||
es.forEach((e) => {
|
||||
let j = $(e)
|
||||
j.val(localStorage.getItem(j.attr("n-input-ls")))
|
||||
j.blur(() => localStorage.setItem(j.attr("n-input-ls"), j.val()))
|
||||
})
|
||||
return ls
|
||||
}
|
||||
}
|
||||
|
||||
// https://www.runoob.com/w3cnote/javascript-copy-clipboard.html
|
||||
|
||||
/**
|
||||
* 复制文字
|
||||
* @param {String} 欲复制的文本
|
||||
*/
|
||||
function copyText(t) {
|
||||
let btn = $("[n-id=textCopierBtn]")
|
||||
btn.attr("data-clipboard-text", t)
|
||||
new ClipboardJS(btn.get(0)).on('success', (e) => {
|
||||
e.clearSelection()
|
||||
})
|
||||
btn.click()
|
||||
}
|
||||
|
||||
// https://zhuanlan.zhihu.com/p/162910462
|
||||
|
||||
/**
|
||||
* 格式化日期
|
||||
* @param {int} 时间戳
|
||||
* @param {String} 欲格式化的文本
|
||||
* @returns {String} 格式后的文本
|
||||
*/
|
||||
Date.prototype.format = function (tms, format) {
|
||||
let tmd = new Date(tms)
|
||||
/*
|
||||
* 例子: format="YYYY-MM-dd hh:mm:ss";
|
||||
*/
|
||||
var o = {
|
||||
"M+": tmd.getMonth() + 1, // month
|
||||
"d+": tmd.getDate(), // day
|
||||
"h+": tmd.getHours(), // hour
|
||||
"m+": tmd.getMinutes(), // minute
|
||||
"s+": tmd.getSeconds(), // second
|
||||
"q+": Math.floor((tmd.getMonth() + 3) / 3), // quarter
|
||||
"S": tmd.getMilliseconds()
|
||||
// millisecond
|
||||
}
|
||||
if (/(y+)/.test(format)) {
|
||||
format = format.replace(RegExp.$1, (tmd.getFullYear() + "")
|
||||
.substr(4 - RegExp.$1.length));
|
||||
}
|
||||
for (var k in o) {
|
||||
if (new RegExp("(" + k + ")").test(format)) {
|
||||
format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k]
|
||||
: ("00" + o[k]).substr(("" + o[k]).length));
|
||||
}
|
||||
}
|
||||
return format;
|
||||
}
|
||||
|
||||
// 既然已经有 Notification 了, 那用回中文也不过分吧 :)
|
||||
class 通知 {
|
||||
constructor() {
|
||||
this.args = {}
|
||||
this.title = ""
|
||||
}
|
||||
static checkAvailable() {
|
||||
return ("Notification" in window)
|
||||
}
|
||||
static async request() {
|
||||
if (!this.checkAvailable()) return false
|
||||
return (await Notification.requestPermission())
|
||||
}
|
||||
setId(id) {
|
||||
this.args.tag = id
|
||||
return this
|
||||
}
|
||||
setTitle(t) {
|
||||
this.title = t
|
||||
return this
|
||||
}
|
||||
setMessage(m) {
|
||||
this.args.body = m
|
||||
return this
|
||||
}
|
||||
setIcon(i) {
|
||||
this.args.icon = i
|
||||
return this
|
||||
}
|
||||
setImage(i) {
|
||||
this.args.image = i
|
||||
return this
|
||||
}
|
||||
setData(data) {
|
||||
this.args.data = data
|
||||
}
|
||||
show(onclick/*, onclose*/) {
|
||||
if (!通知.checkAvailable()) return
|
||||
if (localStorage.useNotifications !== "true") return
|
||||
let n = new Notification(this.title, this.args)
|
||||
n.onclick = onclick == null ? () => n.close() : (n) => onclick(n)
|
||||
return n
|
||||
}
|
||||
}
|
||||
|
||||
class Hash {
|
||||
/**
|
||||
* 获取 MD5sum
|
||||
* @param {String} 数据
|
||||
* @returns {String} Hex化的哈希值
|
||||
*/
|
||||
static md5(data) {
|
||||
return CryptoJS.MD5(data).toString(CryptoJS.enc.Hex)
|
||||
}
|
||||
/**
|
||||
* 获取 SHA256sum
|
||||
* @param {String} 数据
|
||||
* @returns {String} Hex化的哈希值
|
||||
*/
|
||||
static sha256(data) {
|
||||
return CryptoJS.SHA256(data).toString(CryptoJS.enc.Hex)
|
||||
}
|
||||
}
|
||||
|
||||
window.copyText = copyText
|
||||
window.NData = NData
|
||||
window.escapeHTML = escapeHTML
|
||||
window.isMobile = isMobile
|
||||
window.checkEmpty = checkEmpty
|
||||
window.sleep = sleep
|
||||
window.Hash = Hash
|
||||
window.通知 = 通知
|
||||
@@ -48,7 +48,8 @@ if (!io.exists(vals.LINGCHAIR_SERVER_CONFIG_FILE)) io.open(vals.LINGCHAIR_SERVER
|
||||
cert: "",
|
||||
},
|
||||
})).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"))
|
||||
|
||||
Reference in New Issue
Block a user