fix: move todo source entry to detail pane

This commit is contained in:
mesalogo
2026-09-09 01:26:10 +08:00
parent e95c7a60d4
commit b1ba14f98e
5 changed files with 39 additions and 122 deletions
@@ -466,6 +466,10 @@ describe('MagicNotesWorkspace', () => {
const sourceEntry = screen.getByRole('region', { const sourceEntry = screen.getByRole('region', {
name: '对应的笔记记录' name: '对应的笔记记录'
}) })
const todoDetail = screen.getByRole('region', { name: '待办详情' })
const aiComments = screen.getByRole('complementary', { name: 'AI 评论' })
expect(todoDetail).toContainElement(sourceEntry)
expect(aiComments).not.toContainElement(sourceEntry)
expect(within(sourceEntry).getByText('发布笔记')).toBeInTheDocument() expect(within(sourceEntry).getByText('发布笔记')).toBeInTheDocument()
expect(within(sourceEntry).getByText('记录正文')).toBeInTheDocument() expect(within(sourceEntry).getByText('记录正文')).toBeInTheDocument()
}) })
+32 -99
View File
@@ -485,10 +485,6 @@ export function MagicNotesWorkspace({
const [detail, setDetail] = useState<MagicNoteDetail>() const [detail, setDetail] = useState<MagicNoteDetail>()
const [todoSourceDetail, setTodoSourceDetail] = const [todoSourceDetail, setTodoSourceDetail] =
useState<MagicNoteDetail>() useState<MagicNoteDetail>()
const [todoSourceError, setTodoSourceError] = useState<{
message: string
noteId: string
}>()
const [loadStatus, setLoadStatus] = useState<LoadStatus>('loading') const [loadStatus, setLoadStatus] = useState<LoadStatus>('loading')
const [loadError, setLoadError] = useState('') const [loadError, setLoadError] = useState('')
const [refreshError, setRefreshError] = useState('') const [refreshError, setRefreshError] = useState('')
@@ -1408,30 +1404,6 @@ export function MagicNotesWorkspace({
const selectedTodoNoteId = selectedTodo?.noteId const selectedTodoNoteId = selectedTodo?.noteId
const selectedTodoEntryId = selectedTodo?.entryId const selectedTodoEntryId = selectedTodo?.entryId
const loadTodoSource = useCallback(
async (noteId: string): Promise<void> => {
const requestId = ++todoSourceRequestRef.current
try {
const sourceDetail = await window.goodbuddy.magicNotes.get(noteId)
if (todoSourceRequestRef.current === requestId) {
setTodoSourceDetail(sourceDetail)
setTodoSourceError(undefined)
}
} catch (sourceError) {
if (todoSourceRequestRef.current === requestId) {
setTodoSourceError({
message: errorMessage(
sourceError,
tRef.current('errors.operationFailed')
),
noteId
})
}
}
},
[]
)
useEffect(() => { useEffect(() => {
if (libraryView !== 'todos' || !selectedTodoNoteId) { if (libraryView !== 'todos' || !selectedTodoNoteId) {
return return
@@ -1442,41 +1414,27 @@ export function MagicNotesWorkspace({
.then((sourceDetail) => { .then((sourceDetail) => {
if (todoSourceRequestRef.current === requestId) { if (todoSourceRequestRef.current === requestId) {
setTodoSourceDetail(sourceDetail) setTodoSourceDetail(sourceDetail)
setTodoSourceError(undefined)
} }
}) })
.catch((sourceError: unknown) => { .catch((sourceError: unknown) => {
if (todoSourceRequestRef.current === requestId) { if (todoSourceRequestRef.current === requestId) {
setTodoSourceError({ notifyError(sourceError)
message: errorMessage(
sourceError,
tRef.current('errors.operationFailed')
),
noteId: selectedTodoNoteId
})
} }
}) })
return () => { return () => {
todoSourceRequestRef.current += 1 todoSourceRequestRef.current += 1
} }
}, [libraryView, selectedTodoNoteId]) }, [libraryView, notifyError, selectedTodoNoteId])
const selectedTodoSourceEntry = useMemo( const selectedTodoSourceEntry = useMemo(
() => () =>
todoSourceDetail?.entries.find( todoSourceDetail && todoSourceDetail.id === selectedTodoNoteId
? todoSourceDetail.entries.find(
(entry) => entry.id === selectedTodoEntryId (entry) => entry.id === selectedTodoEntryId
),
[selectedTodoEntryId, todoSourceDetail]
) )
const selectedTodoSourceError = : undefined,
todoSourceError && todoSourceError.noteId === selectedTodoNoteId [selectedTodoEntryId, selectedTodoNoteId, todoSourceDetail]
? todoSourceError.message )
: ''
const todoSourceLoading =
Boolean(selectedTodoNoteId) &&
todoSourceDetail?.id !== selectedTodoNoteId &&
!selectedTodoSourceError
const reloadTodos = useCallback(async (): Promise<void> => { const reloadTodos = useCallback(async (): Promise<void> => {
const snapshot = await window.goodbuddy.magicNotes.listTodos() const snapshot = await window.goodbuddy.magicNotes.listTodos()
setTodos(snapshot.todos) setTodos(snapshot.todos)
@@ -1574,7 +1532,6 @@ export function MagicNotesWorkspace({
if (selectedTodoId === todo.id) { if (selectedTodoId === todo.id) {
todoSourceRequestRef.current += 1 todoSourceRequestRef.current += 1
setTodoSourceDetail(result.note) setTodoSourceDetail(result.note)
setTodoSourceError(undefined)
} }
if (requestedNoteIdRef.current === result.note.id) { if (requestedNoteIdRef.current === result.note.id) {
applyDetail(result.note) applyDetail(result.note)
@@ -2767,6 +2724,31 @@ export function MagicNotesWorkspace({
{t('actions.openSourceNote')} {t('actions.openSourceNote')}
</button> </button>
</div> </div>
<section
aria-label={t('todos.sourceEntryLabel')}
className="magic-todo-source-entry"
>
<header>
<div>
<strong>{t('todos.sourceEntryHeading')}</strong>
<small>{selectedTodo.noteTitle}</small>
</div>
{selectedTodoSourceEntry && (
<time dateTime={selectedTodoSourceEntry.updatedAt}>
{dateFormatter.format(
new Date(selectedTodoSourceEntry.updatedAt)
)}
</time>
)}
</header>
{selectedTodoSourceEntry ? (
<MagicNoteContent content={selectedTodoSourceEntry.content} />
) : (
<p className="magic-notes-muted">
{t('todos.sourceEntryMissing')}
</p>
)}
</section>
</section> </section>
)} )}
</section> </section>
@@ -2838,55 +2820,6 @@ export function MagicNotesWorkspace({
<PanelRightClose aria-hidden="true" size={15} /> <PanelRightClose aria-hidden="true" size={15} />
</button> </button>
</div> </div>
{libraryView === 'todos' && selectedTodo && (
<section
aria-label={t('todos.sourceEntryLabel')}
className="magic-todo-source-entry"
>
<header>
<div>
<strong>{t('todos.sourceEntryHeading')}</strong>
<small>{selectedTodo.noteTitle}</small>
</div>
{selectedTodoSourceEntry && (
<time dateTime={selectedTodoSourceEntry.updatedAt}>
{dateFormatter.format(
new Date(selectedTodoSourceEntry.updatedAt)
)}
</time>
)}
</header>
{todoSourceLoading ? (
<p className="magic-notes-muted">
{t('todos.loadingSourceEntry')}
</p>
) : selectedTodoSourceError ? (
<div className="magic-todo-source-entry__error" role="alert">
<span>
{t('todos.sourceEntryLoadFailed', {
error: selectedTodoSourceError
})}
</span>
<button
className="secondary-button"
onClick={() => {
setTodoSourceError(undefined)
void loadTodoSource(selectedTodo.noteId)
}}
type="button"
>
{t('actions.retry')}
</button>
</div>
) : selectedTodoSourceEntry ? (
<MagicNoteContent content={selectedTodoSourceEntry.content} />
) : (
<p className="magic-notes-muted">
{t('todos.sourceEntryMissing')}
</p>
)}
</section>
)}
<div className="magic-notes-ai-controls"> <div className="magic-notes-ai-controls">
<label> <label>
<span>{t('comments.directionLabel')}</span> <span>{t('comments.directionLabel')}</span>
@@ -117,9 +117,7 @@ export const magicNotes = {
sourceNote: 'From note: {{title}}', sourceNote: 'From note: {{title}}',
sourceEntryLabel: 'Related note entry', sourceEntryLabel: 'Related note entry',
sourceEntryHeading: 'Related entry', sourceEntryHeading: 'Related entry',
loadingSourceEntry: 'Loading the related entry…',
sourceEntryMissing: 'The note entry for this to-do could not be found.', sourceEntryMissing: 'The note entry for this to-do could not be found.',
sourceEntryLoadFailed: 'Could not load the related entry: {{error}}',
defaultInstructions: 'This to-do comes from a checklist in a note.', defaultInstructions: 'This to-do comes from a checklist in a note.',
markComplete: 'Mark complete: {{title}}', markComplete: 'Mark complete: {{title}}',
markIncomplete: 'Mark incomplete: {{title}}' markIncomplete: 'Mark incomplete: {{title}}'
@@ -110,9 +110,7 @@ export const magicNotes = {
sourceNote: '来自笔记:{{title}}', sourceNote: '来自笔记:{{title}}',
sourceEntryLabel: '对应的笔记记录', sourceEntryLabel: '对应的笔记记录',
sourceEntryHeading: '对应记录', sourceEntryHeading: '对应记录',
loadingSourceEntry: '正在加载对应记录…',
sourceEntryMissing: '未找到这条待办对应的笔记记录。', sourceEntryMissing: '未找到这条待办对应的笔记记录。',
sourceEntryLoadFailed: '对应记录加载失败:{{error}}',
defaultInstructions: '此待办来自笔记正文中的待办清单。', defaultInstructions: '此待办来自笔记正文中的待办清单。',
markComplete: '标记为已完成:{{title}}', markComplete: '标记为已完成:{{title}}',
markIncomplete: '标记为未完成:{{title}}' markIncomplete: '标记为未完成:{{title}}'
+2 -18
View File
@@ -1399,9 +1399,7 @@
.magic-todo-source-entry { .magic-todo-source-entry {
display: grid; display: grid;
padding: var(--space-3) 0; gap: var(--space-3);
border-bottom: 1px solid var(--border-subtle);
gap: var(--space-2);
} }
.magic-todo-source-entry > header { .magic-todo-source-entry > header {
@@ -1438,11 +1436,9 @@
} }
.magic-todo-source-entry > .magic-note-content { .magic-todo-source-entry > .magic-note-content {
max-height: 240px;
overflow: auto;
border: 1px solid var(--border-default); border: 1px solid var(--border-default);
border-radius: var(--radius-control); border-radius: var(--radius-control);
background: var(--surface-raised); background: var(--surface-subtle);
} }
.magic-todo-source-entry > .magic-note-content .ql-editor { .magic-todo-source-entry > .magic-note-content .ql-editor {
@@ -1453,18 +1449,6 @@
padding-inline: 0; padding-inline: 0;
} }
.magic-todo-source-entry__error {
display: grid;
color: var(--danger);
font-size: var(--font-caption);
line-height: 1.5;
gap: var(--space-2);
}
.magic-todo-source-entry__error .secondary-button {
width: fit-content;
}
.magic-notes-ai-controls { .magic-notes-ai-controls {
display: grid; display: grid;
padding: var(--space-3) 0; padding: var(--space-3) 0;