feat(magic-notes): improve tag management UI
This commit is contained in:
@@ -963,6 +963,7 @@ const api: DesktopApi & RuntimeNativeClientApi = {
|
||||
magicNotes: {
|
||||
search: vi.fn(async () => []),
|
||||
list: vi.fn(async () => ({ notes: [], tags: [] })),
|
||||
createTag: vi.fn(async () => ({ id: 'unused-tag', name: 'unused', noteCount: 0 })),
|
||||
renameTag: vi.fn(async () => {
|
||||
throw new Error("not used");
|
||||
}),
|
||||
|
||||
@@ -317,11 +317,13 @@ export function MagicNoteTagManagerButton({
|
||||
export function MagicNoteTagManager({
|
||||
tags,
|
||||
onClose,
|
||||
onCreate,
|
||||
onRename,
|
||||
onDelete
|
||||
}: {
|
||||
tags: MagicNoteTag[]
|
||||
onClose: () => void
|
||||
onCreate: (name: string) => Promise<MagicNoteTag | undefined>
|
||||
onRename: (tag: MagicNoteTag, name: string) => Promise<MagicNoteTagRenameResult | undefined>
|
||||
onDelete: (tag: MagicNoteTag) => Promise<boolean>
|
||||
}): React.JSX.Element {
|
||||
@@ -332,6 +334,8 @@ export function MagicNoteTagManager({
|
||||
const descriptionId = useId()
|
||||
const [editingId, setEditingId] = useState('')
|
||||
const [draft, setDraft] = useState('')
|
||||
const [creating, setCreating] = useState(false)
|
||||
const [createDraft, setCreateDraft] = useState('')
|
||||
const [deletingId, setDeletingId] = useState('')
|
||||
const [error, setError] = useState('')
|
||||
const [busy, setBusy] = useState(false)
|
||||
@@ -383,6 +387,21 @@ export function MagicNoteTagManager({
|
||||
}
|
||||
}
|
||||
|
||||
const submitCreate = async (): Promise<void> => {
|
||||
if (busy) return
|
||||
const problem = tagNameError(createDraft)
|
||||
if (problem) { setError(problem); return }
|
||||
setBusy(true)
|
||||
const created = await onCreate(createDraft.trim())
|
||||
setBusy(false)
|
||||
if (created) {
|
||||
setCreateDraft('')
|
||||
setCreating(false)
|
||||
setError('')
|
||||
requestAnimationFrame(() => document.getElementById(`magic-note-tag-rename-${created.id}`)?.focus())
|
||||
}
|
||||
}
|
||||
|
||||
return createPortal(
|
||||
<div className="custom-task-dialog">
|
||||
<section
|
||||
@@ -411,6 +430,27 @@ export function MagicNoteTagManager({
|
||||
</button>
|
||||
</header>
|
||||
<div className="magic-note-tag-manager__body" aria-busy={busy}>
|
||||
{creating ? (
|
||||
<form className="magic-note-tag-manager__create" onSubmit={(event) => { event.preventDefault(); void submitCreate() }}>
|
||||
<input
|
||||
autoFocus
|
||||
aria-label={t('tags.inputLabel')}
|
||||
aria-invalid={Boolean(error)}
|
||||
maxLength={MAGIC_NOTE_TAG_MAX_LENGTH}
|
||||
placeholder={t('tags.inputPlaceholder')}
|
||||
value={createDraft}
|
||||
disabled={busy}
|
||||
onChange={(event) => { setCreateDraft(event.target.value); setError('') }}
|
||||
/>
|
||||
<button type="submit" className="primary-button" disabled={busy}>{t('tags.createButton')}</button>
|
||||
<button type="button" className="secondary-button" disabled={busy} onClick={() => { setCreating(false); setCreateDraft(''); setError('') }}>{t('tags.cancel')}</button>
|
||||
{error && <small className="magic-notes-field-error" role="alert">{error}</small>}
|
||||
</form>
|
||||
) : (
|
||||
<button type="button" className="secondary-button magic-note-tag-manager__create-button" disabled={busy} onClick={() => { setCreating(true); setError('') }}>
|
||||
<Plus aria-hidden="true" size={14} />{t('tags.createButton')}
|
||||
</button>
|
||||
)}
|
||||
{tags.length === 0 ? (
|
||||
<p className="magic-notes-muted">{t('tags.managerEmpty')}</p>
|
||||
) : (
|
||||
|
||||
@@ -1776,6 +1776,22 @@ function MagicNotesWorkspaceView({
|
||||
setNarrowNotesOpen(false)
|
||||
document.getElementById('magic-notes-list-toggle')?.focus({ preventScroll: true })
|
||||
}
|
||||
|
||||
const createNoteTag = async (name: string): Promise<MagicNoteTag | undefined> => {
|
||||
const operation = 'create-tag'
|
||||
if (!beginBusy(operation)) return undefined
|
||||
try {
|
||||
const tag = await window.goodbuddy.magicNotes.createTag({ name })
|
||||
notifySuccess(t('tags.created'))
|
||||
await refreshNotes(requestedNoteIdRef.current, true)
|
||||
return tag
|
||||
} catch (error) {
|
||||
notifyError(error)
|
||||
return undefined
|
||||
} finally {
|
||||
endBusy(operation)
|
||||
}
|
||||
}
|
||||
useEffect(() => {
|
||||
if (notesDrawerOpen) document.querySelector<HTMLInputElement>('#magic-library-panel-notes input[type="search"]')?.focus({ preventScroll: true })
|
||||
}, [notesDrawerOpen])
|
||||
@@ -3393,6 +3409,7 @@ function MagicNotesWorkspaceView({
|
||||
<MagicNoteTagManager
|
||||
tags={noteTags}
|
||||
onClose={() => setTagManagerOpen(false)}
|
||||
onCreate={createNoteTag}
|
||||
onRename={renameNoteTag}
|
||||
onDelete={deleteNoteTag}
|
||||
/>
|
||||
|
||||
@@ -135,12 +135,13 @@ export const magicNotes = {
|
||||
inputPlaceholder: 'Type and press Enter',
|
||||
suggestionsLabel: 'Existing tags',
|
||||
create: 'Create "{{name}}"',
|
||||
createButton: 'New tag',
|
||||
remove: 'Remove tag {{name}}',
|
||||
limitReached: 'A note can have at most {{count}} tags',
|
||||
manage: 'Manage tags',
|
||||
managerTitle: 'Manage tags',
|
||||
managerDescription: 'Renaming or deleting a tag updates every note. Notes themselves are never deleted.',
|
||||
managerEmpty: 'No tags yet. Open a note and add one below its title.',
|
||||
managerEmpty: 'No tags yet. Create one here or add one below a note title.',
|
||||
noteCountOne: '{{count}} note',
|
||||
noteCountOther: '{{count}} notes',
|
||||
rename: 'Rename',
|
||||
@@ -154,7 +155,8 @@ export const magicNotes = {
|
||||
mergeHint: 'A tag named "{{name}}" already exists. Saving merges the two tags.',
|
||||
renamed: 'Tag renamed',
|
||||
merged: 'Tag merged into "{{name}}"',
|
||||
deleted: 'Tag deleted'
|
||||
deleted: 'Tag deleted',
|
||||
created: 'Tag created'
|
||||
},
|
||||
todos: {
|
||||
listLabel: 'To-do list',
|
||||
|
||||
@@ -130,12 +130,13 @@ export const magicNotes = {
|
||||
inputPlaceholder: '输入后按回车',
|
||||
suggestionsLabel: '已有标签',
|
||||
create: '新建“{{name}}”',
|
||||
createButton: '新建标签',
|
||||
remove: '移除标签 {{name}}',
|
||||
limitReached: '每篇笔记最多 {{count}} 个标签',
|
||||
manage: '管理标签',
|
||||
managerTitle: '管理标签',
|
||||
managerDescription: '重命名或删除标签会同步到所有笔记,笔记本身不会被删除。',
|
||||
managerEmpty: '还没有标签。打开一篇笔记,在标题下方添加。',
|
||||
managerEmpty: '还没有标签。可以在这里新建,也可以在笔记标题下方添加。',
|
||||
noteCountOne: '{{count}} 篇笔记',
|
||||
noteCountOther: '{{count}} 篇笔记',
|
||||
rename: '重命名',
|
||||
@@ -149,7 +150,8 @@ export const magicNotes = {
|
||||
mergeHint: '已有同名标签“{{name}}”,保存后两个标签会合并。',
|
||||
renamed: '标签已重命名',
|
||||
merged: '标签已合并到“{{name}}”',
|
||||
deleted: '标签已删除'
|
||||
deleted: '标签已删除',
|
||||
created: '标签已创建'
|
||||
},
|
||||
todos: {
|
||||
listLabel: '待办列表',
|
||||
|
||||
@@ -1673,6 +1673,46 @@
|
||||
padding: var(--space-2) var(--space-5) var(--space-5);
|
||||
}
|
||||
|
||||
.magic-note-tag-manager__create {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
padding-block: var(--space-2) var(--space-3);
|
||||
}
|
||||
|
||||
.magic-note-tag-manager__create > input {
|
||||
box-sizing: border-box;
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
min-height: var(--control-height);
|
||||
flex: 1 1 auto;
|
||||
padding: 0 var(--space-3);
|
||||
border: 1px solid var(--border-control);
|
||||
border-radius: var(--radius-control);
|
||||
background: var(--surface-raised);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.magic-note-tag-manager__create > button {
|
||||
flex: 0 0 auto;
|
||||
min-height: var(--control-height);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.magic-note-tag-manager__create > small {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 520px) {
|
||||
.magic-note-tag-manager__create {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.magic-note-tag-manager__create > input {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.magic-note-tag-manager__list {
|
||||
display: grid;
|
||||
margin: 0;
|
||||
@@ -2393,7 +2433,7 @@
|
||||
position: absolute;
|
||||
top: var(--space-2);
|
||||
right: var(--space-2);
|
||||
opacity: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.magic-note-row:hover .magic-note-more,
|
||||
|
||||
Reference in New Issue
Block a user