feat: resize todo panes and collapse note groups
Let users drag the to-do list/detail divider and remember its width. Note headings independently collapse task rows without clearing selected details. Use a 1px divider with a transparent drag target and compact spacing. Validation: 115 focused tests including native Electron input; typecheck and lint passed. Release note: Resize the Magic Notes to-do list with a thin draggable divider and collapse tasks by source note.
This commit is contained in:
@@ -754,7 +754,7 @@ describe('MagicNotesWorkspace detail behavior', () => {
|
||||
await openTodo()
|
||||
const first = screen.getByRole('button', { name: /核对发布材料.*发布笔记/ })
|
||||
expect(screen.getByRole('button', { name: '切换到笔记' })).toBeVisible()
|
||||
expect(screen.queryByRole('separator')).not.toBeInTheDocument()
|
||||
expect(screen.getByRole('separator', { name: '调整待办列表与详情宽度' })).toBeInTheDocument()
|
||||
expect(screen.queryByRole('button', { name: '返回总览' })).not.toBeInTheDocument()
|
||||
expect(first).toHaveAttribute('aria-controls', `magic-todo-detail-${todo.id}`)
|
||||
const list = first.closest('.magic-notes-list')!
|
||||
@@ -764,7 +764,7 @@ describe('MagicNotesWorkspace detail behavior', () => {
|
||||
const toolbar = screen.getByRole('searchbox').closest('.magic-todo-toolbar')!
|
||||
expect(toolbar).toContainElement(screen.getByRole('group', { name: '筛选待办' }))
|
||||
const css = readFileSync('src/renderer/src/styles.css', 'utf8')
|
||||
expect(css).toMatch(/\.magic-todo-workspace\s*\{[^}]*grid-template-columns: minmax\(280px, 34%\) minmax\(0, 1fr\)/)
|
||||
expect(css).toContain('grid-template-columns: var(--magic-notes-todo-width, 320px) 1px minmax(0, 1fr)')
|
||||
expect(css).toContain('@container magic-notes-page (max-width: 700px)')
|
||||
expect(css).toMatch(/\.magic-todo-workspace--selected > \.magic-notes-list,\s*\.magic-todo-detail--empty\s*\{\s*display: none/)
|
||||
fireEvent.click(screen.getByRole('button', { name: /准备演示.*演示笔记/ }))
|
||||
@@ -786,6 +786,30 @@ describe('MagicNotesWorkspace detail behavior', () => {
|
||||
expect(listTodos).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('resizes the todo split and collapses note groups without clearing the selected detail', async () => {
|
||||
const view = render(<MagicNotesWorkspace onNotify={onNotify} />)
|
||||
await openTodo()
|
||||
const separator = screen.getByRole('separator', { name: '调整待办列表与详情宽度' })
|
||||
Object.defineProperties(separator, { setPointerCapture: { value: vi.fn() }, hasPointerCapture: { value: () => true }, releasePointerCapture: { value: vi.fn() } })
|
||||
fireEvent.pointerDown(separator, { button: 0, pointerId: 21 })
|
||||
fireEvent.pointerMove(separator, { clientX: 401, pointerId: 21 })
|
||||
fireEvent.pointerUp(separator, { pointerId: 21 })
|
||||
expect(separator).toHaveAttribute('aria-valuenow', '400')
|
||||
fireEvent.keyDown(separator, { key: 'ArrowRight' })
|
||||
expect(separator).toHaveAttribute('aria-valuenow', '416')
|
||||
const group = screen.getByRole('button', { name: '发布笔记 (1)' })
|
||||
fireEvent.click(group)
|
||||
expect(group).toHaveAttribute('aria-expanded', 'false')
|
||||
expect(screen.queryByRole('button', { name: /核对发布材料.*发布笔记/ })).not.toBeInTheDocument()
|
||||
expect(screen.getByRole('region', { name: '待办详情' })).toBeVisible()
|
||||
fireEvent.click(group)
|
||||
expect(screen.getByRole('button', { name: /核对发布材料.*发布笔记/ })).toBeVisible()
|
||||
view.unmount()
|
||||
render(<MagicNotesWorkspace onNotify={onNotify} />)
|
||||
await openTodo()
|
||||
expect(screen.getByRole('separator', { name: '调整待办列表与详情宽度' })).toHaveAttribute('aria-valuenow', '416')
|
||||
})
|
||||
|
||||
it('retries source failures and ignores a late source from a previously expanded task', async () => {
|
||||
get.mockRejectedValueOnce(new Error('source failed'))
|
||||
render(<MagicNotesWorkspace onNotify={onNotify} />)
|
||||
@@ -851,7 +875,7 @@ describe('MagicNotesWorkspace detail behavior', () => {
|
||||
await openNote()
|
||||
fireEvent.click(screen.getByRole('button', { name: '显示 AI 评论' }))
|
||||
expect(screen.getByRole('separator', { name: '调整编辑区与 AI 评论宽度' })).toHaveAttribute('aria-valuenow', width)
|
||||
expect(JSON.parse(localStorage.getItem('goodbuddy.magic-notes-layout.v1')!)).toEqual({ indexPaneOpen: true, indexPaneWidth: 168, aiPaneOpen: true, aiPaneWidth: Number(width) })
|
||||
expect(JSON.parse(localStorage.getItem('goodbuddy.magic-notes-layout.v1')!)).toMatchObject({ indexPaneOpen: true, indexPaneWidth: 168, aiPaneOpen: true, aiPaneWidth: Number(width) })
|
||||
})
|
||||
|
||||
it('resizes AI with pointer and keyboard and disables resizing in narrow layouts', async () => {
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
CircleAlert,
|
||||
Circle,
|
||||
FileText,
|
||||
FolderTree,
|
||||
Lightbulb,
|
||||
ListTodo,
|
||||
MoreHorizontal,
|
||||
@@ -50,6 +49,7 @@ import type {
|
||||
import type { ApplicationSettings, MagicNoteCommentMode } from '../../shared/application-settings-contracts'
|
||||
import { magicNoteCanvasAnalysisText } from '../../shared/magic-note-canvas-text'
|
||||
import { MagicNoteContent } from './MagicNoteContent'
|
||||
import { MagicTodoDirectory } from './MagicTodoDirectory'
|
||||
import { MagicNoteEditor } from './MagicNoteEditor'
|
||||
import { MagicCanvasEditor, canvasHasContent, type MagicCanvasEditorHandle } from './MagicCanvasEditor'
|
||||
import type { MagicCanvasContentHandle } from './MagicCanvasContent'
|
||||
@@ -106,6 +106,7 @@ type MagicNotesLayoutPreferences = {
|
||||
indexPaneWidth: number
|
||||
aiPaneOpen: boolean
|
||||
aiPaneWidth: number
|
||||
todoPaneWidth: number
|
||||
}
|
||||
|
||||
function loadMagicNotesLayoutPreferences(): MagicNotesLayoutPreferences {
|
||||
@@ -113,7 +114,8 @@ function loadMagicNotesLayoutPreferences(): MagicNotesLayoutPreferences {
|
||||
indexPaneOpen: true,
|
||||
indexPaneWidth: defaultIndexPaneWidth,
|
||||
aiPaneOpen: true,
|
||||
aiPaneWidth: defaultAiPaneWidth
|
||||
aiPaneWidth: defaultAiPaneWidth,
|
||||
todoPaneWidth: 320
|
||||
}
|
||||
try {
|
||||
const value = localStorage.getItem(magicNotesLayoutStorageKey)
|
||||
@@ -124,6 +126,8 @@ function loadMagicNotesLayoutPreferences(): MagicNotesLayoutPreferences {
|
||||
MagicNotesLayoutPreferences
|
||||
>
|
||||
return {
|
||||
todoPaneWidth: typeof parsed.todoPaneWidth === 'number' && Number.isFinite(parsed.todoPaneWidth)
|
||||
? Math.max(240, parsed.todoPaneWidth) : defaults.todoPaneWidth,
|
||||
indexPaneOpen: parsed.indexPaneOpen !== false,
|
||||
indexPaneWidth: typeof parsed.indexPaneWidth === 'number' && Number.isFinite(parsed.indexPaneWidth)
|
||||
? Math.min(maximumIndexPaneWidth, Math.max(minimumIndexPaneWidth, parsed.indexPaneWidth))
|
||||
@@ -466,8 +470,10 @@ export function MagicNotesWorkspace({
|
||||
)
|
||||
const [indexPaneOpen, setIndexPaneOpen] = useState(initialLayoutPreferences.indexPaneOpen)
|
||||
const [indexPaneWidth, setIndexPaneWidth] = useState(initialLayoutPreferences.indexPaneWidth)
|
||||
const [todoPaneWidth, setTodoPaneWidth] = useState(initialLayoutPreferences.todoPaneWidth)
|
||||
const [todoLayoutWidth, setTodoLayoutWidth] = useState(window.innerWidth)
|
||||
const [narrowIndexOpen, setNarrowIndexOpen] = useState(false)
|
||||
const [resizingPane, setResizingPane] = useState<'ai' | 'index'>()
|
||||
const [resizingPane, setResizingPane] = useState<'ai' | 'index' | 'todo'>()
|
||||
const [magicNotesLayoutWidth, setMagicNotesLayoutWidth] = useState(
|
||||
window.innerWidth
|
||||
)
|
||||
@@ -521,7 +527,8 @@ export function MagicNotesWorkspace({
|
||||
const draftAnalysisContextRef = useRef(0)
|
||||
const lastDraftAnalysisStartedAtRef = useRef(0)
|
||||
const magicNotesLayoutRef = useRef<HTMLDivElement>(null)
|
||||
const paneResizeRef = useRef<{ pane: 'ai' | 'index'; pointerId: number; width: number } | undefined>(undefined)
|
||||
const todoLayoutRef = useRef<HTMLDivElement>(null)
|
||||
const paneResizeRef = useRef<{ pane: 'ai' | 'index' | 'todo'; pointerId: number; width: number } | undefined>(undefined)
|
||||
const composerRef = useRef<HTMLDivElement>(null)
|
||||
const continueEditingRef = useRef<HTMLButtonElement>(null)
|
||||
const discardDraftRef = useRef<HTMLButtonElement>(null)
|
||||
@@ -603,9 +610,20 @@ export function MagicNotesWorkspace({
|
||||
indexPaneOpen,
|
||||
indexPaneWidth,
|
||||
aiPaneOpen,
|
||||
aiPaneWidth
|
||||
aiPaneWidth,
|
||||
todoPaneWidth
|
||||
})
|
||||
}, [indexPaneOpen, indexPaneWidth, aiPaneOpen, aiPaneWidth])
|
||||
}, [indexPaneOpen, indexPaneWidth, aiPaneOpen, aiPaneWidth, todoPaneWidth])
|
||||
|
||||
useEffect(() => {
|
||||
const layout = todoLayoutRef.current
|
||||
if (!layout) return
|
||||
const update = (): void => setTodoLayoutWidth(layout.getBoundingClientRect().width || window.innerWidth)
|
||||
update()
|
||||
const observer = new ResizeObserver(update)
|
||||
observer.observe(layout)
|
||||
return () => observer.disconnect()
|
||||
}, [detailView, libraryView])
|
||||
|
||||
useEffect(() => {
|
||||
const layout = magicNotesLayoutRef.current
|
||||
@@ -1507,6 +1525,8 @@ export function MagicNotesWorkspace({
|
||||
? [...entries, editingEntry] : [...entries]).reverse()
|
||||
}, [detail, editingEntry])
|
||||
const isNarrowLayout = magicNotesLayoutWidth <= 800
|
||||
const todoPaneWidthLimits = { minimum: 240, maximum: Math.max(240, todoLayoutWidth - 301) }
|
||||
const displayedTodoWidth = clampMagicNotesPaneWidth(todoPaneWidth, todoPaneWidthLimits)
|
||||
const indexExpanded = isNarrowLayout ? narrowIndexOpen : indexPaneOpen
|
||||
// Clamp displayed widths without replacing the user's saved desktop preferences.
|
||||
const layoutInnerWidth = Math.max(0, magicNotesLayoutWidth - 2)
|
||||
@@ -1534,6 +1554,7 @@ export function MagicNotesWorkspace({
|
||||
event.currentTarget.releasePointerCapture(event.pointerId)
|
||||
}
|
||||
if (resize.pane === 'ai') setAiPaneWidth(resize.width)
|
||||
else if (resize.pane === 'todo') setTodoPaneWidth(resize.width)
|
||||
else setIndexPaneWidth(resize.width)
|
||||
setResizingPane(undefined)
|
||||
}
|
||||
@@ -1541,37 +1562,40 @@ export function MagicNotesWorkspace({
|
||||
const resize = paneResizeRef.current
|
||||
if (!resize || resize.pointerId !== event.pointerId) return
|
||||
const isAi = resize.pane === 'ai'
|
||||
const limits = isAi ? aiPaneWidthLimits : indexPaneWidthLimits
|
||||
if (isNarrowLayout) { finishPaneResize(event); return }
|
||||
const bounds = getLayoutBounds()
|
||||
const isTodo = resize.pane === 'todo'
|
||||
const limits = isTodo ? todoPaneWidthLimits : isAi ? aiPaneWidthLimits : indexPaneWidthLimits
|
||||
if (isTodo ? magicNotesLayoutWidth <= 700 : isNarrowLayout) { finishPaneResize(event); return }
|
||||
const bounds = isTodo ? todoLayoutRef.current!.getBoundingClientRect() : getLayoutBounds()
|
||||
resize.width = clampMagicNotesPaneWidth(isAi ? bounds.right - 1 - event.clientX : event.clientX - bounds.left - 1, limits)
|
||||
magicNotesLayoutRef.current?.style.setProperty(`--magic-notes-${resize.pane}-width`, `${resize.width}px`)
|
||||
const layout = isTodo ? todoLayoutRef.current : magicNotesLayoutRef.current
|
||||
layout?.style.setProperty(`--magic-notes-${resize.pane}-width`, `${resize.width}px`)
|
||||
event.currentTarget.setAttribute('aria-valuenow', String(resize.width))
|
||||
event.currentTarget.setAttribute('aria-valuetext', t(isAi ? 'accessibility.aiPaneWidth' : 'accessibility.indexPaneWidth', { width: resize.width }))
|
||||
event.currentTarget.setAttribute('aria-valuetext', t(isTodo ? 'accessibility.todoPaneWidth' : isAi ? 'accessibility.aiPaneWidth' : 'accessibility.indexPaneWidth', { width: resize.width }))
|
||||
}
|
||||
const startPaneResize = (pane: 'ai' | 'index', event: React.PointerEvent<HTMLDivElement>): void => {
|
||||
const limits = pane === 'ai' ? aiPaneWidthLimits : indexPaneWidthLimits
|
||||
if (event.button !== 0 || isNarrowLayout || limits.maximum <= limits.minimum) return
|
||||
const startPaneResize = (pane: 'ai' | 'index' | 'todo', event: React.PointerEvent<HTMLDivElement>): void => {
|
||||
const limits = pane === 'todo' ? todoPaneWidthLimits : pane === 'ai' ? aiPaneWidthLimits : indexPaneWidthLimits
|
||||
if (event.button !== 0 || (pane === 'todo' ? magicNotesLayoutWidth <= 700 : isNarrowLayout) || limits.maximum <= limits.minimum) return
|
||||
event.preventDefault()
|
||||
paneResizeRef.current = { pane, pointerId: event.pointerId, width: pane === 'ai' ? displayedAiWidth : displayedIndexWidth }
|
||||
paneResizeRef.current = { pane, pointerId: event.pointerId, width: pane === 'todo' ? displayedTodoWidth : pane === 'ai' ? displayedAiWidth : displayedIndexWidth }
|
||||
event.currentTarget.setPointerCapture(event.pointerId)
|
||||
setResizingPane(pane)
|
||||
}
|
||||
const renderPaneSeparator = (pane: 'ai' | 'index'): React.JSX.Element => {
|
||||
const renderPaneSeparator = (pane: 'ai' | 'index' | 'todo'): React.JSX.Element => {
|
||||
const isAi = pane === 'ai'
|
||||
const limits = isAi ? aiPaneWidthLimits : indexPaneWidthLimits
|
||||
const width = isAi ? displayedAiWidth : displayedIndexWidth
|
||||
const enabled = !isNarrowLayout && limits.maximum > limits.minimum
|
||||
const setWidth = isAi ? setAiPaneWidth : setIndexPaneWidth
|
||||
const isTodo = pane === 'todo'
|
||||
const limits = isTodo ? todoPaneWidthLimits : isAi ? aiPaneWidthLimits : indexPaneWidthLimits
|
||||
const width = isTodo ? displayedTodoWidth : isAi ? displayedAiWidth : displayedIndexWidth
|
||||
const enabled = (isTodo ? magicNotesLayoutWidth > 700 : !isNarrowLayout) && limits.maximum > limits.minimum
|
||||
const setWidth = isTodo ? setTodoPaneWidth : isAi ? setAiPaneWidth : setIndexPaneWidth
|
||||
return <div
|
||||
aria-controls={isAi ? 'magic-notes-ai-pane' : 'magic-notes-index'}
|
||||
aria-controls={isTodo ? 'magic-todo-list' : isAi ? 'magic-notes-ai-pane' : 'magic-notes-index'}
|
||||
aria-disabled={!enabled}
|
||||
aria-label={t(isAi ? 'accessibility.resizeAiPane' : 'accessibility.resizeIndexPane')}
|
||||
aria-label={t(isTodo ? 'accessibility.resizeTodoPane' : isAi ? 'accessibility.resizeAiPane' : 'accessibility.resizeIndexPane')}
|
||||
aria-orientation="vertical"
|
||||
aria-valuemax={limits.maximum}
|
||||
aria-valuemin={limits.minimum}
|
||||
aria-valuenow={width}
|
||||
aria-valuetext={t(isAi ? 'accessibility.aiPaneWidth' : 'accessibility.indexPaneWidth', { width })}
|
||||
aria-valuetext={t(isTodo ? 'accessibility.todoPaneWidth' : isAi ? 'accessibility.aiPaneWidth' : 'accessibility.indexPaneWidth', { width })}
|
||||
className={`magic-notes-pane-resize-handle magic-notes-${pane}-resize-handle`}
|
||||
onKeyDown={(event) => {
|
||||
if (!enabled) return
|
||||
@@ -2310,8 +2334,8 @@ export function MagicNotesWorkspace({
|
||||
value={todoFilter}
|
||||
/>
|
||||
</div>
|
||||
<div className={`magic-todo-workspace${selectedTodo ? ' magic-todo-workspace--selected' : ''}`}>
|
||||
<div className="magic-notes-list">
|
||||
<div ref={todoLayoutRef} style={{ '--magic-notes-todo-width': `${displayedTodoWidth}px` } as React.CSSProperties} className={`magic-todo-workspace${selectedTodo ? ' magic-todo-workspace--selected' : ''}${resizingPane === 'todo' ? ' magic-todo-workspace--resizing' : ''}`}>
|
||||
<div className="magic-notes-list" id="magic-todo-list">
|
||||
{loadStatus === 'loading' ? (
|
||||
<p className="magic-notes-muted">
|
||||
{t('status.loadingTodos')}
|
||||
@@ -2342,16 +2366,12 @@ export function MagicNotesWorkspace({
|
||||
</>
|
||||
) : (
|
||||
todoDirectories.map((directory) => (
|
||||
<section
|
||||
className="magic-todo-directory"
|
||||
<MagicTodoDirectory
|
||||
key={directory.noteId}
|
||||
noteId={directory.noteId}
|
||||
title={directory.noteTitle}
|
||||
count={directory.todos.length}
|
||||
>
|
||||
<div className="magic-todo-directory__heading">
|
||||
<FolderTree aria-hidden="true" size={14} />
|
||||
<strong>{directory.noteTitle}</strong>
|
||||
<span>{directory.todos.length}</span>
|
||||
</div>
|
||||
<div className="magic-todo-directory__items">
|
||||
{directory.todos.map((todo) => (
|
||||
<div className="magic-todo-task" key={todo.id}>
|
||||
<TodoListItem
|
||||
@@ -2373,12 +2393,12 @@ export function MagicNotesWorkspace({
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</MagicTodoDirectory>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
{selectedTodo ? (
|
||||
{renderPaneSeparator('todo')}
|
||||
{selectedTodo ? (
|
||||
<section
|
||||
aria-label={t('todos.detailLabel')}
|
||||
className="magic-todo-detail"
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
|
||||
import { afterEach, expect, it } from 'vitest'
|
||||
import { MagicTodoDirectory } from './MagicTodoDirectory'
|
||||
|
||||
afterEach(cleanup)
|
||||
|
||||
it('independently collapses groups with unique, stable controlled regions', () => {
|
||||
render(<>
|
||||
<MagicTodoDirectory noteId="first" title="First note" count={1}>
|
||||
<button>First task</button>
|
||||
</MagicTodoDirectory>
|
||||
<MagicTodoDirectory noteId="second" title="Second note" count={1}>
|
||||
<button>Second task</button>
|
||||
</MagicTodoDirectory>
|
||||
</>)
|
||||
const first = screen.getByRole('button', { name: 'First note (1)' })
|
||||
const second = screen.getByRole('button', { name: 'Second note (1)' })
|
||||
const itemsId = first.getAttribute('aria-controls')!
|
||||
const items = document.getElementById(itemsId)
|
||||
expect(itemsId).not.toBe(second.getAttribute('aria-controls'))
|
||||
expect(items).toContainElement(screen.getByRole('button', { name: 'First task' }))
|
||||
expect(first).toHaveAttribute('aria-expanded', 'true')
|
||||
|
||||
first.focus()
|
||||
fireEvent.click(first)
|
||||
expect(first).toHaveAttribute('aria-expanded', 'false')
|
||||
expect(first).toHaveFocus()
|
||||
expect(items).not.toBeVisible()
|
||||
expect(screen.queryByRole('button', { name: 'First task' })).not.toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: 'Second task' })).toBeVisible()
|
||||
expect(second).toHaveAttribute('aria-expanded', 'true')
|
||||
|
||||
fireEvent.click(first)
|
||||
expect(first).toHaveAttribute('aria-expanded', 'true')
|
||||
expect(first).toHaveAttribute('aria-controls', itemsId)
|
||||
expect(screen.getByRole('button', { name: 'First task' })).toBeVisible()
|
||||
})
|
||||
@@ -0,0 +1,49 @@
|
||||
import { ChevronRight, FolderTree } from 'lucide-react'
|
||||
import { useId, useState, type ReactNode } from 'react'
|
||||
|
||||
type MagicTodoDirectoryProps = {
|
||||
noteId: string
|
||||
title: string
|
||||
count: number
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export function MagicTodoDirectory({
|
||||
noteId,
|
||||
title,
|
||||
count,
|
||||
children
|
||||
}: MagicTodoDirectoryProps): React.JSX.Element {
|
||||
const [expanded, setExpanded] = useState(true)
|
||||
const itemsId = useId()
|
||||
|
||||
return (
|
||||
<section className="magic-todo-directory" data-note-id={noteId}>
|
||||
<button
|
||||
aria-controls={itemsId}
|
||||
aria-expanded={expanded}
|
||||
aria-label={`${title} (${count})`}
|
||||
className="magic-todo-directory__heading"
|
||||
onClick={() => setExpanded((current) => !current)}
|
||||
type="button"
|
||||
>
|
||||
<ChevronRight
|
||||
aria-hidden="true"
|
||||
size={14}
|
||||
style={{ transform: expanded ? 'rotate(90deg)' : undefined }}
|
||||
/>
|
||||
<FolderTree aria-hidden="true" size={14} />
|
||||
<strong>{title}</strong>
|
||||
<span>{count}</span>
|
||||
</button>
|
||||
<div
|
||||
className="magic-todo-directory__items"
|
||||
hidden={!expanded}
|
||||
id={itemsId}
|
||||
style={{ display: expanded ? undefined : 'none' }}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -181,6 +181,8 @@ export const magicNotes = {
|
||||
accessibility: {
|
||||
resizeAiPane: 'Resize the editor and AI comments panes',
|
||||
resizeIndexPane: 'Resize the record index',
|
||||
resizeTodoPane: 'Resize the to-do list and details',
|
||||
todoPaneWidth: 'To-do list width {{width}} pixels',
|
||||
indexPaneWidth: 'Record index width {{width}} pixels',
|
||||
aiPaneWidth: 'AI comments pane, {{width}} pixels'
|
||||
},
|
||||
|
||||
@@ -169,6 +169,8 @@ export const magicNotes = {
|
||||
accessibility: {
|
||||
resizeAiPane: '调整编辑区与 AI 评论宽度',
|
||||
resizeIndexPane: '调整记录索引宽度',
|
||||
resizeTodoPane: '调整待办列表与详情宽度',
|
||||
todoPaneWidth: '待办列表宽度 {{width}} 像素',
|
||||
indexPaneWidth: '记录索引宽度 {{width}} 像素',
|
||||
aiPaneWidth: 'AI 评论栏 {{width}} 像素'
|
||||
},
|
||||
|
||||
@@ -1697,12 +1697,17 @@
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
grid-template-columns: minmax(280px, 34%) minmax(0, 1fr);
|
||||
grid-template-columns: var(--magic-notes-todo-width, 320px) 1px minmax(0, 1fr);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.magic-todo-workspace > .magic-notes-list {
|
||||
padding-right: var(--space-3);
|
||||
padding-right: var(--space-2);
|
||||
}
|
||||
|
||||
.magic-todo-workspace > .magic-notes-todo-resize-handle {
|
||||
background: transparent;
|
||||
margin-left: -4px;
|
||||
}
|
||||
|
||||
.magic-todo-detail {
|
||||
@@ -1711,8 +1716,7 @@
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
align-content: start;
|
||||
border-left: 1px solid var(--border-subtle);
|
||||
padding: var(--space-3) var(--space-4) var(--space-4);
|
||||
padding: var(--space-3) var(--space-2) var(--space-4);
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
@@ -1740,6 +1744,28 @@
|
||||
}
|
||||
}
|
||||
|
||||
.magic-todo-workspace--resizing {
|
||||
cursor: col-resize;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.magic-todo-workspace--resizing > .magic-notes-todo-resize-handle::after {
|
||||
background: var(--accent);
|
||||
width: 2px;
|
||||
}
|
||||
|
||||
@container magic-notes-page (min-width: 701px) {
|
||||
.magic-todo-workspace > .magic-notes-todo-resize-handle {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
@container magic-notes-page (max-width: 700px) {
|
||||
.magic-todo-workspace > .magic-notes-todo-resize-handle {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.magic-todo-detail h3,
|
||||
.magic-todo-instructions {
|
||||
margin: 0;
|
||||
@@ -1797,7 +1823,21 @@
|
||||
color: var(--text-secondary);
|
||||
font-size: var(--font-caption);
|
||||
gap: var(--space-1);
|
||||
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||
grid-template-columns: auto auto minmax(0, 1fr) auto;
|
||||
width: 100%;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.magic-todo-directory__heading:hover {
|
||||
background: var(--surface-subtle);
|
||||
}
|
||||
|
||||
.magic-todo-directory__heading:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
.magic-todo-directory__heading strong {
|
||||
|
||||
Reference in New Issue
Block a user