fix: simplify note capture controls and entry points
Remove duplicate quick-note shortcuts, redundant scope text and visible content labels. Right-align note action groups while retaining accessible input names and validation feedback. Release note: Simplify the notes panel and align save and source actions to the right. Open notes from the workbar or conversation capture actions.
This commit is contained in:
@@ -14056,11 +14056,13 @@ describe("App", () => {
|
||||
const preview = await screen.findByLabelText('待加入内容')
|
||||
expect(preview).toHaveValue(messages.map(message => `## ${message.role === 'user' ? '用户' : '助手'}\n\n${message.content}`).join('\n\n'))
|
||||
expect(screen.getAllByRole('tab', { name: '笔记' })).toHaveLength(1)
|
||||
fireEvent.click(screen.getByRole('button', { name: '快速笔记' }))
|
||||
expect(screen.queryByRole('button', { name: '快速笔记' })).not.toBeInTheDocument()
|
||||
fireEvent.click(screen.getByRole('button', { name: '打开工作栏应用' }))
|
||||
fireEvent.click(screen.getByRole('button', { name: /^笔记搜索/ }))
|
||||
expect(screen.getAllByRole('tab', { name: '笔记' })).toHaveLength(1)
|
||||
fireEvent.change(preview, { target: { value: 'Frozen edited capture' } })
|
||||
fireEvent.click(screen.getByRole('button', { name: /收起工作栏|助手工作栏/ }))
|
||||
fireEvent.click(screen.getByRole('button', { name: '快速笔记' }))
|
||||
fireEvent.click(screen.getByRole('button', { name: '切换助手工作栏' }))
|
||||
expect(await screen.findByLabelText('待加入内容')).toHaveValue('Frozen edited capture')
|
||||
fireEvent.click(screen.getByRole('button', { name: /新建对话/ }))
|
||||
expect(await screen.findByLabelText('待加入内容')).toHaveValue('Frozen edited capture')
|
||||
@@ -14150,7 +14152,9 @@ describe("App", () => {
|
||||
render(<App />)
|
||||
await screen.findByText('Source history 101')
|
||||
expect(screen.queryByText('Source history 1')).not.toBeInTheDocument()
|
||||
fireEvent.click(screen.getByRole('button', { name: '快速笔记' }))
|
||||
fireEvent.click(screen.getByRole('button', { name: '切换助手工作栏' }))
|
||||
fireEvent.click(screen.getByRole('button', { name: '打开工作栏应用' }))
|
||||
fireEvent.click(screen.getByRole('button', { name: /^笔记搜索/ }))
|
||||
const notesPanel = within(await screen.findByRole('region', { name: '快速笔记' }))
|
||||
fireEvent.click(await notesPanel.findByRole('button', { name: /Source note/ }))
|
||||
fireEvent.click(await notesPanel.findByRole('button', { name: '查看来源消息' }))
|
||||
@@ -14172,7 +14176,9 @@ describe("App", () => {
|
||||
it('guards disabling Magic Notes with an unsaved quick draft before saving application settings', async () => {
|
||||
await api.updates!.updateSettings({ magicNotesEnabled: true })
|
||||
render(<App />)
|
||||
fireEvent.click(await screen.findByRole('button', { name: '快速笔记' }))
|
||||
fireEvent.click(await screen.findByRole('button', { name: '切换助手工作栏' }))
|
||||
fireEvent.click(screen.getByRole('button', { name: '打开工作栏应用' }))
|
||||
fireEvent.click(screen.getByRole('button', { name: /^笔记搜索/ }))
|
||||
fireEvent.click(await screen.findByRole('button', { name: '新建笔记' }))
|
||||
fireEvent.change(screen.getByLabelText('笔记标题'), { target: { value: 'Unsaved quick note' } })
|
||||
fireEvent.click(screen.getByRole('button', { name: '应用中心' }))
|
||||
|
||||
@@ -9340,7 +9340,6 @@ function App(): React.JSX.Element {
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{magicNotesEnabled && <button type="button" className="nav-item" title={t('magicNotes:capture.quick')} onClick={openQuickNotes}><FileText size={17} aria-hidden="true" /><span>{t('magicNotes:capture.quick')}</span></button>}
|
||||
<div className="sidebar-footer sidebar-footer--applications">
|
||||
<button
|
||||
className="nav-item"
|
||||
@@ -9502,7 +9501,6 @@ function App(): React.JSX.Element {
|
||||
</>
|
||||
)}
|
||||
<div className="topbar__actions">
|
||||
{magicNotesEnabled && !sidebarOpen && <button type="button" className="icon-button" aria-label={t('magicNotes:capture.quick')} title={t('magicNotes:capture.quick')} onClick={openQuickNotes}><FileText size={17} aria-hidden="true" /></button>}
|
||||
<button
|
||||
aria-controls="assistant-sidebar"
|
||||
aria-expanded={assistantSidebarOpen}
|
||||
|
||||
@@ -19,7 +19,7 @@ export function MagicNoteSource({ source, onOpen }: { source: Source; onOpen?: O
|
||||
}
|
||||
return <aside className="magic-note-source" aria-label={t('capture.source')}>
|
||||
<span>{t('capture.source')}: {source.conversationTitle}</span>
|
||||
<span>{source.projectName ?? t('capture.global')} · <time dateTime={source.capturedAt}>{new Date(source.capturedAt).toLocaleString(i18n.language)}</time></span>
|
||||
<span>{source.projectName && `${source.projectName} · `}<time dateTime={source.capturedAt}>{new Date(source.capturedAt).toLocaleString(i18n.language)}</time></span>
|
||||
{onOpen && <div className="magic-note-panel__actions">
|
||||
<button className="secondary-button" type="button" disabled={missing || busy} onClick={() => void open()}>{t(missing ? 'capture.missingConversation' : 'capture.openSource')}</button>
|
||||
{source.kind === 'message' && <button className="secondary-button" type="button" disabled={missing || busy} onClick={() => void open(source.messageIds[0])}>{t('capture.openMessage')}</button>}
|
||||
|
||||
@@ -51,9 +51,13 @@ describe('compact notes', () => {
|
||||
render(<Harness />)
|
||||
fireEvent.click(screen.getByText('Capture'))
|
||||
await screen.findByDisplayValue('Captured text')
|
||||
expect(screen.getByRole('textbox', { name: 'Text to add' })).toBeVisible()
|
||||
expect(screen.queryByText('Text to add')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('Global')).not.toBeInTheDocument()
|
||||
expect(create).not.toHaveBeenCalled()
|
||||
fireEvent.click(screen.getByRole('button', { name: 'New note' }))
|
||||
expect(screen.getByLabelText('Note title')).toHaveValue('Discussion A')
|
||||
expect(document.getElementById('compact-note-validation')).not.toBeInTheDocument()
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Add to note' }))
|
||||
await waitFor(() => {
|
||||
expect(create).toHaveBeenCalledWith({ title: 'Discussion A', content: capturedNoteContent('Captured text'), source })
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { MagicNoteCommentMode } from '../../shared/application-settings-con
|
||||
import { MagicNoteSource, type OpenMagicNoteSource } from './MagicNoteSource'
|
||||
import type { useMagicNoteDraft } from './use-magic-note-draft'
|
||||
import type { AppNotificationInput } from './notifications'
|
||||
import { ScopeBadge, SegmentedControl } from './WorkspacePrimitives'
|
||||
import { SegmentedControl } from './WorkspacePrimitives'
|
||||
import './magic-notes-panel.css'
|
||||
|
||||
const MagicCanvasThumbnail = lazy(async () => ({ default: (await import('./MagicCanvasThumbnail')).MagicCanvasThumbnail }))
|
||||
@@ -149,7 +149,8 @@ export function MagicNotesPanel({ state, active, commentMode, commentFormat = 'c
|
||||
setSelectedNoteId('')
|
||||
requestAnimationFrame(() => document.getElementById('compact-note-search')?.focus())
|
||||
}
|
||||
const validationId = `compact-note-validation${saveError ? ' compact-note-save-error' : ''}`
|
||||
const validationMessage = tooLong ? t('capture.tooLong') : draft && !valid ? t('capture.required') : undefined
|
||||
const validationId = [validationMessage && 'compact-note-validation', saveError && 'compact-note-save-error'].filter(Boolean).join(' ') || undefined
|
||||
const list = (
|
||||
<>
|
||||
<label htmlFor="compact-note-search">{t('capture.search')}</label>
|
||||
@@ -182,7 +183,6 @@ export function MagicNotesPanel({ state, active, commentMode, commentFormat = 'c
|
||||
}}>
|
||||
<header className="magic-note-panel__header">
|
||||
<strong>{draft?.source ? t('capture.add') : currentDetail?.title ?? t('page.title')}</strong>
|
||||
<ScopeBadge scope={{ kind: 'global' }} />
|
||||
{(!draft || quickDraft) && currentDetail && (
|
||||
<div className="magic-note-panel__actions">
|
||||
<button className="icon-button" type="button" title={t('capture.back')}
|
||||
@@ -202,12 +202,11 @@ export function MagicNotesPanel({ state, active, commentMode, commentFormat = 'c
|
||||
{draft && !quickDraft ? (
|
||||
<>
|
||||
{draft.source && <div className="magic-note-source">
|
||||
<span>{draft.source.projectName ?? t('capture.global')} / {draft.source.conversationTitle}</span>
|
||||
<span>{draft.source.projectName && `${draft.source.projectName} / `}{draft.source.conversationTitle}</span>
|
||||
<p>{t(draft.source.kind === 'message' ? 'capture.messageScope' : 'capture.conversationScope')}</p>
|
||||
{draft.incomplete && <p>{t('capture.incomplete')}</p>}
|
||||
</div>}
|
||||
<label htmlFor="compact-note-text">{t('capture.content')}</label>
|
||||
<textarea ref={preview} id="compact-note-text" value={draft.text} disabled={saving}
|
||||
<textarea ref={preview} id="compact-note-text" aria-label={t('capture.content')} value={draft.text} disabled={saving}
|
||||
aria-describedby={validationId} aria-invalid={tooLong || undefined}
|
||||
onChange={event => setDraft({ ...draft, text: event.target.value })} />
|
||||
<h3>{t('capture.target')}</h3>
|
||||
@@ -259,14 +258,13 @@ export function MagicNotesPanel({ state, active, commentMode, commentFormat = 'c
|
||||
{(draft || currentDetail) && (
|
||||
<footer className="magic-note-panel__footer">
|
||||
{(!draft || quickDraft) && currentDetail && <>
|
||||
<label htmlFor="compact-note-append">{t('capture.content')}</label>
|
||||
<textarea id="compact-note-append" value={draft?.text ?? ''} disabled={saving}
|
||||
<textarea id="compact-note-append" aria-label={t('capture.content')} value={draft?.text ?? ''} disabled={saving}
|
||||
aria-describedby={validationId} aria-invalid={tooLong || undefined}
|
||||
onChange={event => setDraft(draft ? { ...draft, text: event.target.value } : {
|
||||
text: event.target.value, initialText: '', title: '', initialTitle: '', targetId: currentDetail.id, newNote: false
|
||||
})} />
|
||||
</>}
|
||||
<p id="compact-note-validation">{tooLong ? t('capture.tooLong') : draft && !valid ? t('capture.required') : t('capture.global')}</p>
|
||||
{validationMessage && <p id="compact-note-validation">{validationMessage}</p>}
|
||||
{saveError && <p role="alert" id="compact-note-save-error">{saveError}</p>}
|
||||
<div className="magic-note-panel__actions">
|
||||
{draft && <button className="secondary-button" type="button" disabled={saving}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
.magic-note-panel__scroll > * { flex-shrink: 0; }
|
||||
.magic-note-panel h3 { margin: 0; font-size: var(--font-section-title); }
|
||||
.magic-note-panel__scroll > .segmented-control { flex: 0 0 auto; }
|
||||
.magic-note-panel__actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }
|
||||
.magic-note-panel__actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: var(--space-2); }
|
||||
.magic-note-panel input, .magic-note-panel textarea { box-sizing: border-box; width: 100%; min-width: 0; padding: var(--space-2); color: var(--text-primary); background: var(--surface-raised); border: 1px solid var(--border-control); border-radius: var(--radius-control); font: inherit; }
|
||||
.magic-note-panel textarea { min-height: 180px; resize: vertical; }
|
||||
.magic-note-panel__list { display: flex; flex-direction: column; gap: var(--space-2); }
|
||||
|
||||
Reference in New Issue
Block a user