diff --git a/client/client-side.js b/client/client-side.js new file mode 100644 index 0000000..3130a0f --- /dev/null +++ b/client/client-side.js @@ -0,0 +1,19 @@ +/* + * ©2024 The LingChair Project + * + * Make a more colorful world... + * + * License - Apache License 2.0 + * Author - @MoonLeeeaf + * Organization - @LingChair + */ + +class Client { + +} + +class ClientUser { + +} + + diff --git a/client/custom-elements-index.js b/client/custom-elements-index.js new file mode 100644 index 0000000..72e213e --- /dev/null +++ b/client/custom-elements-index.js @@ -0,0 +1,164 @@ +/* + * ©2024 The LingChair Project + * + * Make a more colorful world... + * + * License - Apache License 2.0 + * Author - @MoonLeeeaf + * Organization - @LingChair + */ + +class MessageNormal extends HTMLElement { + static observedAttributes = ['avatar', 'sender-name', 'sender-id', 'msg', 'direction'] + constructor() { + super() + + const shadow = this.attachShadow({ mode: "open" }) + } + connectedCallback() { + const shadow = this.shadowRoot + + shadow.appendChild($('#message-normal-template').get(0).content.cloneNode(true)) + + $(shadow).find('#sender-name-left').hide() + + this.update() + } + attributeChangedCallback(_name, _oldValue, _newValue) { + this.update() + } + update() { + const shadow = this.shadowRoot + + // 消息视图的的左右方向 + let isRightDirection = this.getAttribute('direction') == 'right' + $(shadow).find('#_direction_1').css('justify-content', isRightDirection ? 'flex-end' : 'flex-start') + $(shadow).find('#_direction_2').css('justify-content', isRightDirection ? 'flex-end' : 'flex-start') + + $(shadow).find('#_direction_3').css('align-self', isRightDirection ? 'flex-end' : 'flex-start') + $(shadow).find('#_direction_3').css('margin-left', isRightDirection ? '' : '55px') + $(shadow).find('#_direction_3').css('margin-right', isRightDirection ? '55px' : '') + + $(shadow).find('#sender-name-left')[isRightDirection ? 'show' : 'hide']() + $(shadow).find('#sender-name-right')[isRightDirection ? 'hide' : 'show']() + + // 头像 + let avatar = $(shadow).find('#avatar') + this.hasAttribute('avatar') ? avatar.attr('src', this.getAttribute('avatar')) : avatar.text((this.getAttribute('sender-name') || '').substring(0, 1)) + + // 发送者 + $(shadow).find('#sender-name-left').text(this.getAttribute('sender-name')) + $(shadow).find('#sender-name-right').text(this.getAttribute('sender-name')) + $(shadow).find('#sender-id').text(this.getAttribute('sender-id')) + + // 消息 + this.hasAttribute('msg') && $(shadow).find('#msg').text(this.getAttribute('msg')) + } +} + +class MessageSystem extends HTMLElement { + constructor() { + super() + + const shadow = this.attachShadow({ mode: "open" }) + } + connectedCallback() { + const shadow = this.shadowRoot + shadow.appendChild($('#message-system-template').get(0).content.cloneNode(true)) + } +} + +class MessageHolder extends HTMLElement { + constructor() { + super() + + const shadow = this.attachShadow({ mode: "open" }) + } + connectedCallback() { + const shadow = this.shadowRoot + shadow.appendChild($('#message-holder-template').get(0).content.cloneNode(true)) + } + addMessage({ senderId = '', senderName = '', msg = '', avatar, direction = 'left' }, atStart) { + const v = new MessageNormal() + $(v).attr('sender-id', senderId).attr('sender-name', senderName).attr('avatar', avatar).attr('direction', direction).text(msg) + $(this)[atStart ? 'prepend' : 'append'](v) + } + addSystemMessage(msg, atStart) { + const v = new MessageSystem() + $(v).text(msg) + $(this)[atStart ? 'prepend' : 'append'](v) + } + getMessages(withSystemMessage) { + let ls = [] + $(this).find('message-normal' + withSystemMessage ? ', message-system' : '').each((_i, e) => { + let a = $(e) + ls.push({ + senderName: a.attr('sender-name'), + avatar: a.attr('avatar'), + senderId: a.attr('sender-id'), + direction: a.attr('direction'), + }) + }) + } +} + +customElements.define('message-normal', MessageNormal) +customElements.define('message-system', MessageSystem) +customElements.define('message-holder', MessageHolder) + +customElements.define('main-navigation-item', class extends mdui.NavigationRailItem { + static observedAttributes = ['img', 'id', 'text'] + constructor() { + super() + + const shadow = this.attachShadow({ mode: "open" }) + } + connectedCallback() { + // 现在这是 mdui-navigation-rail-item, 不应该加到shadow而是自身 + // 害得我修了好久 + + this.appendChild($('#main-navigation-item-template').get(0).content.cloneNode(true)) + + const self = this + const avatar = $(this).find('#avatar') + const avatarImg = $(this).find('#img') + avatarImg.bind('error', () => { + avatar.text((self.getAttribute('text') || '').substring(0, 1)) + }) + + this.myUpdate() + super.connectedCallback() + } + attributeChangedCallback(_name, _oldValue, _newValue) { + this.myUpdate() + super.attributeChangedCallback() + } + myUpdate() { + this.hasAttribute('img') ? $(this).find('#img').attr('src', this.getAttribute('img')) : $(this).find('#avatar').text((this.getAttribute('text') || '').substring(0, 1)) + $(this).find('#tip').attr('content', this.getAttribute('text')) + this.hasAttribute('id') && $(this).attr('value', this.getAttribute('id')) + } +}) + +customElements.define('message-img', class extends HTMLElement { + constructor() { + super() + } + connectedCallback() { + let e = new Image() + e.style.maxWidth = "100%" + e.style.maxHeight = "90%" + e.style.marginTop = "13px" + e.style.borderRadius = "var(--mdui-shape-corner-medium)" + e.src = $(this).attr('src') + e.alt = $(this).attr('alt') + e.onerror = () => { + $(this).html(`
`) + $(this).attr('alt', '图像损坏') + } + e.onclick = () => { + openImageViewer($(this).attr('src')) + } + this.appendChild(e) + } +}) diff --git a/client/global-appconfig.js b/client/global-appconfig.js new file mode 100644 index 0000000..be8c975 --- /dev/null +++ b/client/global-appconfig.js @@ -0,0 +1,13 @@ +/* + * ©2024 The LingChair Project + * + * Make a more colorful world... + * + * License - Apache License 2.0 + * Author - @MoonLeeeaf + * Organization - @LingChair + */ + +fetch('client_config.json').then((re) => re.json()).then((config) => { + +}) diff --git a/client/icon.ico b/client/icon.ico new file mode 100644 index 0000000..8677e11 Binary files /dev/null and b/client/icon.ico differ diff --git a/client/index.html b/client/index.html new file mode 100644 index 0000000..2ca8048 --- /dev/null +++ b/client/index.html @@ -0,0 +1,448 @@ + + + + + + + + + + + + + + + + + + + + + + + TheWhiteSilk + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + - + + + + 详细列表 + + + + + 最近 + 联系人 + 群聊 + + + + + + + + 关于 + + + + + + + + + + + + + + 关闭 + + + + + + + +
+ + +
+ LingChair +
+ + Nightly +
+ + 在 GitHub 查看源码 + +
+
+
+
+ + + + + + + + + + + +
+ + + + + + + 主面板 + + + 院审 +
+
+ + + + + 资料 + + + 设置 + + + + +
+ + + 加载更多 +
+ + 我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神 + + 启动 + + 我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神 + + 原神 被 测试 踢出了群聊 + + 满月姐姐满月姐姐满月姐姐满月姐姐满月姐姐满月姐姐满月姐姐满月姐姐满月姐姐满月姐姐满月姐姐满月姐姐满月姐姐满月姐姐满月姐姐满月姐姐满月姐姐满月姐姐满月姐姐满月姐姐满月姐姐 + + 启动 + 阿弥诺斯 + + 我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神我要玩原神 + + 我要玩原神
+ +
+ 我要玩院审😭👊
+ +
+
+
+ + + + + + + + + + + 插入图片 + + + 插入音频 + + + 插入文件 + + + 插入链接 + + + + + 插入名片 + + + + + 展开输入框 + + + + + +
+
+ + + + + + + + + \ No newline at end of file diff --git a/client/signin.html b/client/signin.html new file mode 100644 index 0000000..4dcf9c1 --- /dev/null +++ b/client/signin.html @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + 登录到 铃之椅 + + + + + + + +
+

登录到 铃之椅

+ + + + +
+ 登录账号 +
+
+ 注册账号 +
+
+ 游客登录 +
+
+ ©2024 满月叶 + + + + + + + + \ No newline at end of file diff --git a/client/style-shared.css b/client/style-shared.css new file mode 100644 index 0000000..9c3f342 --- /dev/null +++ b/client/style-shared.css @@ -0,0 +1,87 @@ +/* + * ©2024 The LingChair Project + * + * Make a more colorful world... + * + * License - Apache License 2.0 + * Author - @MoonLeeeaf + * Organization - @LingChair + */ + +/* 滑条*/ +.no-scroll-bar::-webkit-scrollbar { + width: 0px !important; +} + +/* https://blog.csdn.net/qq_39347364/article/details/111996581*/ +*::-webkit-scrollbar { + width: 7px; + height: 10px; +} + +*::-webkit-scrollbar-track { + width: 6px; + background: rgba(#101F1C, 0.1); + -webkit-border-radius: 2em; + -moz-border-radius: 2em; + border-radius: 2em; +} + +*::-webkit-scrollbar-thumb { + background-color: rgba(144, 147, 153, .5); + background-clip: padding-box; + min-height: 28px; + -webkit-border-radius: 2em; + -moz-border-radius: 2em; + border-radius: 2em; + transition: background-color .3s; + cursor: pointer; +} + +*::-webkit-scrollbar-thumb:hover { + background-color: rgba(144, 147, 153, .3); +} + +/* 使用系统字体 在部分系统表现很好*/ +/* 我们至今仍未能知道桌面端浏览器字体的秘密*/ +*:not(.material-icons, .mdui-icon, mdui-icon, .fa, .google-symbols) { + font-family: -apple-system, system-ui, -webkit-system-font !important; +} + +body { + display: flex; + margin: 0 0 0 0; +} + +/* 防止小尺寸图片模糊*/ +* { + image-rendering: -moz-crisp-edges; + image-rendering: -o-crisp-edges; + image-rendering: -webkit-optimize-contrast; + image-rendering: crisp-edges; + -ms-interpolation-mode: nearest-neighbor; +} + +/* 与窗口同大小*/ +.size-as-window { + width: var(--window-width); + height: var(--window-height); +} + +/* Flex - 铺满*/ +.fill-width { + width: 100%; +} + +.fill-height { + height: 100%; +} + +/* 链接使用主题色*/ +a { + color: rgb(var(--mdui-color-primary)); +} + +.contents-only { + display: contents; +} \ No newline at end of file diff --git a/client/ui-controller-index.js b/client/ui-controller-index.js new file mode 100644 index 0000000..ac10b0b --- /dev/null +++ b/client/ui-controller-index.js @@ -0,0 +1,190 @@ +/* + * ©2024 The LingChair Project + * + * Make a more colorful world... + * + * License - Apache License 2.0 + * Author - @MoonLeeeaf + * Organization - @LingChair + */ + +/** + * ======================================================== + * 侧边导航栏列表 + * ======================================================== + */ + +// 按钮:查看详细列表 +$('#switch-navigation-list-info-menuicon').parent().click(() => { + let list = $('#nav-list-information-dialog > mdui-list').empty() + let selected = $('#switch-navigation-list-menu').get(0).value + $('#main-navigation-list-' + selected + " > main-navigation-item").each((_i, e) => { + let i = $.parseHTML(``) + let a = $.parseHTML(``) + let img = new Image() + img.style.cssText = `width: 100%; height: 100%; object-fit: contain;` + img.src = e.getAttribute('img') + img.onerror = () => { + $(a).text((e.getAttribute('text') || '').substring(0, 1)) + } + $(a).append(img) + $(i).append(e.getAttribute('text')) + $(i).append(a) + list.append(i) + }) + $('#nav-list-information-dialog').attr('headline', (function () { + let t + switch (selected + '') { + case "1": + t = '最近' + break; + case "2": + t = '联系人' + break; + case "3": + t = '群组' + break; + } + return t + })()) + $('#nav-list-information-dialog').attr('open', true) +}) + +// 切换列表选项 +let lastValue = $('#switch-navigation-list-menu').get(0).value +$('#switch-navigation-list-menu').on('change', (e) => { + // 当前选择的值 + let value = e.target.value + + // 特殊:详细列表 + if (value == 0) { + // 选回原来的选项 + e.target.value = lastValue + value = lastValue + $('#switch-navigation-list-menu > mdui-menu-item[value=' + value + ']').get(0).selected = true + $('#switch-navigation-list-menu > mdui-menu-item[value=0]').get(0).selected = false + // 错误示范: 在这里写点击事件 + } + + // 禁止空选择 + if (value == null) { + e.target.value = lastValue + value = lastValue + $('#switch-navigation-list-menu > mdui-menu-item[value=' + value + ']').get(0).selected = true + } + + // 显示指定的列表 + $('#main-navigation-list-1').hide() + $('#main-navigation-list-2').hide() + $('#main-navigation-list-3').hide() + $('#main-navigation-list-' + value).show() + + // 修改图标 + let icon = (function () { + let ico + switch (value + '') { + case "1": + ico = 'watch_later--outlined' + break; + case "2": + ico = 'contacts--outlined' + break; + case "3": + ico = 'group--outlined' + break; + } + return ico + })() + $('#switch-navigation-list-button').attr('icon', icon) + $('#switch-navigation-list-info-menuicon').attr('name', icon) + + // 更新最后的值用以防止空选择 + lastValue = value +}) + +// 最开始只选择 最近, 隐藏其他列表 +$('#main-navigation-list-2').hide() +$('#main-navigation-list-3').hide() + +// 子项目被点击时 +$('mdui-navigation-rail').on('click', (event) => { + let e = event.target + let tagName = e.tagName.toLowerCase() + while (tagName != 'main-navigation-item') { + e = e.parentNode + tagName = (e.tagName || 'mdui-navigation-rail').toLowerCase() + if (tagName == 'mdui-navigation-rail') return + } + // 获取到Item + +}) + +/** + * ======================================================== + * 输入框与消息编辑 + * ======================================================== + */ + +windowOnResizingCallbacks.push((w, h) => { + $('#input_message').width(w - ($('mdui-navigation-rail').width() + $('#send_message').width() * 2 + 100)) +}) + +/** + * ======================================================== + * 消息列表 + * ======================================================== + */ + +function scrollMessageHolderToBottom() { + window.scrollBy({ + top: 1145141919810, + behavior: "smooth", + }) +} + +/** + * ======================================================== + * 图片查看对话框 + * ======================================================== + */ + +function openImageViewer(src) { + $('#image-viewer-dialog-inner').empty() + + let e = new Image() + e.src = src + e.onerror = () => { + $('#image-viewer-dialog-inner').empty() + $('#image-viewer-dialog-inner').append($.parseHTML(``)) + } + $('#image-viewer-dialog-inner').append(e) + + e.onload = () => $('#image-viewer-dialog-inner').get(0).setTransform({ + scale: 0.6, + x: $(window).width() / 2 - (e.width / 4), + y: $(window).height() / 2 - (e.height / 3), + }) + $('#image-viewer-dialog').get(0).open = true +} + +/** + * ======================================================== + * 下载 + * ======================================================== + */ + +async function downloadFromUrl(src) { + let re = await fetch(src) + let blob = await re.blob() + let url = URL.createObjectURL(blob) + $('#download-helper').attr('download', url).attr('href', url).get(0).click() + setTimeout(() => URL.revokeObjectURL(url), 10000) +} + +/** + * ======================================================== + * 杂项 + * ======================================================== + */ + +// mdui.setColorScheme("#FFB4AA") diff --git a/client/ui-controller-shared.js b/client/ui-controller-shared.js new file mode 100644 index 0000000..4f6ecb1 --- /dev/null +++ b/client/ui-controller-shared.js @@ -0,0 +1,41 @@ +/* + * ©2024 The LingChair Project + * + * Make a more colorful world... + * + * License - Apache License 2.0 + * Author - @MoonLeeeaf + * Organization - @LingChair + */ + +/** + * ======================================================== + * 窗口大小 + * ======================================================== + */ + +// 在窗口加载完毕后会将所有页面大小变化的回调都调用一次 +const windowOnResizingCallbacks = [] + +function updateWindowSize() { + document.body.style.setProperty('--window-width', `${window.innerWidth}px`) + document.body.style.setProperty('--window-height', `${window.innerHeight}px`) + windowOnResizingCallbacks.forEach((v) => v(window.innerWidth, window.innerHeight)) +} +window.addEventListener('resize', updateWindowSize) +// 初步确定(值有偏差) +$(() => updateWindowSize()) +// 完全确定(值已经确定) +window.addEventListener('load', updateWindowSize) + +/** + * ======================================================== + * Shadow 元素辅助代码 + * ======================================================== + */ + +// 将组件添加到影子DOM中 +$(() => $('* > shadow-inner').each((_i, v) => { + $(v.parentElement.shadowRoot).append(v.children) + v.parentNode.removeChild(v) +})) diff --git a/client/utils-shared.js b/client/utils-shared.js new file mode 100644 index 0000000..99ea501 --- /dev/null +++ b/client/utils-shared.js @@ -0,0 +1,30 @@ +/* + * ©2024 The LingChair Project + * + * Make a more colorful world... + * + * License - Apache License 2.0 + * Author - @MoonLeeeaf + * Organization - @LingChair + */ + +/** + * ======================================================== + * 移动端检测 + * ======================================================== + */ + +const isMobile = () => ('ontouchstart' in document.documentElement) + +/** + * ======================================================== + * 移动端调试 + * ======================================================== + */ + +if (isMobile()) { + let a = document.createElement('script') + a.src = "https://unpkg.com/eruda/eruda.js" + a.onload = () => eruda.init() + document.head.appendChild(a) +}