feat(desktop): align explore view with web reader
This commit is contained in:
@@ -7,11 +7,12 @@
|
||||
- Viewport: 420 × 820 CSS px at device scale factor 1; implementation image is 420 × 820 px.
|
||||
- Source image: 144 × 343 px and only shows the compact icon-navigation language. It does not define the requested all-projects dashboard.
|
||||
- State: signed-in light theme, “工作台 / 全部项目” selected, one pending task and one latest file. The content header is “全部项目” with its creation action aligned on the right.
|
||||
- Primary interactions tested: default all-projects homepage, task editor modal, explore view, project picker, project-only right navigation, document panel, AI panel, and profile dropdown. Console errors: none.
|
||||
- Primary interactions tested: default all-projects homepage, task editor modal, dataset-backed explore view, project picker, project-only right navigation, document panel, AI panel, and profile dropdown. Console errors: none.
|
||||
|
||||
## Evidence
|
||||
|
||||
- Full current implementation: `test-results/app-home.png`
|
||||
- Explore reading flow: `test-results/app-explore.png`
|
||||
- Earlier focused icon-rail comparison: `test-results/design-qa-rail-comparison.png`
|
||||
|
||||
The source rail is used only for the compact icon treatment. The latest requested behavior intentionally hides that rail in the all-projects dashboard and displays it only after selecting a specific project.
|
||||
@@ -25,10 +26,12 @@ No actionable P0, P1, or P2 findings.
|
||||
- Colors and visual tokens: workbench/explore selection uses the existing blue token, while statistic colors distinguish projects, pending work, files, and plan tasks without overpowering the task list.
|
||||
- Image and icon fidelity: the existing product mark is retained and all controls use the installed Arco icon set; no placeholder or hand-drawn image assets were added.
|
||||
- Copy and content: “工作台”, “探索”, and “全部项目” make scope explicit; its homepage shows only pending tasks and latest files. The project-only menu contains 概况、计划、资料、AI、计划任务 and no longer exposes 收集箱.
|
||||
- Explore parity: the desktop view now uses the `web_v1` dataset endpoints and mirrors its core source-selection, item-list, and detail-reading workflow in a narrow reading layout. With 全部项目 selected, the “沉淀到项目” action is intentionally disabled until a concrete project is selected.
|
||||
|
||||
## Comparison history
|
||||
|
||||
- Current pass: removed the completed-task section and upload action from the all-projects homepage, then removed the content-level “工作台” label and moved 创建项目 beside 全部项目. Re-captured `test-results/app-home.png` and verified that only pending tasks and latest files remain.
|
||||
- Explore pass: replaced the project-card placeholder with the dataset source and item reader. Captured `test-results/app-explore.png`; two sources and the selected article detail render without console errors.
|
||||
|
||||
## Follow-up polish
|
||||
|
||||
@@ -41,6 +44,7 @@ No actionable P0, P1, or P2 findings.
|
||||
- [x] Add all-project statistics, pending tasks, project creation, and latest files.
|
||||
- [x] Hide right navigation for all-projects and explore views.
|
||||
- [x] Remove 收集箱 and rename 周期计划 to 计划任务.
|
||||
- [x] Align top-level 探索 with the `web_v1` dataset reading flow.
|
||||
- [x] Verify key interaction paths and responsive composition.
|
||||
|
||||
final result: passed
|
||||
|
||||
@@ -23,6 +23,8 @@ const workspace = {
|
||||
cronPlans: [{ id: 'cron-1', projectId, title: '每周复盘', schedule: '0 17 * * 5', nextRun: null, enabled: true, lastResult: '', owner: '张明' }],
|
||||
}
|
||||
const expert = { id: 'expert-1', slug: 'product-manager', category: 'product', categoryName: '产品', name: '产品经理', description: '负责需求分析', emoji: '📘', color: '#165DFF' }
|
||||
const datasetSource = { id: 'source-1', name: '行业资讯', kind: 'rss', url: 'https://example.com/feed', iconUrl: '', description: '行业动态', enabled: true, builtIn: false, lastSyncedAt: null, itemCount: 1, createdAt: '2026-07-22T10:00:00Z', updatedAt: '2026-07-22T10:00:00Z' }
|
||||
const datasetItem = { id: 'item-1', sourceId: 'source-1', title: 'AI 工作台趋势', summary: '新的协作方式正在改变项目管理。', content: '这是来自数据源的探索内容,可在 Web 工作台继续管理数据源和同步任务。', url: '', imageUrl: '', status: 'unread', starred: false, publishedAt: '2026-07-22T10:00:00Z', createdAt: '2026-07-22T10:00:00Z', updatedAt: '2026-07-22T10:00:00Z' }
|
||||
|
||||
await page.route('http://localhost:9150/api/v1/**', async (route) => {
|
||||
const request = route.request(); const path = new URL(request.url()).pathname
|
||||
@@ -31,6 +33,8 @@ await page.route('http://localhost:9150/api/v1/**', async (route) => {
|
||||
if (path === '/api/v1/projects') return route.fulfill({ json: [workspace.project] })
|
||||
if (path === `/api/v1/projects/${projectId}/workspace`) return route.fulfill({ json: workspace })
|
||||
if (path === '/api/v1/ai-experts') return route.fulfill({ json: [expert] })
|
||||
if (path === '/api/v1/dataset-sources') return route.fulfill({ json: [datasetSource] })
|
||||
if (path === '/api/v1/dataset-items') return route.fulfill({ json: { items: [datasetItem], total: 1, limit: 50, offset: 0 } })
|
||||
if (path.includes('/tasks/') && request.method() === 'PATCH') return route.fulfill({ json: {} })
|
||||
return route.fulfill({ status: 404, json: { error: { code: 'not_found', message: '未配置的检查接口' } } })
|
||||
})
|
||||
@@ -52,7 +56,10 @@ await page.locator('.task-row').first().click()
|
||||
const taskModal = await page.getByText('编辑任务').count()
|
||||
await page.getByRole('button', { name: '取消' }).click()
|
||||
await page.getByRole('button', { name: '探索' }).click()
|
||||
const exploreProjects = await page.locator('.project-explore-card').count()
|
||||
await page.waitForSelector('.explore-detail')
|
||||
const exploreSources = await page.locator('.explore-source-strip button').count()
|
||||
const exploreDetail = await page.locator('.explore-detail').count()
|
||||
await page.screenshot({ path: 'test-results/app-explore.png', fullPage: true })
|
||||
await page.locator('.project-current').click()
|
||||
await page.getByRole('option', { name: '森林项目' }).click()
|
||||
await page.waitForSelector('.channel-rail')
|
||||
@@ -74,10 +81,10 @@ if (home.navCount !== 0) failures.push(`all-projects view must hide the right ra
|
||||
if (home.statCount !== 4) failures.push(`expected four summary cards, got ${home.statCount}`)
|
||||
if (home.pending !== 1 || home.completed !== 0) failures.push(`all-project task sections incorrect: ${JSON.stringify(home)}`)
|
||||
if (taskModal !== 1) failures.push('task editor did not open')
|
||||
if (exploreProjects !== 1) failures.push(`expected one project in explore, got ${exploreProjects}`)
|
||||
if (exploreSources !== 2 || exploreDetail !== 1) failures.push(`explore reader did not render: ${JSON.stringify({ exploreSources, exploreDetail })}`)
|
||||
if (documents !== 1) failures.push(`expected one document, got ${documents}`)
|
||||
if (experts !== 1) failures.push(`expected one AI expert, got ${experts}`)
|
||||
if (profileMenu !== 1) failures.push('profile menu did not open')
|
||||
if (errors.length) failures.push(`console errors: ${errors.join('; ')}`)
|
||||
console.log(JSON.stringify({ home, documents, experts, taskModal, exploreProjects, profileMenu, errors }, null, 2))
|
||||
console.log(JSON.stringify({ home, documents, experts, taskModal, exploreSources, exploreDetail, profileMenu, errors }, null, 2))
|
||||
if (failures.length) throw new Error(failures.join('\n'))
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { Alert, Avatar, Button, Dropdown, Empty, Form, Input, Menu, Modal, Select, Spin, Switch, Tag, Typography } from '@arco-design/web-react'
|
||||
import {
|
||||
IconApps, IconCheckCircle, IconCheckCircleFill, IconClockCircle, IconCompass, IconDown, IconFile,
|
||||
IconFolder, IconLeft, IconList, IconMoon, IconPlus, IconRight, IconRobot,
|
||||
IconApps, IconBook, IconCheckCircle, IconCheckCircleFill, IconClockCircle, IconCompass, IconDown, IconFile,
|
||||
IconFolder, IconLeft, IconList, IconMoon, IconPlus, IconRefresh, IconRight, IconRobot,
|
||||
IconSun, IconUser,
|
||||
} from '@arco-design/web-react/icon'
|
||||
import {
|
||||
ApiError, createProject, fetchProjectWorkspace, fetchProjects, getCurrentUser,
|
||||
listAIExperts, login, setApiBaseUrl, updateCurrentUser, updateTask,
|
||||
type ApiSession, type CurrentUser, type Expert, type Project, type Workspace, type WorkspaceTask,
|
||||
listAIExperts, listDatasetItems, listDatasetSources, login, setApiBaseUrl, updateCurrentUser, updateTask,
|
||||
type ApiSession, type CurrentUser, type DatasetItem, type DatasetSource, type Expert, type Project, type Workspace, type WorkspaceTask,
|
||||
} from './api'
|
||||
|
||||
const { Text, Title } = Typography
|
||||
@@ -113,7 +113,7 @@ export function App() {
|
||||
</section>
|
||||
|
||||
{error ? <Alert className="app-alert" type="error" content={error} closable onClose={() => setError('')} /> : null}
|
||||
<main className="app-main"><Spin loading={loading} block>{workspaceMode === 'explore' ? <ExplorePanel projects={projects} onOpen={(id) => { setProjectId(id); setWorkspaceMode('workbench') }} /> : !projectId ? <AllProjectsHome projects={projects} workspaces={visibleWorkspaces} tasks={tasks} onEditTask={setTaskEditor} onCreateProject={() => setProjectModalOpen(true)} /> : visibleWorkspaces.length ? <Content view={view} title={title} workspaces={visibleWorkspaces} tasks={tasks} onEditTask={setTaskEditor} onLoadAI={loadAI} experts={experts} /> : <Empty description="项目加载失败,请刷新后重试" />}</Spin></main>
|
||||
<main className="app-main"><Spin loading={loading} block>{workspaceMode === 'explore' ? <ExplorePanel session={session} projectName={activeProject?.name ?? ''} /> : !projectId ? <AllProjectsHome projects={projects} workspaces={visibleWorkspaces} tasks={tasks} onEditTask={setTaskEditor} onCreateProject={() => setProjectModalOpen(true)} /> : visibleWorkspaces.length ? <Content view={view} title={title} workspaces={visibleWorkspaces} tasks={tasks} onEditTask={setTaskEditor} onLoadAI={loadAI} experts={experts} /> : <Empty description="项目加载失败,请刷新后重试" />}</Spin></main>
|
||||
|
||||
{projectId && workspaceMode === 'workbench' ? <aside className="channel-rail" aria-label="项目子菜单">
|
||||
{navItems.map((item) => <button key={item.id} title={item.label} className={view === item.id ? 'active' : ''} onClick={() => { setView(item.id); if (item.id === 'ai') void loadAI() }}>{item.icon}<span>{item.label}</span></button>)}
|
||||
@@ -177,8 +177,30 @@ function AllProjectsHome({ projects, workspaces, tasks, onEditTask, onCreateProj
|
||||
return <section className="content-page all-projects-home"><div className="content-heading"><div><Title heading={4}>全部项目</Title></div><Button type="primary" icon={<IconPlus />} onClick={onCreateProject}>创建项目</Button></div><div className="stat-grid">{statItems.map((item) => <div className={`stat-card ${item.color}`} key={item.label}><strong>{item.value}</strong><span>{item.label}</span></div>)}</div><TaskBoard title="待办任务" subtitle="" tasks={tasks} onEditTask={onEditTask} showCompleted={false} /><section className="latest-files"><div className="section-label"><strong>最新文件</strong><span>{latestFiles.length}</span></div><div className="detail-list">{latestFiles.map((file) => <article className="detail-row" key={file.id}><IconFile /><div><strong>{file.name}</strong><p>{file.extension || file.mimeType || file.kind} · {formatDate(file.updatedAt)}</p></div><Tag>{file.projectName}</Tag></article>)}{!latestFiles.length && <Empty description="暂无文件" />}</div></section></section>
|
||||
}
|
||||
|
||||
function ExplorePanel({ projects, onOpen }: { projects: Project[]; onOpen: (id: string) => void }) {
|
||||
return <section className="content-page explore-page"><div className="content-heading"><div><Text type="secondary">探索</Text><Title heading={4}>项目空间</Title></div><Tag color="arcoblue">{projects.length} 个项目</Tag></div><div className="project-card-grid">{projects.map((project) => <button key={project.id} className="project-explore-card" onClick={() => onOpen(project.id)}><i style={{ background: project.background || '#165DFF' }}>{projectLabel(project)}</i><span><strong>{project.name}</strong><small>{project.description || project.identifier}</small></span><IconRight /></button>)}{!projects.length && <Empty description="还没有项目" />}</div></section>
|
||||
function ExplorePanel({ session, projectName }: { session: ApiSession; projectName: string }) {
|
||||
const [sources, setSources] = useState<DatasetSource[]>([])
|
||||
const [items, setItems] = useState<DatasetItem[]>([])
|
||||
const [sourceId, setSourceId] = useState('all')
|
||||
const [selectedId, setSelectedId] = useState('')
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState('')
|
||||
|
||||
useEffect(() => { void refreshSources() }, [session])
|
||||
useEffect(() => { void refreshItems() }, [session, sourceId])
|
||||
|
||||
async function refreshSources() {
|
||||
setLoading(true); setError('')
|
||||
try { setSources(await listDatasetSources(session)) } catch (requestError) { setError(errorMessage(requestError)) } finally { setLoading(false) }
|
||||
}
|
||||
async function refreshItems() {
|
||||
setLoading(true); setError('')
|
||||
try { const page = await listDatasetItems(session, sourceId === 'all' ? undefined : sourceId); setItems(page.items); setSelectedId((current) => page.items.some((item) => item.id === current) ? current : page.items[0]?.id ?? '') } catch (requestError) { setError(errorMessage(requestError)) } finally { setLoading(false) }
|
||||
}
|
||||
|
||||
const allCount = sources.reduce((total, source) => total + source.itemCount, 0)
|
||||
const selected = items.find((item) => item.id === selectedId) ?? items[0]
|
||||
const selectedSource = sources.find((source) => source.id === selected?.sourceId)
|
||||
return <section className="content-page explore-page"><div className="content-heading"><div><Title heading={4}>探索</Title><Text type="secondary">集中阅读数据源内容,筛选后沉淀到项目资料。</Text></div><Button type="text" shape="circle" aria-label="刷新探索内容" loading={loading} icon={<IconRefresh />} onClick={() => { void refreshSources(); void refreshItems() }} /></div>{error ? <Alert type="error" content={error} /> : null}<div className="explore-source-strip"><button className={sourceId === 'all' ? 'active' : ''} onClick={() => setSourceId('all')}><IconCompass /><span>全部</span><small>{allCount}</small></button>{sources.map((source) => <button className={sourceId === source.id ? 'active' : ''} key={source.id} onClick={() => setSourceId(source.id)}>{source.iconUrl ? <img src={source.iconUrl} alt="" /> : <IconBook />}<span>{source.name}</span><small>{source.itemCount}</small></button>)}</div><div className="explore-reader">{items.length ? <><div className="explore-item-list">{items.map((item) => <button className={item.id === selected?.id ? 'active' : ''} key={item.id} onClick={() => setSelectedId(item.id)}><span><b>{item.title}</b><small>{item.summary || '暂无摘要'}</small></span><i>{item.starred ? '★' : formatDate(item.publishedAt || item.createdAt)}</i></button>)}</div><article className="explore-detail"><div className="explore-detail-meta"><Tag color="arcoblue">{selectedSource?.name || '全部'}</Tag><small>{formatDate(selected?.publishedAt || selected?.createdAt || '')}</small></div><Title heading={5}>{selected?.title}</Title>{selected?.imageUrl ? <img src={selected.imageUrl} alt="" /> : null}<p>{selected?.content || selected?.summary || '暂无正文'}</p><div className="explore-detail-actions"><Button type="text" icon={<IconBook />} disabled={!projectName}>沉淀到{projectName || '项目'}</Button>{selected?.url ? <Button type="text" href={selected.url} target="_blank">打开原文</Button> : null}</div></article></> : <Empty description="暂无探索内容,请先在 Web 工作台添加或同步数据源" />}</div></section>
|
||||
}
|
||||
|
||||
function DocumentPanel({ title, workspaces }: { title: string; workspaces: Workspace[] }) {
|
||||
|
||||
@@ -57,6 +57,9 @@ export type Expert = { id: string; slug: string; category: string; categoryName:
|
||||
export type AISession = { id: string; projectId: string; title: string; context: string; status: string; expert: Expert | null; createdAt: string; updatedAt: string }
|
||||
export type CreateTaskInput = { title: string; description?: string; status?: string; dueAt?: string; tag?: string }
|
||||
export type UpdateTaskInput = { title: string; description?: string; status?: string; completed: boolean; nextProjectId?: string; tag?: string }
|
||||
export type DatasetSource = { id: string; name: string; kind: 'manual' | 'link' | 'rss'; url: string; iconUrl: string; description: string; enabled: boolean; builtIn: boolean; lastSyncedAt: string | null; itemCount: number; createdAt: string; updatedAt: string }
|
||||
export type DatasetItem = { id: string; sourceId: string; title: string; summary: string; content: string; url: string; imageUrl: string; status: 'unread' | 'read' | 'archived'; starred: boolean; publishedAt: string | null; createdAt: string; updatedAt: string }
|
||||
export type DatasetItemPage = { items: DatasetItem[]; total: number; limit: number; offset: number }
|
||||
|
||||
export class ApiError extends Error {
|
||||
constructor(public status: number, public code: string, message: string) {
|
||||
@@ -134,6 +137,18 @@ export function createAISession(session: ApiSession, projectId: string, input: {
|
||||
return apiRequest<AISession>(`/api/v1/projects/${projectId}/ai-sessions`, { method: 'POST', token: session.token, body: input })
|
||||
}
|
||||
|
||||
export function listDatasetSources(session: ApiSession) {
|
||||
useSessionBase(session)
|
||||
return apiRequest<DatasetSource[]>('/api/v1/dataset-sources', { token: session.token })
|
||||
}
|
||||
|
||||
export function listDatasetItems(session: ApiSession, sourceId?: string, offset = 0, limit = 50) {
|
||||
useSessionBase(session)
|
||||
const params = new URLSearchParams({ offset: String(offset), limit: String(limit) })
|
||||
if (sourceId) params.set('sourceId', sourceId)
|
||||
return apiRequest<DatasetItemPage>(`/api/v1/dataset-items?${params}`, { token: session.token })
|
||||
}
|
||||
|
||||
export async function checkServerConnection(server: string, signal?: AbortSignal) {
|
||||
try {
|
||||
const response = await fetch(`${normalizeBaseUrl(server)}/api/v1/status`, { signal })
|
||||
|
||||
@@ -67,6 +67,7 @@ button { font: inherit; }
|
||||
.expert-chips span { display: inline-flex; align-items: center; gap: 4px; padding: 4px 7px; border: 1px solid var(--line); border-radius: 999px; background: var(--surface); font-size: 11px; }.expert-chips i { display: grid; place-items: center; width: 17px; height: 17px; border-radius: 50%; font-style: normal; }
|
||||
.stat-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; margin-bottom: 13px; }.stat-card { display: grid; gap: 3px; padding: 11px; border: 1px solid var(--line); border-radius: 11px; background: var(--surface); }.stat-card strong { font-size: 22px; line-height: 1; }.stat-card span { color: var(--muted); font-size: 11px; }.stat-card.blue strong { color: #165dff; }.stat-card.orange strong { color: #ff7d00; }.stat-card.green strong { color: #00b42a; }.stat-card.purple strong { color: #722ed1; }
|
||||
.quick-workspace-actions { display: flex; gap: 8px; margin-bottom: 18px; }.all-projects-home .task-page { margin-top: 4px; }.latest-files { padding: 13px; border: 1px solid var(--line); border-radius: 12px; background: var(--surface); }.project-card-grid { display: grid; gap: 9px; }.project-explore-card { display: grid; grid-template-columns: 34px minmax(0, 1fr) auto; align-items: center; gap: 10px; width: 100%; padding: 13px; border: 1px solid var(--line); border-radius: 11px; color: var(--text); background: var(--surface); text-align: left; cursor: pointer; }.project-explore-card:hover { border-color: #9fc9ff; background: #f7faff; }.project-explore-card > i { display: grid; place-items: center; width: 34px; height: 34px; border-radius: 10px; color: #fff; font-style: normal; font-weight: 700; font-size: 11px; }.project-explore-card span { display: grid; min-width: 0; gap: 3px; }.project-explore-card strong, .project-explore-card small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }.project-explore-card strong { font-size: 13px; }.project-explore-card small { color: var(--muted); font-size: 11px; }
|
||||
.explore-source-strip { display: flex; gap: 7px; margin-bottom: 12px; overflow-x: auto; padding-bottom: 2px; }.explore-source-strip button { display: grid; grid-template-columns: 18px auto; grid-template-rows: auto auto; column-gap: 5px; align-items: center; min-width: 82px; padding: 8px; border: 1px solid var(--line); border-radius: 10px; color: var(--text); background: var(--surface); text-align: left; cursor: pointer; }.explore-source-strip button.active { border-color: #9bc7ff; background: #edf6ff; color: #165dff; }.explore-source-strip svg, .explore-source-strip img { grid-row: 1 / 3; width: 18px; height: 18px; border-radius: 5px; object-fit: cover; }.explore-source-strip span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; font-weight: 600; }.explore-source-strip small { color: var(--muted); font-size: 10px; }.explore-reader { display: grid; gap: 10px; }.explore-item-list { display: grid; gap: 6px; }.explore-item-list button { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; width: 100%; padding: 10px; border: 1px solid var(--line); border-radius: 10px; color: var(--text); background: var(--surface); text-align: left; cursor: pointer; }.explore-item-list button.active { border-color: #9bc7ff; background: #f0f7ff; }.explore-item-list span { display: grid; min-width: 0; gap: 4px; }.explore-item-list b, .explore-item-list small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }.explore-item-list b { font-size: 13px; }.explore-item-list small { color: var(--muted); font-size: 11px; }.explore-item-list i { align-self: center; color: #86909c; font-style: normal; font-size: 10px; }.explore-detail { padding: 14px; border: 1px solid var(--line); border-radius: 12px; background: var(--surface); }.explore-detail .arco-typography { margin-top: 9px; }.explore-detail-meta { display: flex; align-items: center; justify-content: space-between; }.explore-detail-meta small { color: var(--muted); font-size: 11px; }.explore-detail > img { width: 100%; max-height: 180px; margin: 4px 0 10px; border-radius: 9px; object-fit: cover; }.explore-detail p { margin: 0; color: var(--muted); font-size: 13px; line-height: 1.7; white-space: pre-wrap; }.explore-detail-actions { display: flex; gap: 4px; margin-top: 8px; }
|
||||
|
||||
.login-page { display: grid; min-height: 100vh; place-items: center; padding: 20px; background: #edf2f9; }.login-card { display: grid; width: min(100%, 360px); gap: 12px; padding: 28px; border: 1px solid #e5e6eb; border-radius: 16px; background: #fff; box-shadow: 0 18px 48px rgba(31, 35, 41, .12); }.login-card > img { width: 34px; }.login-card .arco-typography { margin: 0; }.login-card label { display: grid; gap: 5px; color: #4e5969; font-size: 12px; }
|
||||
.theme-dark .project-strip, .theme-dark .app-header, .theme-dark .task-section, .theme-dark .detail-row, .theme-dark .tag-filter button { color: var(--text); background: var(--surface); }.theme-dark .project-pill { color: var(--muted); }.theme-dark .project-pill.active, .theme-dark .channel-rail button.active { color: #8ac7ff; background: #1d4d77; }.theme-dark .task-row:hover { background: #293847; border-color: #477da7; }.theme-dark .section-label span { color: #8ac7ff; background: #1d4d77; }
|
||||
|
||||
Reference in New Issue
Block a user