diff --git a/app/frontend/src/components/SearchPalette.tsx b/app/frontend/src/components/SearchPalette.tsx index 3e81cb5..9da6fa4 100644 --- a/app/frontend/src/components/SearchPalette.tsx +++ b/app/frontend/src/components/SearchPalette.tsx @@ -1,4 +1,5 @@ import { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router'; import { CommandPalette, isAbortError, type CommandItem } from '@project-graphite/ui'; import { useAuth } from '../auth'; import { itemHref, useSpaces, type Tag } from '../spaces'; @@ -27,6 +28,8 @@ const pages = [ export function SearchPalette({ onClose }: { onClose: () => void }) { const { request, user } = useAuth(); const spaces = useSpaces(); + const navigate = useNavigate(); + const personal = spaces.data?.find((space) => space.kind === 'personal'); const [query, setQuery] = useState(''); const [results, setResults] = useState(); const text = query.trim(); @@ -53,7 +56,22 @@ export function SearchPalette({ onClose }: { onClose: () => void }) { const matches = (label: string) => label.toLowerCase().includes(text.toLowerCase()); const spaceName = (spaceId: string) => spaces.data?.find((space) => space.id === spaceId)?.name; + const actions: CommandItem[] = personal + ? [ + { + group: 'Create', + id: 'new-page', + label: 'New page', + onSelect: () => + void request<{ id: string }>(`/spaces/${personal.id}/notes`, { method: 'POST', body: JSON.stringify({}) }).then((created) => + navigate(`/notes/${created.id}`), + ), + }, + { group: 'Create', href: `/spaces/${personal.id}/tasks`, id: 'new-task', label: 'New task' }, + ] + : []; const items: CommandItem[] = [ + ...actions.filter((action) => !text || matches(action.label)), ...(results?.items ?? []).map((item) => ({ group: 'Items', hint: `${item.kind} · ${item.spaceName}${item.archived ? ' · archived' : ''}`, diff --git a/app/frontend/src/dashboard/capture-widget.tsx b/app/frontend/src/dashboard/capture-widget.tsx new file mode 100644 index 0000000..bd8ddd8 --- /dev/null +++ b/app/frontend/src/dashboard/capture-widget.tsx @@ -0,0 +1,61 @@ +import { useState } from 'react'; +import { useAuth } from '../auth'; +import { useSpaces } from '../spaces'; +import { useAction } from '../useAction'; +import { Empty } from './widget-parts'; + +export function Capture() { + const auth = useAuth(); + const spaces = useSpaces(); + const saving = useAction(); + const [text, setText] = useState(''); + const [spaceId, setSpaceId] = useState(''); + const editable = (spaces.data ?? []).filter((space) => space.role !== 'viewer'); + const target = spaceId || editable[0]?.id; + + if (spaces.data && editable.length === 0) return You can only view your spaces, so there is nowhere to save.; + + function save(kind: 'note' | 'task') { + const content = text.trim(); + if (!content || !target) return; + void saving.run(async () => { + if (kind === 'note') { + await auth.request(`/spaces/${target}/notes`, { method: 'POST', body: JSON.stringify({ text: content }) }); + } else { + await auth.request(`/spaces/${target}/tasks`, { method: 'POST', body: JSON.stringify({ title: content.split(/\r?\n/)[0]!.slice(0, 200) }) }); + } + setText(''); + return kind === 'note' ? 'Saved as a page.' : 'Saved as a task.'; + }, 'Could not save that'); + } + + return ( +
+