rework the memory view and rename profiles to Agent
Replace the three separate memory editors with a card layout that shows the source file, a plain-text summary, the last modified time, and the character count for each section, so the stored files are visible without opening them. Rename the sidebar "用户" entry to "Agent" in both locales and add the memory.editTitle, memory.writeTo, and memory.charCount strings.
This commit is contained in:
@@ -204,7 +204,7 @@ export default {
|
||||
kanban: 'Kanban',
|
||||
workflow: 'Workflow',
|
||||
models: 'Models',
|
||||
profiles: 'Profiles',
|
||||
profiles: 'Agent',
|
||||
skills: 'Skills',
|
||||
plugins: 'Plugins',
|
||||
mcp: 'MCP',
|
||||
@@ -1195,6 +1195,9 @@ export default {
|
||||
title: 'Memory',
|
||||
refresh: 'Refresh',
|
||||
loadFailed: 'Failed to load memory',
|
||||
editTitle: 'Edit · {name}',
|
||||
writeTo: 'Write to',
|
||||
charCount: '{count} chars',
|
||||
myNotes: 'My Notes',
|
||||
noNotes: 'No notes yet.',
|
||||
notesPlaceholder: 'Write your notes...',
|
||||
|
||||
@@ -204,7 +204,7 @@ export default {
|
||||
kanban: '看板',
|
||||
workflow: '工作流',
|
||||
models: '模型',
|
||||
profiles: '用户',
|
||||
profiles: 'Agent',
|
||||
plugins: '插件',
|
||||
mcp: 'MCP',
|
||||
skills: '技能',
|
||||
@@ -1195,6 +1195,9 @@ export default {
|
||||
title: '记忆',
|
||||
refresh: '刷新',
|
||||
loadFailed: '加载记忆失败',
|
||||
editTitle: '修改 · {name}',
|
||||
writeTo: '写入文件',
|
||||
charCount: '{count} 字符',
|
||||
myNotes: '我的笔记',
|
||||
noNotes: '暂无笔记。',
|
||||
notesPlaceholder: '输入笔记内容...',
|
||||
|
||||
@@ -2,19 +2,40 @@
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { useMessage } from '@/composables/useMessage'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import MarkdownRenderer from '@/components/hermes/chat/MarkdownRenderer.vue'
|
||||
import { fetchMemory, saveMemory, type MemoryData } from '@/api/hermes/skills'
|
||||
import { useProfilesStore } from '@/stores/hermes/profiles'
|
||||
|
||||
type MemorySection = 'memory' | 'user' | 'soul'
|
||||
|
||||
const { t } = useI18n()
|
||||
const message = useMessage()
|
||||
const profilesStore = useProfilesStore()
|
||||
const loading = ref(false)
|
||||
const data = ref<MemoryData | null>(null)
|
||||
const editingSection = ref<'memory' | 'user' | 'soul' | null>(null)
|
||||
const editingSection = ref<MemorySection | null>(null)
|
||||
const editContent = ref('')
|
||||
const saving = ref(false)
|
||||
|
||||
// 三段记忆固定对应三个文件,落盘路径在服务端决定,前端只做展示。
|
||||
const SECTION_FILES: Record<MemorySection, string> = {
|
||||
memory: 'memories/MEMORY.md',
|
||||
user: 'memories/USER.md',
|
||||
soul: 'SOUL.md',
|
||||
}
|
||||
|
||||
const SECTIONS: { key: MemorySection; labelKey: string }[] = [
|
||||
{ key: 'memory', labelKey: 'memory.myNotes' },
|
||||
{ key: 'user', labelKey: 'memory.userProfile' },
|
||||
{ key: 'soul', labelKey: 'memory.soul' },
|
||||
]
|
||||
|
||||
// 各段的修改时间在接口里是平铺字段,key 不可由 section 直接拼出(soul_mtime 等)。
|
||||
const MTIME_KEY: Record<MemorySection, 'memory_mtime' | 'user_mtime' | 'soul_mtime'> = {
|
||||
memory: 'memory_mtime',
|
||||
user: 'user_mtime',
|
||||
soul: 'soul_mtime',
|
||||
}
|
||||
|
||||
onMounted(loadMemory)
|
||||
|
||||
async function loadMemory() {
|
||||
@@ -32,7 +53,7 @@ async function loadMemory() {
|
||||
}
|
||||
}
|
||||
|
||||
function startEdit(section: 'memory' | 'user' | 'soul') {
|
||||
function startEdit(section: MemorySection) {
|
||||
editingSection.value = section
|
||||
editContent.value = data.value?.[section] || ''
|
||||
}
|
||||
@@ -59,7 +80,7 @@ async function handleSave() {
|
||||
}
|
||||
|
||||
function formatTime(ts: number | null): string {
|
||||
if (!ts) return ''
|
||||
if (!ts) return '—'
|
||||
return new Date(ts).toLocaleString([], {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
@@ -68,193 +89,181 @@ function formatTime(ts: number | null): string {
|
||||
})
|
||||
}
|
||||
|
||||
const memoryEmpty = computed(() => !data.value?.memory?.trim())
|
||||
const userEmpty = computed(() => !data.value?.user?.trim())
|
||||
const soulEmpty = computed(() => !data.value?.soul?.trim())
|
||||
// 存储态用 § 分隔段落;展示与摘要都按纯文本处理。
|
||||
function plainText(raw: string | undefined): string {
|
||||
return (raw || '').replace(/§/g, ' ').replace(/\s+/g, ' ').trim()
|
||||
}
|
||||
|
||||
const displayMemory = computed(() => (data.value?.memory || '').replace(/§/g, '\n\n'))
|
||||
const displayUser = computed(() => (data.value?.user || '').replace(/§/g, '\n\n'))
|
||||
const displaySoul = computed(() => (data.value?.soul || '').replace(/§/g, '\n\n'))
|
||||
interface MemoryCard {
|
||||
key: MemorySection
|
||||
title: string
|
||||
file: string
|
||||
summary: string
|
||||
mtime: number | null
|
||||
chars: number
|
||||
empty: boolean
|
||||
// 进度条按 28 字符折算 1%,与原型一致。
|
||||
percent: number
|
||||
}
|
||||
|
||||
const cards = computed<MemoryCard[]>(() =>
|
||||
SECTIONS.map(({ key, labelKey }) => {
|
||||
const raw = data.value?.[key] || ''
|
||||
const plain = plainText(raw)
|
||||
const chars = raw.length
|
||||
return {
|
||||
key,
|
||||
title: t(labelKey),
|
||||
file: SECTION_FILES[key],
|
||||
summary: plain.length > 120 ? `${plain.slice(0, 120)}…` : plain,
|
||||
mtime: data.value?.[MTIME_KEY[key]] ?? null,
|
||||
chars,
|
||||
empty: !raw.trim(),
|
||||
percent: Math.min(100, Math.max(3, Math.round(chars / 28))),
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
const SECTION_LABEL_KEY: Record<MemorySection, string> = {
|
||||
memory: 'memory.myNotes',
|
||||
user: 'memory.userProfile',
|
||||
soul: 'memory.soul',
|
||||
}
|
||||
|
||||
const SECTION_PLACEHOLDER_KEY: Record<MemorySection, string> = {
|
||||
memory: 'memory.notesPlaceholder',
|
||||
user: 'memory.profilePlaceholder',
|
||||
soul: 'memory.soulPlaceholder',
|
||||
}
|
||||
|
||||
const SECTION_EMPTY_KEY: Record<MemorySection, string> = {
|
||||
memory: 'memory.noNotes',
|
||||
user: 'memory.noProfile',
|
||||
soul: 'memory.noSoul',
|
||||
}
|
||||
|
||||
const editingTitle = computed(() =>
|
||||
editingSection.value
|
||||
? t('memory.editTitle', { name: t(SECTION_LABEL_KEY[editingSection.value]) })
|
||||
: '',
|
||||
)
|
||||
|
||||
const editingFile = computed(() => (editingSection.value ? SECTION_FILES[editingSection.value] : ''))
|
||||
|
||||
const editingPlaceholder = computed(() =>
|
||||
editingSection.value ? t(SECTION_PLACEHOLDER_KEY[editingSection.value]) : '',
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="memory-view">
|
||||
<header class="page-header">
|
||||
<h2 class="header-title">{{ t('memory.title') }}</h2>
|
||||
<a-button size="small" type="text" @click="loadMemory">
|
||||
<template #icon>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="23 4 23 10 17 10" />
|
||||
<path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10" />
|
||||
</svg>
|
||||
</template>
|
||||
{{ t('memory.refresh') }}
|
||||
</a-button>
|
||||
<div class="header-actions">
|
||||
<a-button size="small" type="text" :loading="loading" @click="loadMemory">
|
||||
<template #icon>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="23 4 23 10 17 10" />
|
||||
<path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10" />
|
||||
</svg>
|
||||
</template>
|
||||
{{ t('memory.refresh') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="memory-content">
|
||||
<div class="memory-content cy-view">
|
||||
<div v-if="loading && !data" class="memory-loading">{{ t('common.loading') }}</div>
|
||||
<div v-else class="memory-sections">
|
||||
<!-- My Notes -->
|
||||
<div class="memory-section cy-panel">
|
||||
<div class="section-header cy-panel-h">
|
||||
<span class="section-icon">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
|
||||
<div v-else class="memcards">
|
||||
<article v-for="card in cards" :key="card.key" class="memcard cy-panel">
|
||||
<header class="memcard-h cy-panel-h">
|
||||
<span class="memcard-t">
|
||||
<span class="mi">
|
||||
<svg v-if="card.key === 'memory'" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
|
||||
<polyline points="14 2 14 8 20 8" />
|
||||
<line x1="16" y1="13" x2="8" y2="13" />
|
||||
<line x1="16" y1="17" x2="8" y2="17" />
|
||||
</svg>
|
||||
</span>
|
||||
<h3 class="section-title">{{ t('memory.myNotes') }}</h3>
|
||||
<span v-if="data?.memory_mtime" class="cy-meta">{{ formatTime(data.memory_mtime) }}</span>
|
||||
<div class="cy-panel-actions">
|
||||
<a-button v-if="editingSection !== 'memory'" size="mini" type="text" @click="startEdit('memory')">
|
||||
<template #icon>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
|
||||
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" />
|
||||
</svg>
|
||||
</template>
|
||||
{{ t('common.edit') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- View mode -->
|
||||
<div v-if="editingSection !== 'memory'" class="section-body cy-panel-b">
|
||||
<MarkdownRenderer v-if="!memoryEmpty" :content="displayMemory" />
|
||||
<p v-else class="empty-text">{{ t('memory.noNotes') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Edit mode -->
|
||||
<div v-else class="section-edit cy-panel-b">
|
||||
<textarea
|
||||
v-model="editContent"
|
||||
class="edit-textarea"
|
||||
:placeholder="t('memory.notesPlaceholder')"
|
||||
spellcheck="false"
|
||||
></textarea>
|
||||
<div class="edit-actions">
|
||||
<a-button size="small" @click="cancelEdit">{{ t('common.cancel') }}</a-button>
|
||||
<a-button size="small" type="primary" :loading="saving" @click="handleSave">{{ t('common.save') }}</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- User Profile -->
|
||||
<div class="memory-section cy-panel">
|
||||
<div class="section-header cy-panel-h">
|
||||
<span class="section-icon">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<svg v-else-if="card.key === 'user'" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
|
||||
<circle cx="12" cy="7" r="4" />
|
||||
</svg>
|
||||
</span>
|
||||
<h3 class="section-title">{{ t('memory.userProfile') }}</h3>
|
||||
<span v-if="data?.user_mtime" class="cy-meta">{{ formatTime(data.user_mtime) }}</span>
|
||||
<div class="cy-panel-actions">
|
||||
<a-button v-if="editingSection !== 'user'" size="mini" type="text" @click="startEdit('user')">
|
||||
<template #icon>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
|
||||
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" />
|
||||
</svg>
|
||||
</template>
|
||||
{{ t('common.edit') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- View mode -->
|
||||
<div v-if="editingSection !== 'user'" class="section-body cy-panel-b">
|
||||
<MarkdownRenderer v-if="!userEmpty" :content="displayUser" />
|
||||
<p v-else class="empty-text">{{ t('memory.noProfile') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Edit mode -->
|
||||
<div v-else class="section-edit cy-panel-b">
|
||||
<textarea
|
||||
v-model="editContent"
|
||||
class="edit-textarea"
|
||||
:placeholder="t('memory.profilePlaceholder')"
|
||||
spellcheck="false"
|
||||
></textarea>
|
||||
<div class="edit-actions">
|
||||
<a-button size="small" @click="cancelEdit">{{ t('common.cancel') }}</a-button>
|
||||
<a-button size="small" type="primary" :loading="saving" @click="handleSave">{{ t('common.save') }}</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Soul -->
|
||||
<div class="memory-section cy-panel">
|
||||
<div class="section-header cy-panel-h">
|
||||
<span class="section-icon">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<svg v-else width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z" />
|
||||
<path d="M8 14s1.5 2 4 2 4-2 4-2" />
|
||||
<line x1="9" y1="9" x2="9.01" y2="9" />
|
||||
<line x1="15" y1="9" x2="15.01" y2="9" />
|
||||
</svg>
|
||||
</span>
|
||||
<h3 class="section-title">{{ t('memory.soul') }}</h3>
|
||||
<span v-if="data?.soul_mtime" class="cy-meta">{{ formatTime(data.soul_mtime) }}</span>
|
||||
<div class="cy-panel-actions">
|
||||
<a-button v-if="editingSection !== 'soul'" size="mini" type="text" @click="startEdit('soul')">
|
||||
<template #icon>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
|
||||
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" />
|
||||
</svg>
|
||||
</template>
|
||||
{{ t('common.edit') }}
|
||||
</a-button>
|
||||
</div>
|
||||
<h3>{{ card.title }}</h3>
|
||||
</span>
|
||||
<div class="cy-panel-actions">
|
||||
<a-button size="mini" type="text" @click="startEdit(card.key)">
|
||||
<template #icon>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
|
||||
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" />
|
||||
</svg>
|
||||
</template>
|
||||
{{ t('common.edit') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- View mode -->
|
||||
<div v-if="editingSection !== 'soul'" class="section-body cy-panel-b">
|
||||
<MarkdownRenderer v-if="!soulEmpty" :content="displaySoul" />
|
||||
<p v-else class="empty-text">{{ t('memory.noSoul') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Edit mode -->
|
||||
<div v-else class="section-edit cy-panel-b">
|
||||
<textarea
|
||||
v-model="editContent"
|
||||
class="edit-textarea"
|
||||
:placeholder="t('memory.soulPlaceholder')"
|
||||
spellcheck="false"
|
||||
></textarea>
|
||||
<div class="edit-actions">
|
||||
<a-button size="small" @click="cancelEdit">{{ t('common.cancel') }}</a-button>
|
||||
<a-button size="small" type="primary" :loading="saving" @click="handleSave">{{ t('common.save') }}</a-button>
|
||||
</div>
|
||||
<div class="memcard-b">
|
||||
<p v-if="!card.empty" class="memcard-sum">{{ card.summary }}</p>
|
||||
<p v-else class="memcard-sum empty-text">{{ t(SECTION_EMPTY_KEY[card.key]) }}</p>
|
||||
<div class="memcard-meta">
|
||||
<span class="memcard-file">{{ card.file }}</span>
|
||||
<span class="grow"></span>
|
||||
<span class="memcard-time">{{ formatTime(card.mtime) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="memcard-bar">
|
||||
<span class="cy-meter"><i :style="{ width: `${card.percent}%` }"></i></span>
|
||||
<span class="memcard-chars">{{ t('memory.charCount', { count: card.chars }) }}</span>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a-modal
|
||||
:visible="editingSection !== null"
|
||||
:title="editingTitle"
|
||||
width="min(1080px, calc(100vw - 32px))"
|
||||
:mask-closable="!saving"
|
||||
:closable="!saving"
|
||||
:esc-to-close="!saving"
|
||||
@cancel="cancelEdit"
|
||||
>
|
||||
<div class="edit-body">
|
||||
<div class="cy-kv">
|
||||
<div class="cy-kr">
|
||||
<span class="cy-kk">{{ t('memory.writeTo') }}</span>
|
||||
<span class="cy-kv-v edit-file">{{ editingFile }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<textarea
|
||||
v-model="editContent"
|
||||
class="edit-textarea"
|
||||
:placeholder="editingPlaceholder"
|
||||
spellcheck="false"
|
||||
></textarea>
|
||||
</div>
|
||||
<template #footer>
|
||||
<a-button size="small" :disabled="saving" @click="cancelEdit">{{ t('common.cancel') }}</a-button>
|
||||
<a-button size="small" type="primary" :loading="saving" @click="handleSave">{{ t('common.save') }}</a-button>
|
||||
</template>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use '@/styles/variables' as *;
|
||||
|
||||
.memory-view {
|
||||
height: calc(100 * var(--vh));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.memory-content {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
padding: 20px 22px 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.memory-loading {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
@@ -264,82 +273,166 @@ const displaySoul = computed(() => (data.value?.soul || '').replace(/§/g, '\n\n
|
||||
color: $text-muted;
|
||||
}
|
||||
|
||||
.memory-sections {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
// 三张卡片横排,宽度不足时自动折行 —— 对应原型 .memcards。
|
||||
// 最小列宽比原型的 280px 更紧:侧栏占了 240px,
|
||||
// 280px 的门槛会让中等宽度下折成 2+1,原型是三连排。
|
||||
.memcards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
|
||||
gap: 14px;
|
||||
margin-top: 4px;
|
||||
|
||||
@media (max-width: $breakpoint-mobile) {
|
||||
flex-direction: column;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.memory-section {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
.memcard {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
transition: border-color $transition-fast, transform $transition-fast;
|
||||
|
||||
// 三栏并排,不吃 .cy-panel 的纵向堆叠间距。
|
||||
& + .memory-section {
|
||||
// 三卡并排,不吃 .cy-panel 的纵向堆叠间距。
|
||||
& + .memcard {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: rgba(var(--accent-primary-rgb), 0.38);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
}
|
||||
|
||||
.section-header {
|
||||
.memcard-h {
|
||||
flex-shrink: 0;
|
||||
background: $bg-secondary;
|
||||
}
|
||||
|
||||
.section-icon {
|
||||
color: $text-faint;
|
||||
.memcard-t {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-family: $font-code;
|
||||
font-size: 13.5px;
|
||||
font-weight: 600;
|
||||
line-height: 1.3;
|
||||
color: $text-primary;
|
||||
}
|
||||
|
||||
.mi {
|
||||
display: flex;
|
||||
color: $accent-primary;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.section-body {
|
||||
.memcard-b {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 11px;
|
||||
padding: 13px 14px;
|
||||
}
|
||||
|
||||
.memcard-sum {
|
||||
margin: 0;
|
||||
font-size: 12.5px;
|
||||
line-height: 1.72;
|
||||
color: $text-secondary;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 5;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
color: $text-muted;
|
||||
font-style: italic;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.section-edit {
|
||||
flex: 1;
|
||||
.memcard-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: auto;
|
||||
padding-top: 10px;
|
||||
border-top: 1px dashed $border-light;
|
||||
|
||||
.edit-textarea {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
padding: 12px;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: $radius-sm;
|
||||
background: $bg-input;
|
||||
color: $text-primary;
|
||||
font-family: $font-code;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
resize: none;
|
||||
outline: none;
|
||||
|
||||
&:focus {
|
||||
border-color: $accent-primary;
|
||||
.grow {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-actions {
|
||||
.memcard-file {
|
||||
font-family: $font-code;
|
||||
font-size: 11px;
|
||||
color: $text-faint;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.memcard-time {
|
||||
font-size: 11px;
|
||||
color: $text-faint;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.memcard-bar {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 9px 14px;
|
||||
border-top: 1px solid $border-light;
|
||||
background: $bg-secondary;
|
||||
|
||||
.cy-meter {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.memcard-chars {
|
||||
font-size: 11px;
|
||||
color: $text-muted;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.edit-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.edit-file {
|
||||
font-family: $font-code;
|
||||
font-size: 12.5px;
|
||||
color: $text-primary;
|
||||
}
|
||||
|
||||
.edit-textarea {
|
||||
width: 100%;
|
||||
// 弹窗比原型更宽更高,编辑区跟着放大。
|
||||
min-height: min(60vh, 560px);
|
||||
resize: vertical;
|
||||
outline: none;
|
||||
padding: 12px 13px;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: $radius-md;
|
||||
background: $bg-input;
|
||||
color: $text-primary;
|
||||
font-family: $font-code;
|
||||
font-size: 12.5px;
|
||||
line-height: 1.72;
|
||||
|
||||
&:focus {
|
||||
border-color: $accent-primary;
|
||||
box-shadow: 0 0 0 3px var(--accent-soft);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user