feat(mira): P0 河道完善 3 task — Mira 頭像 / @mira 選擇性回覆 / 編輯器 popup
對應 tasks.md backlog #5a / #5b / #5c(leo 2026-05-16 P0),design.md §3.6.5。
#5a Mira 發文獨立頭像
- 新 MiraAvatar 元件(紫色漸層圓 + 🤖 emoji)
- isMiraSource() 判斷 post 來源是 leo(km-writer-direct/logseq/mobile/web/tg/rss)
還是 mira(ai-* / mira-* / 其他 test sources)
- PostCard header:showMira ? Mira 頭像 + 名「Mira」 : leo 頭像 + leo 名
#5b @mira 選擇性回覆(撤回每篇 auto-reply)
- 新 hasMiraMention() regex:偵測文字含 @mira(前後可有標點)
- PostComposer / BlockEditor / ReplyLine 三處 submit:只有 @mira 時 triggerAiReply
- triggerAiReply prompt 加 topic 抽取(@mira 後第一段到標點)+ scope hint
- wiki_synthesis trigger 跟 @mira 無關,每篇都跑(KB 副本同步)
- 不擋手動筆記(leo 隨手寫不需要 mira 每篇都回)
#5c 編輯器 popup 放大
- composer 加 ⇱ 放大按鈕 → 切到 fullscreen overlay popup
- popup 含大 textarea + 同 markdown toolbar + 同 submit 邏輯
- ⌘+Enter 發布、Esc 收起、外點 backdrop 收起
- 對應 leo「寫長文 textarea 太小」需求
CSS 加 .mira-avatar-mira / .mira-composer-popup-* 系列。
TS check pass。
This commit is contained in:
+189
-49
@@ -158,6 +158,12 @@ export default function MiraPage() {
|
|||||||
|
|
||||||
// ─── AI 回覆觸發器(fire-and-forget)──────────────────────
|
// ─── AI 回覆觸發器(fire-and-forget)──────────────────────
|
||||||
|
|
||||||
|
// P0 #5b:偵測文字含 @mira(mention) — 大小寫不限,前後可有標點
|
||||||
|
// 用法:leo 在貼文 / 留言寫 `@mira <主題>` → 觸發 mira reply
|
||||||
|
function hasMiraMention(text: string): boolean {
|
||||||
|
return /(^|[^\w])@mira\b/i.test(text);
|
||||||
|
}
|
||||||
|
|
||||||
async function triggerWikiSynthesis(opts: { apiKey: string; rawBlockId: string }) {
|
async function triggerWikiSynthesis(opts: { apiKey: string; rawBlockId: string }) {
|
||||||
// 觸發 arcrun wiki_synthesis workflow(arcrun-native public trigger endpoint)
|
// 觸發 arcrun wiki_synthesis workflow(arcrun-native public trigger endpoint)
|
||||||
// 不等結果(workflow 60-90s 含 2 次 claude_api pause/resume)
|
// 不等結果(workflow 60-90s 含 2 次 claude_api pause/resume)
|
||||||
@@ -187,10 +193,18 @@ async function triggerAiReply(opts: {
|
|||||||
parentBlockId: string;
|
parentBlockId: string;
|
||||||
pageName: string;
|
pageName: string;
|
||||||
}) {
|
}) {
|
||||||
|
// 抽 @mira 後面的 topic(同行第一段,到換行 / 句號 / 標點停)
|
||||||
|
const mentionMatch = opts.postContent.match(/@mira\s*([^\n。.!!??,,]*)/i);
|
||||||
|
const topic = mentionMatch?.[1]?.trim() || '';
|
||||||
|
const topicHint = topic
|
||||||
|
? `\nleo 用 \`@mira ${topic}\` 呼叫了你,所以這則對話的主題鎖定在「${topic}」。\n`
|
||||||
|
: '\nleo 用 \`@mira\` 呼叫了你,請針對訊息回覆。\n';
|
||||||
|
|
||||||
const prompt =
|
const prompt =
|
||||||
`用戶 leo 在 mira 河道發了這則貼文:\n\n` +
|
`用戶 leo 在 mira 河道發了這則訊息:\n\n` +
|
||||||
`「${opts.postContent}」\n\n` +
|
`「${opts.postContent}」\n\n` +
|
||||||
`請以 Mira 副駕 AI 的身份留言回應。\n` +
|
topicHint +
|
||||||
|
`\n請以 Mira 副駕 AI 的身份留言回應。\n` +
|
||||||
`規則:\n` +
|
`規則:\n` +
|
||||||
`- 繁體中文(台灣用語)\n` +
|
`- 繁體中文(台灣用語)\n` +
|
||||||
`- 簡短 1-3 段,務實,不客套\n` +
|
`- 簡短 1-3 段,務實,不客套\n` +
|
||||||
@@ -257,7 +271,9 @@ function PostComposer({
|
|||||||
const [text, setText] = useState('');
|
const [text, setText] = useState('');
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
const [err, setErr] = useState<string | null>(null);
|
const [err, setErr] = useState<string | null>(null);
|
||||||
|
const [expanded, setExpanded] = useState(false);
|
||||||
const taRef = useRef<HTMLTextAreaElement>(null);
|
const taRef = useRef<HTMLTextAreaElement>(null);
|
||||||
|
const popupTaRef = useRef<HTMLTextAreaElement>(null);
|
||||||
|
|
||||||
const submit = useCallback(async () => {
|
const submit = useCallback(async () => {
|
||||||
const trimmed = text.trim();
|
const trimmed = text.trim();
|
||||||
@@ -288,16 +304,18 @@ function PostComposer({
|
|||||||
const postBlockId = data.data.id;
|
const postBlockId = data.data.id;
|
||||||
setText('');
|
setText('');
|
||||||
|
|
||||||
// fire-and-forget 觸發 Mira AI 回覆(不擋用戶)
|
// P0 #5b:只有 @mira 時才觸發 Mira AI 回覆(撤回每篇 auto-reply)
|
||||||
void triggerAiReply({
|
// 對應 design.md §3.6.5「河道是 process 場 + Mira 是 KB 同步介面」
|
||||||
apiKey: me.api_key,
|
if (hasMiraMention(trimmed)) {
|
||||||
postContent: trimmed,
|
void triggerAiReply({
|
||||||
parentBlockId: postBlockId,
|
apiKey: me.api_key,
|
||||||
pageName,
|
postContent: trimmed,
|
||||||
});
|
parentBlockId: postBlockId,
|
||||||
|
pageName,
|
||||||
|
});
|
||||||
|
}
|
||||||
// 7B.3h:fire-and-forget 觸發 wiki_synthesis(browser → cypher.arcrun.dev,arcrun-native)
|
// 7B.3h:fire-and-forget 觸發 wiki_synthesis(browser → cypher.arcrun.dev,arcrun-native)
|
||||||
// 不走 watcher 是因為 cypher-executor 自己 fetch 自己 workers.dev URL 被 CF 1042 擋
|
// 跟 @mira 無關,每篇都跑 — 河道書寫永遠進 wiki KB 副本
|
||||||
// watcher 仍作為 cron-driven backup(漏掉的 raws 5 分鐘後補跑),但需先解 self-fetch 問題
|
|
||||||
void triggerWikiSynthesis({ apiKey: me.api_key, rawBlockId: postBlockId });
|
void triggerWikiSynthesis({ apiKey: me.api_key, rawBlockId: postBlockId });
|
||||||
onAiTriggered(pageName);
|
onAiTriggered(pageName);
|
||||||
|
|
||||||
@@ -312,42 +330,120 @@ function PostComposer({
|
|||||||
}, [text, submitting, me, onPosted, onAiTriggered]);
|
}, [text, submitting, me, onPosted, onAiTriggered]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mira-card mira-composer-card">
|
<>
|
||||||
<div className="mira-composer-row">
|
<div className="mira-card mira-composer-card">
|
||||||
<Avatar me={me} />
|
<div className="mira-composer-row">
|
||||||
<textarea
|
<Avatar me={me} />
|
||||||
ref={taRef}
|
<textarea
|
||||||
value={text}
|
ref={taRef}
|
||||||
onChange={e => setText(e.target.value)}
|
value={text}
|
||||||
onKeyDown={e => {
|
onChange={e => setText(e.target.value)}
|
||||||
if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
|
onKeyDown={e => {
|
||||||
e.preventDefault();
|
if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
|
||||||
submit();
|
e.preventDefault();
|
||||||
return;
|
submit();
|
||||||
}
|
return;
|
||||||
handleTabIndent(e, text, setText);
|
}
|
||||||
|
handleTabIndent(e, text, setText);
|
||||||
|
}}
|
||||||
|
placeholder="現在想分享什麼?(@mira 呼叫 Mira 回覆)"
|
||||||
|
rows={2}
|
||||||
|
disabled={submitting}
|
||||||
|
className="mira-composer-textarea"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mira-composer-actions">
|
||||||
|
<MarkdownToolbar textareaRef={taRef} value={text} setValue={setText} compact />
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
setExpanded(true);
|
||||||
|
setTimeout(() => popupTaRef.current?.focus(), 50);
|
||||||
|
}}
|
||||||
|
className="mira-btn-ghost mira-composer-expand"
|
||||||
|
title="放大編輯(⇱)"
|
||||||
|
aria-label="放大編輯"
|
||||||
|
>
|
||||||
|
⇱
|
||||||
|
</button>
|
||||||
|
{err && <span className="mira-msg-error">{err}</span>}
|
||||||
|
<span style={{ flex: 1 }} />
|
||||||
|
<span className="mira-kbd">⌘+Enter</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
disabled={submitting || !text.trim()}
|
||||||
|
onClick={submit}
|
||||||
|
className={`mira-btn-primary${!text.trim() ? ' disabled' : ''}`}
|
||||||
|
>
|
||||||
|
{submitting ? '送出中⋯' : '貼文'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Popup 放大編輯(FB 覆蓋版面風格)— P0 #5c */}
|
||||||
|
{expanded && (
|
||||||
|
<div
|
||||||
|
className="mira-composer-popup-backdrop"
|
||||||
|
onClick={(e) => {
|
||||||
|
if (e.target === e.currentTarget) setExpanded(false);
|
||||||
}}
|
}}
|
||||||
placeholder="現在想分享什麼?"
|
|
||||||
rows={2}
|
|
||||||
disabled={submitting}
|
|
||||||
className="mira-composer-textarea"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mira-composer-actions">
|
|
||||||
<MarkdownToolbar textareaRef={taRef} value={text} setValue={setText} compact />
|
|
||||||
{err && <span className="mira-msg-error">{err}</span>}
|
|
||||||
<span style={{ flex: 1 }} />
|
|
||||||
<span className="mira-kbd">⌘+Enter</span>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
disabled={submitting || !text.trim()}
|
|
||||||
onClick={submit}
|
|
||||||
className={`mira-btn-primary${!text.trim() ? ' disabled' : ''}`}
|
|
||||||
>
|
>
|
||||||
{submitting ? '送出中⋯' : '貼文'}
|
<div className="mira-composer-popup">
|
||||||
</button>
|
<header className="mira-composer-popup-header">
|
||||||
</div>
|
<Avatar me={me} size={32} />
|
||||||
</div>
|
<span className="mira-composer-popup-title">寫一篇</span>
|
||||||
|
<span style={{ flex: 1 }} />
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setExpanded(false)}
|
||||||
|
className="mira-btn-ghost"
|
||||||
|
title="收起"
|
||||||
|
aria-label="關閉"
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</header>
|
||||||
|
<textarea
|
||||||
|
ref={popupTaRef}
|
||||||
|
value={text}
|
||||||
|
onChange={e => setText(e.target.value)}
|
||||||
|
onKeyDown={e => {
|
||||||
|
if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
submit().then(() => setExpanded(false));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
setExpanded(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleTabIndent(e, text, setText);
|
||||||
|
}}
|
||||||
|
placeholder="現在想分享什麼?(Esc 收起、⌘+Enter 發布)"
|
||||||
|
disabled={submitting}
|
||||||
|
className="mira-composer-popup-textarea"
|
||||||
|
/>
|
||||||
|
<div className="mira-composer-popup-actions">
|
||||||
|
<MarkdownToolbar textareaRef={popupTaRef} value={text} setValue={setText} />
|
||||||
|
{err && <span className="mira-msg-error">{err}</span>}
|
||||||
|
<span style={{ flex: 1 }} />
|
||||||
|
<span className="mira-kbd">⌘+Enter</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
disabled={submitting || !text.trim()}
|
||||||
|
onClick={async () => {
|
||||||
|
await submit();
|
||||||
|
setExpanded(false);
|
||||||
|
}}
|
||||||
|
className={`mira-btn-primary${!text.trim() ? ' disabled' : ''}`}
|
||||||
|
>
|
||||||
|
{submitting ? '送出中⋯' : '貼文'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -373,6 +469,35 @@ function Avatar({ me, size = 40 }: { me: Me; size?: number }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mira 自有頭像(區別於 leo),用機器人 emoji + 紫色圓
|
||||||
|
// 對應 design.md §3.6.5 + tasks.md backlog #5a (P0)
|
||||||
|
function MiraAvatar({ size = 40 }: { size?: number }) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="mira-avatar mira-avatar-mira"
|
||||||
|
style={{ width: size, height: size, fontSize: size * 0.55 }}
|
||||||
|
title="Mira"
|
||||||
|
aria-label="Mira"
|
||||||
|
>
|
||||||
|
🤖
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用 source 判斷該 post 是 leo 寫的還是 mira 生成的
|
||||||
|
// 已知 leo 來源 → leo 頭像;其他(ai-* / mira-* / test sources)→ mira 頭像
|
||||||
|
function isMiraSource(source: string | null | undefined): boolean {
|
||||||
|
if (!source) return false;
|
||||||
|
const s = source.toLowerCase();
|
||||||
|
// leo 寫入的所有 channels
|
||||||
|
if (s.startsWith('km-writer-direct')) return false;
|
||||||
|
if (s.startsWith('logseq')) return false;
|
||||||
|
if (s === 'mobile' || s === 'web' || s.startsWith('tg') || s === 'telegram') return false;
|
||||||
|
if (s === 'rss') return false;
|
||||||
|
// 其他(ai-* / mira* / test sources)視為 mira
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// ─── ⋮ 選單 ──────────────────────────────────────────────
|
// ─── ⋮ 選單 ──────────────────────────────────────────────
|
||||||
|
|
||||||
type MenuItem = { label: string; onClick: () => void; danger?: boolean };
|
type MenuItem = { label: string; onClick: () => void; danger?: boolean };
|
||||||
@@ -646,15 +771,18 @@ function DocCard({
|
|||||||
setBlocks(curr => curr ? curr.map(b => b.id === id ? { ...b, content: newContent } : b) : null);
|
setBlocks(curr => curr ? curr.map(b => b.id === id ? { ...b, content: newContent } : b) : null);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const postSource = inferSource(blocks, doc.page_name);
|
||||||
|
const showMira = isMiraSource(postSource);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article className="mira-card mira-post" ref={cardRef}>
|
<article className="mira-card mira-post" ref={cardRef}>
|
||||||
{/* 頭部:作者區塊 */}
|
{/* 頭部:作者區塊(依 source 判斷頭像 + 名字) */}
|
||||||
<header className="mira-post-header">
|
<header className="mira-post-header">
|
||||||
<Avatar me={me} size={40} />
|
{showMira ? <MiraAvatar size={40} /> : <Avatar me={me} size={40} />}
|
||||||
<div className="mira-post-author">
|
<div className="mira-post-author">
|
||||||
<div className="mira-post-name">{me.display_name || me.email}</div>
|
<div className="mira-post-name">{showMira ? 'Mira' : (me.display_name || me.email)}</div>
|
||||||
<div className="mira-post-time">
|
<div className="mira-post-time">
|
||||||
<SourceBadge source={inferSource(blocks, doc.page_name)} />
|
<SourceBadge source={postSource} />
|
||||||
<span>·</span>
|
<span>·</span>
|
||||||
<RelTime when={doc.updated_at} />
|
<RelTime when={doc.updated_at} />
|
||||||
</div>
|
</div>
|
||||||
@@ -979,6 +1107,12 @@ function BlockLine({
|
|||||||
tags_json: '[]', refs_json: '[]', sort_order: 0,
|
tags_json: '[]', refs_json: '[]', sort_order: 0,
|
||||||
created_at: Math.floor(Date.now() / 1000), updated_at: Math.floor(Date.now() / 1000),
|
created_at: Math.floor(Date.now() / 1000), updated_at: Math.floor(Date.now() / 1000),
|
||||||
});
|
});
|
||||||
|
// P0 #5b:reply 內含 @mira → 觸發 mira 接龍(parent = 此 reply 形成 thread)
|
||||||
|
if (hasMiraMention(trimmed)) {
|
||||||
|
void triggerAiReply({
|
||||||
|
apiKey, postContent: trimmed, parentBlockId: data.data.id, pageName: docPageName,
|
||||||
|
});
|
||||||
|
}
|
||||||
setMode('view'); setDraft('');
|
setMode('view'); setDraft('');
|
||||||
} catch (e) { setErr(e instanceof Error ? e.message : String(e)); }
|
} catch (e) { setErr(e instanceof Error ? e.message : String(e)); }
|
||||||
finally { setSaving(false); }
|
finally { setSaving(false); }
|
||||||
@@ -1094,6 +1228,12 @@ function ReplyLine({
|
|||||||
tags_json: '[]', refs_json: '[]', sort_order: 0,
|
tags_json: '[]', refs_json: '[]', sort_order: 0,
|
||||||
created_at: Math.floor(Date.now() / 1000), updated_at: Math.floor(Date.now() / 1000),
|
created_at: Math.floor(Date.now() / 1000), updated_at: Math.floor(Date.now() / 1000),
|
||||||
});
|
});
|
||||||
|
// P0 #5b:reply 內含 @mira → 觸發 mira 接龍(parent = 此 reply 形成 thread)
|
||||||
|
if (hasMiraMention(trimmed)) {
|
||||||
|
void triggerAiReply({
|
||||||
|
apiKey, postContent: trimmed, parentBlockId: data.data.id, pageName: docPageName,
|
||||||
|
});
|
||||||
|
}
|
||||||
setMode('view'); setDraft('');
|
setMode('view'); setDraft('');
|
||||||
} catch (e) { setErr(e instanceof Error ? e.message : String(e)); }
|
} catch (e) { setErr(e instanceof Error ? e.message : String(e)); }
|
||||||
finally { setSaving(false); }
|
finally { setSaving(false); }
|
||||||
|
|||||||
@@ -139,6 +139,18 @@
|
|||||||
font-family: var(--mira-font-en);
|
font-family: var(--mira-font-en);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Mira 自有頭像 — 跟 leo 區分(紫色漸層 + 機器人 emoji) */
|
||||||
|
.mira-app .mira-avatar-mira {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
background: linear-gradient(135deg, oklch(0.45 0.15 280), oklch(0.55 0.18 300));
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
/* emoji 本身有色彩,背景僅當邊框襯托 */
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
/* ── post 貼文 ── */
|
/* ── post 貼文 ── */
|
||||||
.mira-app .mira-post {
|
.mira-app .mira-post {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -775,3 +787,75 @@
|
|||||||
border-bottom: 1px solid var(--mira-line-soft);
|
border-bottom: 1px solid var(--mira-line-soft);
|
||||||
padding-bottom: 4px;
|
padding-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Composer popup (P0 #5c 編輯器放大) ── */
|
||||||
|
.mira-app .mira-composer-expand {
|
||||||
|
font-size: 18px;
|
||||||
|
padding: 0 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--mira-text-3);
|
||||||
|
}
|
||||||
|
.mira-app .mira-composer-expand:hover {
|
||||||
|
color: var(--mira-text-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* backdrop 蓋全頁 + 居中 */
|
||||||
|
.mira-composer-popup-backdrop {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
z-index: 1000;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* popup card 大編輯區 */
|
||||||
|
.mira-composer-popup {
|
||||||
|
background: var(--mira-bg-1);
|
||||||
|
border: 1px solid var(--mira-line);
|
||||||
|
border-radius: var(--mira-radius-lg);
|
||||||
|
width: min(800px, 100%);
|
||||||
|
max-height: 90vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
font-family: var(--mira-font-zh);
|
||||||
|
color: var(--mira-text-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mira-composer-popup-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
border-bottom: 1px solid var(--mira-line);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mira-composer-popup-title {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mira-composer-popup-textarea {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 320px;
|
||||||
|
padding: 16px;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--mira-text-1);
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
resize: none;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mira-composer-popup-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-top: 1px solid var(--mira-line);
|
||||||
|
background: var(--mira-bg-0);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user