feat(magic-notes): improve tag management UI
This commit is contained in:
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
|
||||
Reference in New Issue
Block a user