Compare commits

...

4 Commits

Author SHA1 Message Date
CrescentLeaf
4b9d78d0d5 ui: 三个侧边列表的搜索框边距修缮 2025-11-30 00:36:15 +08:00
CrescentLeaf
1f6f8a768f ui: 时间显示修缮: 小时和分钟补齐一个 0 2025-11-30 00:32:27 +08:00
CrescentLeaf
a7c61d9306 ui: 不再为每个消息显示发送用户
* 合并显示, 但不完全
2025-11-30 00:32:08 +08:00
CrescentLeaf
0247eaeda9 ui: 对话页面的 Tab 栏项目过多时可以左右滑动, 并保持原有的形态 2025-11-30 00:30:10 +08:00
5 changed files with 53 additions and 25 deletions

View File

@@ -1,4 +1,4 @@
import { Tab, TextField } from "mdui" import { Tab, Tabs, TextField } from "mdui"
import { $ } from "mdui/jq" import { $ } from "mdui/jq"
import useEventListener from "../useEventListener.ts" import useEventListener from "../useEventListener.ts"
import Element_Message from "./Message.tsx" import Element_Message from "./Message.tsx"
@@ -31,6 +31,7 @@ import JoinRequestsList from "./JoinRequestsList.tsx"
import getUrlForFileByHash from "../../getUrlForFileByHash.ts" import getUrlForFileByHash from "../../getUrlForFileByHash.ts"
import escapeHTML from "../../escapeHtml.ts" import escapeHTML from "../../escapeHtml.ts"
import GroupMembersList from "./GroupMembersList.tsx" import GroupMembersList from "./GroupMembersList.tsx"
import isMobileUI from "../isMobileUI.ts"
interface Args extends React.HTMLAttributes<HTMLElement> { interface Args extends React.HTMLAttributes<HTMLElement> {
target: string target: string
@@ -95,6 +96,18 @@ const markedInstance = new marked.Marked({
} }
}) })
interface MduiTabFitSizeArgs extends React.HTMLAttributes<HTMLElement & Tab> {
value: string
}
function MduiTabFitSize({ children, ...props }: MduiTabFitSizeArgs) {
return <mdui-tab {...props} style={{
...props?.style,
minWidth: 'fit-content',
}}>
{children}
</mdui-tab>
}
export default function ChatFragment({ target, showReturnButton, onReturnButtonClicked, openChatInfoDialog, openUserInfoDialog, ...props }: Args) { export default function ChatFragment({ target, showReturnButton, onReturnButtonClicked, openChatInfoDialog, openUserInfoDialog, ...props }: Args) {
const [messagesList, setMessagesList] = React.useState([] as Message[]) const [messagesList, setMessagesList] = React.useState([] as Message[])
const [chatInfo, setChatInfo] = React.useState({ const [chatInfo, setChatInfo] = React.useState({
@@ -114,6 +127,7 @@ export default function ChatFragment({ target, showReturnButton, onReturnButtonC
React.useEffect(() => { React.useEffect(() => {
$(containerTabRef.current!.shadowRoot).append(`<style>.container::after { height: 0 !important; }</style>`) $(containerTabRef.current!.shadowRoot).append(`<style>.container::after { height: 0 !important; }</style>`)
$(tabRef.current!.shadowRoot).append(`<style>.container::after { height: 0 !important; }</style>`) $(tabRef.current!.shadowRoot).append(`<style>.container::after { height: 0 !important; }</style>`)
; (!isMobileUI()) && $(tabRef.current!.shadowRoot).append(`<style>.no-scroll-bar::-webkit-scrollbar{width:0px !important}*::-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,0.5);background-clip:padding-box;min-height:28px;-webkit-border-radius:2em;-moz-border-radius:2em;border-radius:2em;transition:background-color 0.3s;cursor:pointer}*::-webkit-scrollbar-thumb:hover{background-color:rgba(144,147,153,0.3)}</style>`)
}, [target]) }, [target])
async function getChatInfoAndInit() { async function getChatInfoAndInit() {
@@ -347,17 +361,18 @@ export default function ChatFragment({ target, showReturnButton, onReturnButtonC
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
height: "100%", height: "100%",
overflowX: 'auto',
}}> }}>
{ {
chatInfo.is_member ? <> chatInfo.is_member ? <>
<mdui-tab value="Chat">{chatInfo.title}</mdui-tab> <MduiTabFitSize value="Chat">{chatInfo.title}</MduiTabFitSize>
{chatInfo.type == 'group' && chatInfo.is_admin && <mdui-tab value="NewMemberRequests"></mdui-tab>} {chatInfo.type == 'group' && chatInfo.is_admin && <MduiTabFitSize value="NewMemberRequests"></MduiTabFitSize>}
{chatInfo.type == 'group' && <mdui-tab value="GroupMembers"></mdui-tab>} {chatInfo.type == 'group' && <MduiTabFitSize value="GroupMembers"></MduiTabFitSize>}
</> </>
: <mdui-tab value="RequestJoin">{chatInfo.title}</mdui-tab> : <MduiTabFitSize value="RequestJoin">{chatInfo.title}</MduiTabFitSize>
} }
{chatInfo.type == 'group' && <mdui-tab value="Settings"></mdui-tab>} {chatInfo.type == 'group' && <MduiTabFitSize value="Settings"></MduiTabFitSize>}
<mdui-tab value="None" style={{ display: 'none' }}></mdui-tab> <MduiTabFitSize value="None" style={{ display: 'none' }}></MduiTabFitSize>
</mdui-tabs> </mdui-tabs>
<div style={{ <div style={{
flexGrow: '1', flexGrow: '1',
@@ -441,9 +456,20 @@ export default function ChatFragment({ target, showReturnButton, onReturnButtonC
{ {
(() => { (() => {
let date = new Date(0) let date = new Date(0)
let user: string
function timeAddZeroPrefix(t: number) {
if (t >= 0 && t < 10)
return '0' + t
return t + ''
}
return messagesList.map((msg) => { return messagesList.map((msg) => {
const lastDate = date const lastDate = date
const lastUser = user
date = new Date(msg.time) date = new Date(msg.time)
user = msg.user_id
const shouldShowTime = msg.user_id != null &&
(date.getMinutes() != lastDate.getMinutes() || date.getDate() != lastDate.getDate() || date.getMonth() != lastDate.getMonth() || date.getFullYear() != lastDate.getFullYear())
const msgElement = msg.user_id == null ? <SystemMessage><div dangerouslySetInnerHTML={{ const msgElement = msg.user_id == null ? <SystemMessage><div dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(markedInstance.parse(msg.text) as string, { __html: DOMPurify.sanitize(markedInstance.parse(msg.text) as string, {
@@ -455,6 +481,7 @@ export default function ChatFragment({ target, showReturnButton, onReturnButtonC
], ],
}) })
}} /></SystemMessage> : <Element_Message }} /></SystemMessage> : <Element_Message
noUserDisplay={lastUser == user && !shouldShowTime}
rawData={msg.text} rawData={msg.text}
renderHTML={DOMPurify.sanitize(markedInstance.parse(msg.text) as string, sanitizeConfig)} renderHTML={DOMPurify.sanitize(markedInstance.parse(msg.text) as string, sanitizeConfig)}
message={msg} message={msg}
@@ -467,18 +494,18 @@ export default function ChatFragment({ target, showReturnButton, onReturnButtonC
return ( return (
<> <>
{ {
msg.user_id != null && shouldShowTime
(date.getMinutes() != lastDate.getMinutes() || date.getDate() != lastDate.getDate() || date.getMonth() != lastDate.getMonth() || date.getFullYear() != lastDate.getFullYear())
&& <mdui-tooltip content={`${date.getFullYear()}${date.getMonth() + 1}${date.getDate()}${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`}> && <mdui-tooltip content={`${date.getFullYear()}${date.getMonth() + 1}${date.getDate()}${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`}>
<div style={{ <div style={{
fontSize: '87%', fontSize: '87%',
marginTop: '10px', marginTop: '13px',
marginBottom: '10px',
}}> }}>
{ {
(date.getFullYear() != lastDate.getFullYear() ? `${date.getFullYear()}` : '') (date.getFullYear() != lastDate.getFullYear() ? `${date.getFullYear()}` : '')
+ `${date.getMonth() + 1}` + `${date.getMonth() + 1}`
+ `${date.getDate()}` + `${date.getDate()}`
+ ` ${date.getHours()}:${date.getMinutes()}` + ` ${timeAddZeroPrefix(date.getHours())}:${timeAddZeroPrefix(date.getMinutes())}`
} }
</div> </div>
</mdui-tooltip> </mdui-tooltip>

View File

@@ -13,14 +13,6 @@ import User from "../../api/client_data/User.ts"
import getUrlForFileByHash from "../../getUrlForFileByHash.ts" import getUrlForFileByHash from "../../getUrlForFileByHash.ts"
import escapeHTML from "../../escapeHtml.ts" import escapeHTML from "../../escapeHtml.ts"
interface Args extends React.HTMLAttributes<HTMLElement> {
userId: string
rawData: string
renderHTML: string
message: Data_Message
openUserInfoDialog: (user: User | string) => void
}
function prettyFlatParsedMessage(html: string) { function prettyFlatParsedMessage(html: string) {
const elements = new DOMParser().parseFromString(html, 'text/html').body.children const elements = new DOMParser().parseFromString(html, 'text/html').body.children
// 纯文本直接处理 // 纯文本直接处理
@@ -72,7 +64,16 @@ function prettyFlatParsedMessage(html: string) {
return ret return ret
} }
export default function Message({ userId, rawData, renderHTML, message, openUserInfoDialog, ...props }: Args) { interface Args extends React.HTMLAttributes<HTMLElement> {
userId: string
noUserDisplay?: boolean
rawData: string
renderHTML: string
message: Data_Message
openUserInfoDialog: (user: User | string) => void
}
export default function Message({ userId, rawData, renderHTML, message, openUserInfoDialog, noUserDisplay, ...props }: Args) {
const isAtRight = Client.myUserProfile?.id == userId const isAtRight = Client.myUserProfile?.id == userId
const [nickName, setNickName] = React.useState("") const [nickName, setNickName] = React.useState("")
@@ -123,7 +124,7 @@ export default function Message({ userId, rawData, renderHTML, message, openUser
{...props}> {...props}>
<div <div
style={{ style={{
display: "flex", display: noUserDisplay ? 'none' : "flex",
justifyContent: isAtRight ? "flex-end" : "flex-start", justifyContent: isAtRight ? "flex-end" : "flex-start",
}}> }}>
{ {
@@ -168,7 +169,7 @@ export default function Message({ userId, rawData, renderHTML, message, openUser
maxWidth: 'var(--whitesilk-widget-message-maxwidth)', // (window.matchMedia('(pointer: fine)') && "50%") || (window.matchMedia('(pointer: coarse)') && "77%"), maxWidth: 'var(--whitesilk-widget-message-maxwidth)', // (window.matchMedia('(pointer: fine)') && "50%") || (window.matchMedia('(pointer: coarse)') && "77%"),
minWidth: "0%", minWidth: "0%",
[isAtRight ? "marginRight" : "marginLeft"]: "55px", [isAtRight ? "marginRight" : "marginLeft"]: "55px",
marginTop: "-5px", marginTop: noUserDisplay ? '5px' : "-5px",
alignSelf: isAtRight ? "flex-end" : "flex-start", alignSelf: isAtRight ? "flex-end" : "flex-start",
// boxShadow: isUsingFullDisplay ? 'inherit' : 'var(--mdui-elevation-level1)', // boxShadow: isUsingFullDisplay ? 'inherit' : 'var(--mdui-elevation-level1)',
// padding: isUsingFullDisplay ? undefined : "13px", // padding: isUsingFullDisplay ? undefined : "13px",

View File

@@ -60,7 +60,7 @@ export default function AllChatsList({
width: '100%', width: '100%',
}} {...props}> }} {...props}>
<mdui-text-field icon="search" type="search" clearable ref={searchRef} variant="outlined" placeholder="搜索..." style={{ <mdui-text-field icon="search" type="search" clearable ref={searchRef} variant="outlined" placeholder="搜索..." style={{
paddingTop: '5px', paddingTop: '12px',
paddingBottom: '13px', paddingBottom: '13px',
position: 'sticky', position: 'sticky',
top: '0', top: '0',

View File

@@ -74,7 +74,7 @@ export default function ContactsList({
zIndex: '10', zIndex: '10',
}}> }}>
<mdui-text-field icon="search" type="search" clearable ref={searchRef} variant="outlined" placeholder="搜索..." style={{ <mdui-text-field icon="search" type="search" clearable ref={searchRef} variant="outlined" placeholder="搜索..." style={{
marginTop: '5px', paddingTop: '12px',
}}></mdui-text-field> }}></mdui-text-field>
<mdui-list-item rounded style={{ <mdui-list-item rounded style={{
marginTop: '13px', marginTop: '13px',

View File

@@ -61,7 +61,7 @@ export default function RecentsList({
width: '100%', width: '100%',
}} {...props}> }} {...props}>
<mdui-text-field icon="search" type="search" clearable ref={searchRef} variant="outlined" placeholder="搜索..." style={{ <mdui-text-field icon="search" type="search" clearable ref={searchRef} variant="outlined" placeholder="搜索..." style={{
marginTop: '5px', paddingTop: '12px',
marginBottom: '13px', marginBottom: '13px',
position: 'sticky', position: 'sticky',
top: '0', top: '0',