feat(magic-notes): improve tag management UI

This commit is contained in:
mesalogo
2026-10-07 21:05:14 +08:00
parent e0607a153f
commit 13c29badfb
14 changed files with 160 additions and 25 deletions
+13 -9
View File
@@ -7805,8 +7805,8 @@ describe('AssistantDatabase', () => {
const retagged = database.updateMagicNote({ noteId: work.id, tags: ['Work'], expectedRevision: work.revision })
expect(retagged.tags).toEqual(['Work'])
expect(retagged.revision).toBe(work.revision + 1)
// "Project A" lost its last note and is removed.
expect(database.listMagicNoteTags().map((tag) => tag.name)).not.toContain('Project A')
// Tags remain available after their last note link is removed.
expect(database.listMagicNoteTags().map((tag) => tag.name)).toContain('Project A')
expect(() => database.updateMagicNote({ noteId: work.id, tags: [], expectedRevision: work.revision })).toThrow()
expect(database.getMagicNote(work.id).tags).toEqual(['Work'])
@@ -7820,19 +7820,23 @@ describe('AssistantDatabase', () => {
const merged = database.renameMagicNoteTag({ tagId: workTag.id, name: 'Personal' })
expect(merged).toMatchObject({ merged: true, tag: { name: 'personal', noteCount: 3 } })
expect(database.getMagicNote(both.id).tags).toEqual(['personal'])
expect(database.listMagicNoteTags()).toHaveLength(1)
expect(database.listMagicNoteTags().map((tag) => tag.name)).toEqual(['personal', 'Project A'])
const calls = onMagicNotesChanged.mock.calls.length
database.deleteMagicNoteTag(merged.tag.id)
expect(onMagicNotesChanged).toHaveBeenCalledTimes(calls + 1)
expect(database.listMagicNoteTags()).toEqual([])
expect(database.getMagicNote(personal.id)).toMatchObject({ title: 'Personal', tags: [] })
expect(() => database.deleteMagicNoteTag(merged.tag.id)).toThrow('标签不存在')
expect(() => database.renameMagicNoteTag({ tagId: merged.tag.id, name: 'x' })).toThrow('标签不存在')
expect(database.listMagicNoteTags().map((tag) => tag.name)).toEqual(['Project A'])
expect(database.getMagicNote(personal.id)).toMatchObject({ title: 'Personal', tags: [] })
expect(() => database.deleteMagicNoteTag(merged.tag.id)).toThrow('标签不存在')
expect(() => database.renameMagicNoteTag({ tagId: merged.tag.id, name: 'x' })).toThrow('标签不存在')
database.updateMagicNote({ noteId: personal.id, tags: ['Solo'], expectedRevision: database.getMagicNote(personal.id).revision })
const empty = database.createMagicNoteTag({ name: 'Ideas' })
expect(empty).toEqual(expect.objectContaining({ name: 'Ideas', noteCount: 0 }))
expect(database.listMagicNoteTags().map((tag) => tag.name)).toContain('Ideas')
database.updateMagicNote({ noteId: personal.id, tags: ['Solo'], expectedRevision: database.getMagicNote(personal.id).revision })
database.deleteMagicNote(personal.id)
expect(database.listMagicNoteTags()).toEqual([])
expect(database.listMagicNoteTags().map((tag) => tag.name)).toEqual(['Ideas', 'Project A', 'Solo'])
} finally {
database.close()
}
+18 -10
View File
@@ -4169,13 +4169,30 @@ export class AssistantDatabase {
FROM magic_note_tags t
LEFT JOIN magic_note_tag_links l ON l.tag_id = t.id
GROUP BY t.id
HAVING note_count > 0
ORDER BY note_count DESC, t.name_key ASC`
)
.all() as MagicNoteTagRow[]
return rows.map(toMagicNoteTag)
}
createMagicNoteTag(input: { name: string }): MagicNoteTag {
const database = this.requireDatabase()
const now = new Date().toISOString()
const id = randomUUID()
const key = magicNoteTagKey(input.name)
try {
database.prepare(
`INSERT INTO magic_note_tags (id, name, name_key, created_at, updated_at)
VALUES (?, ?, ?, ?, ?)`
).run(id, input.name, key, now, now)
} catch (error) {
if (error instanceof Error && error.message.includes('UNIQUE')) throw new Error('标签已存在')
throw error
}
this.options.onMagicNotesChanged?.()
return { id, name: input.name, noteCount: 0 }
}
/**
* Renames a tag across every note. When the new name matches another tag,
* the two merge: links move to the existing tag and the old tag is removed.
@@ -4286,14 +4303,6 @@ export class AssistantDatabase {
}
insertLink.run(noteId, tag.id, position, now)
})
this.deleteOrphanMagicNoteTags(database)
}
private deleteOrphanMagicNoteTags(database: DatabaseSync): void {
database.exec(
`DELETE FROM magic_note_tags
WHERE NOT EXISTS (SELECT 1 FROM magic_note_tag_links l WHERE l.tag_id = magic_note_tags.id)`
)
}
getMagicNote(noteId: string): MagicNoteDetail {
@@ -4474,7 +4483,6 @@ export class AssistantDatabase {
if (result.changes !== 1) {
throw new Error('笔记不存在')
}
this.deleteOrphanMagicNoteTags(database)
database.exec('COMMIT')
} catch (error) {
database.exec('ROLLBACK')