Compare commits

..

4 Commits

Author SHA1 Message Date
CrescentLeaf
922791a0f5 feat(wip): 拉取历史消息 2026-01-09 00:44:53 +08:00
CrescentLeaf
6bfa1bf6b7 隐藏暂时无用的返回按钮 2026-01-09 00:26:21 +08:00
CrescentLeaf
6aa985bfca 分离群组设置 2026-01-09 00:21:07 +08:00
CrescentLeaf
7db95ed677 feat: 自定义多行富文本输入框! 2026-01-08 23:10:25 +08:00
3 changed files with 86 additions and 74 deletions

View File

@@ -3,8 +3,8 @@ import { $ } from "mdui"
export default class MduiPatchedTextAreaElement extends HTMLElement { export default class MduiPatchedTextAreaElement extends HTMLElement {
static observedAttributes = ['value', 'placeholder'] static observedAttributes = ['value', 'placeholder']
declare inputDiv?: HTMLDivElement declare inputDiv?: HTMLDivElement
declare inputPlaceHolderDiv?: HTMLDivElement
declare inputContainerDiv?: HTMLDivElement declare inputContainerDiv?: HTMLDivElement
declare placeholder?: string | number | null
constructor() { constructor() {
super() super()
@@ -18,8 +18,10 @@ export default class MduiPatchedTextAreaElement extends HTMLElement {
this.inputContainerDiv = new DOMParser().parseFromString(` this.inputContainerDiv = new DOMParser().parseFromString(`
<div style="overflow-y: auto; height: 100%;"> <div style="overflow-y: auto; height: 100%;">
<div role="textbox" aria-multiline="true" aria-labelledby="txtboxMultilineLabel" contentEditable="true" style="outline: none !important; color: rgb(var(--mdui-color-on-surface-variant)); display: inline-block; word-break: break-word; white-space: pre-wrap;"></div> <div role="textbox" aria-multiline="true" aria-labelledby="txtboxMultilineLabel" contentEditable="true" style="outline: none !important; color: rgb(var(--mdui-color-on-surface-variant)); display: inline-block; word-break: break-word; white-space: pre-wrap;"></div>
<div style="display: none;"></div>
<style> <style>
[contenteditable="true"]:empty:before {
content: attr(data-placeholder);
}
*::-webkit-scrollbar { *::-webkit-scrollbar {
width: 7px; width: 7px;
height: 10px; height: 10px;
@@ -51,15 +53,8 @@ export default class MduiPatchedTextAreaElement extends HTMLElement {
</div> </div>
`, 'text/html').body.firstChild as HTMLDivElement `, 'text/html').body.firstChild as HTMLDivElement
console.log(this.inputContainerDiv.children)
this.inputDiv = this.inputContainerDiv.children[0] as HTMLDivElement this.inputDiv = this.inputContainerDiv.children[0] as HTMLDivElement
this.inputPlaceHolderDiv = this.inputContainerDiv.children[1] as HTMLDivElement
this.inputDiv.addEventListener('input', () => {
// TODO: 修复 placeholder
this.inputPlaceHolderDiv!.style.display = this.value == '' ? '' : 'none'
this.inputDiv!.style.display = this.value == '' ? 'none' : ''
})
this.inputDiv.addEventListener('blur', () => { this.inputDiv.addEventListener('blur', () => {
if (this._lastValue !== this.value) { if (this._lastValue !== this.value) {
this._lastValue = this.value || '' this._lastValue = this.value || ''
@@ -73,16 +68,19 @@ export default class MduiPatchedTextAreaElement extends HTMLElement {
this.inputDiv.style.width = '100%' this.inputDiv.style.width = '100%'
$(this.inputDiv).attr('data-placeholder', $(this).attr('placeholder'))
shadow.appendChild(this.inputContainerDiv) shadow.appendChild(this.inputContainerDiv)
} }
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null) { attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null) {
console.log(this.inputDiv, name, oldValue, newValue)
switch (name) { switch (name) {
case 'value': { case 'value': {
this.value = newValue || '' this.value = newValue || ''
break break
} }
case 'placeholder': { case 'placeholder': {
this.inputPlaceHolderDiv && (this.inputPlaceHolderDiv.innerText = newValue || '') this.inputDiv && $(this.inputDiv).attr('data-placeholder', newValue)
break break
} }
} }

View File

@@ -44,10 +44,21 @@ export default function ChatFragment({
const inputRef = React.useRef<TextField>() const inputRef = React.useRef<TextField>()
const chatPagePanelRef = React.useRef<ChatPanelRef>() const chatPagePanelRef = React.useRef<ChatPanelRef>()
/**
* 发送消息, 成功则清空文本
*/
async function performSendMessage() { async function performSendMessage() {
await chatInfo.sendMessageOrThrow(inputRef.current!.value) await chatInfo.sendMessageOrThrow(inputRef.current!.value)
inputRef.current!.value = '' inputRef.current!.value = ''
} }
/**
* 拉取更多消息
* 本质是修改获取的偏移?
* WIP
*/
async function pullMoreMessages() {
}
return ( return (
<div style={{ <div style={{
@@ -66,13 +77,13 @@ export default function ChatFragment({
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
}}> }}>
{ {/* {
openedInDialog && <mdui-button-icon icon="arrow_back" onClick={() => AppState.closeChat()} style={{ openedInDialog && <mdui-button-icon icon="arrow_back" onClick={() => AppState.closeChat()} style={{
alignSelf: 'center', alignSelf: 'center',
marginLeft: '5px', marginLeft: '5px',
marginRight: '5px', marginRight: '5px',
}}></mdui-button-icon> }}></mdui-button-icon>
} } */}
<mdui-tabs ref={tabRef} value={tabItemSelected} style={{ <mdui-tabs ref={tabRef} value={tabItemSelected} style={{
position: 'sticky', position: 'sticky',
display: "flex", display: "flex",
@@ -227,68 +238,7 @@ export default function ChatFragment({
<input accept="image/*" type="file" name="上传对话头像"></input> <input accept="image/*" type="file" name="上传对话头像"></input>
</div> </div>
{ {
/* chatInfo.getType() == 'group' && <PreferenceLayout> // 群组设置?
<PreferenceUpdater.Provider value={groupPreferenceStore.createUpdater()}>
<PreferenceHeader
title="群组资料" />
<Preference
title="上传新的头像"
icon="image"
disabled={!chatInfo.isAdmin()}
onClick={() => {
uploadChatAvatarRef.current!.click()
}} />
<TextFieldPreference
title="设置群名称"
icon="edit"
id="group_title"
state={groupPreferenceStore.state.group_title || ''}
disabled={!chatInfo.isAdmin()} />
<TextFieldPreference
title="设置群别名"
icon="edit"
id="group_name"
description="以便于添加, 可留空"
state={groupPreferenceStore.state.group_name || ''}
disabled={!chatInfo.isAdmin()} />
<PreferenceHeader
title="入群设定" />
<SwitchPreference
title="允许入群"
icon="person_add"
id="allow_new_member_join"
disabled={!chatInfo.isAdmin()}
state={groupPreferenceStore.state.allow_new_member_join || false} />
<SwitchPreference
title="允许成员邀请"
description="目前压根没有这项功能, 甚至还不能查看成员列表, 以后再说吧"
id="allow_new_member_from_invitation"
icon="_"
disabled={true || !chatInfo.isAdmin()}
state={groupPreferenceStore.state.allow_new_member_from_invitation || false} />
<SelectPreference
title="入群验证方式"
icon="_"
id="new_member_join_method"
selections={{
disabled: "无需验证",
allowed_by_admin: "只需要管理员批准 (WIP)",
answered_and_allowed_by_admin: "需要回答问题并获得管理员批准 (WIP)",
}}
disabled={!chatInfo.isAdmin() || !groupPreferenceStore.state.allow_new_member_join}
state={groupPreferenceStore.state.new_member_join_method || 'disabled'} />
{
groupPreferenceStore.state.new_member_join_method == 'answered_and_allowed_by_admin'
&& <TextFieldPreference
title="设置问题"
icon="_"
id="answered_and_allowed_by_admin_question"
description="WIP"
state={groupPreferenceStore.state.answered_and_allowed_by_admin_question || ''}
disabled={true || !chatInfo.isAdmin()} />
}
</PreferenceUpdater.Provider>
</PreferenceLayout> */
} }
{ {
chatInfo.getType() == 'private' && ( chatInfo.getType() == 'private' && (

View File

@@ -0,0 +1,64 @@
export default function GroupSettingsFragment() {
/* chatInfo.getType() == 'group' && <PreferenceLayout>
<PreferenceUpdater.Provider value={groupPreferenceStore.createUpdater()}>
<PreferenceHeader
title="群组资料" />
<Preference
title="上传新的头像"
icon="image"
disabled={!chatInfo.isAdmin()}
onClick={() => {
uploadChatAvatarRef.current!.click()
}} />
<TextFieldPreference
title="设置群名称"
icon="edit"
id="group_title"
state={groupPreferenceStore.state.group_title || ''}
disabled={!chatInfo.isAdmin()} />
<TextFieldPreference
title="设置群别名"
icon="edit"
id="group_name"
description="以便于添加, 可留空"
state={groupPreferenceStore.state.group_name || ''}
disabled={!chatInfo.isAdmin()} />
<PreferenceHeader
title="入群设定" />
<SwitchPreference
title="允许入群"
icon="person_add"
id="allow_new_member_join"
disabled={!chatInfo.isAdmin()}
state={groupPreferenceStore.state.allow_new_member_join || false} />
<SwitchPreference
title="允许成员邀请"
description="目前压根没有这项功能, 甚至还不能查看成员列表, 以后再说吧"
id="allow_new_member_from_invitation"
icon="_"
disabled={true || !chatInfo.isAdmin()}
state={groupPreferenceStore.state.allow_new_member_from_invitation || false} />
<SelectPreference
title="入群验证方式"
icon="_"
id="new_member_join_method"
selections={{
disabled: "无需验证",
allowed_by_admin: "只需要管理员批准 (WIP)",
answered_and_allowed_by_admin: "需要回答问题并获得管理员批准 (WIP)",
}}
disabled={!chatInfo.isAdmin() || !groupPreferenceStore.state.allow_new_member_join}
state={groupPreferenceStore.state.new_member_join_method || 'disabled'} />
{
groupPreferenceStore.state.new_member_join_method == 'answered_and_allowed_by_admin'
&& <TextFieldPreference
title="设置问题"
icon="_"
id="answered_and_allowed_by_admin_question"
description="WIP"
state={groupPreferenceStore.state.answered_and_allowed_by_admin_question || ''}
disabled={true || !chatInfo.isAdmin()} />
}
</PreferenceUpdater.Provider>
</PreferenceLayout> */
}