| 15 | } |
| 16 | |
| 17 | export function FrontmatterEditor({ value, onChange }: FrontmatterEditorProps) { |
| 18 | const t = useT(); |
| 19 | const [tagInput, setTagInput] = useState(""); |
| 20 | |
| 21 | function set<K extends keyof PageFrontmatter>(key: K, v: PageFrontmatter[K]) { |
| 22 | onChange({ ...value, [key]: v }); |
| 23 | } |
| 24 | |
| 25 | const tags = (value.tags || []) as string[]; |
| 26 | const sources = (value.sources || []) as string[]; |
| 27 | |
| 28 | function addTag() { |
| 29 | const tg = tagInput.trim(); |
| 30 | if (!tg) return; |
| 31 | if (tags.includes(tg)) return; |
| 32 | set("tags", [...tags, tg]); |
| 33 | setTagInput(""); |
| 34 | } |
| 35 | |
| 36 | function removeTag(tg: string) { |
| 37 | set("tags", tags.filter((x) => x !== tg)); |
| 38 | } |
| 39 | |
| 40 | return ( |
| 41 | <div className="space-y-3 text-sm"> |
| 42 | <Row label={t("fm.field.title")}> |
| 43 | <input |
| 44 | type="text" |
| 45 | value={value.title || ""} |
| 46 | onChange={(e) => set("title", e.target.value)} |
| 47 | className="w-full rounded border bg-background px-2 py-1 text-sm" |
| 48 | /> |
| 49 | </Row> |
| 50 | <Row label={t("fm.field.type")}> |
| 51 | <Select |
| 52 | value={value.type} |
| 53 | options={TYPE_OPTIONS} |
| 54 | renderOption={(o) => t(`type.${o}` as never) || o} |
| 55 | onChange={(v) => set("type", v)} |
| 56 | /> |
| 57 | </Row> |
| 58 | <Row label={t("fm.field.status")}> |
| 59 | <Select |
| 60 | value={value.status} |
| 61 | options={STATUS_OPTIONS} |
| 62 | renderOption={(o) => t(`status.${o}` as never) || o} |
| 63 | onChange={(v) => set("status", v)} |
| 64 | /> |
| 65 | </Row> |
| 66 | <Row label={t("fm.field.confidence")}> |
| 67 | <Select |
| 68 | value={value.confidence} |
| 69 | options={CONFIDENCE_OPTIONS} |
| 70 | renderOption={(o) => t(`confidence.${o}` as never) || o} |
| 71 | onChange={(v) => set("confidence", v)} |
| 72 | /> |
| 73 | </Row> |
| 74 | <Row label={t("fm.field.last_modified_by")}> |