[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
This commit is contained in:
94
components/notes/NoteOutline.tsx
Normal file
94
components/notes/NoteOutline.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
import { ListTree, X } from "lucide-react";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import { extractNoteHeadings, type NoteHeadingItem } from "../../domain/notes";
|
||||
import { useI18n } from "../../application/i18n/I18nProvider";
|
||||
|
||||
export interface NoteOutlineProps {
|
||||
content: string;
|
||||
onSelectHeading?: (heading: NoteHeadingItem, index: number) => void;
|
||||
onClose?: () => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const NoteOutline: React.FC<NoteOutlineProps> = ({
|
||||
content,
|
||||
onSelectHeading,
|
||||
onClose,
|
||||
className = "",
|
||||
}) => {
|
||||
const { t } = useI18n();
|
||||
const headings = useMemo(() => extractNoteHeadings(content), [content]);
|
||||
const [activeHeadingId, setActiveHeadingId] = useState<string | null>(null);
|
||||
const minimumHeadingLevel = useMemo(
|
||||
() => headings.length > 0
|
||||
? Math.min(...headings.map((heading) => heading.level))
|
||||
: 1,
|
||||
[headings],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setActiveHeadingId(null);
|
||||
}, [content]);
|
||||
|
||||
return (
|
||||
<nav
|
||||
aria-label={t("notes.outline.title")}
|
||||
data-note-outline="true"
|
||||
className={`flex h-full flex-col bg-background select-none ${className}`}
|
||||
>
|
||||
<div className="flex shrink-0 items-center justify-between px-3.5 pb-2 pt-3">
|
||||
<div className="flex min-w-0 items-center gap-2 text-xs font-medium text-foreground/90">
|
||||
<ListTree size={14} className="shrink-0 text-muted-foreground" />
|
||||
<span className="truncate">{t("notes.outline.title")}</span>
|
||||
<span className="shrink-0 text-[11px] font-normal tabular-nums text-muted-foreground/60">
|
||||
{headings.length}
|
||||
</span>
|
||||
</div>
|
||||
{onClose && (
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-md p-1 text-muted-foreground transition-colors hover:bg-secondary/60 hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/60"
|
||||
onClick={onClose}
|
||||
title={t("common.close")}
|
||||
aria-label={t("common.close")}
|
||||
>
|
||||
<X size={14} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex-1 space-y-0.5 overflow-y-auto px-2 pb-3">
|
||||
{headings.length === 0 ? (
|
||||
<div className="px-3 py-8 text-center text-xs text-muted-foreground">
|
||||
<p>{t("notes.outline.empty")}</p>
|
||||
<p className="mt-1 text-[11px] opacity-70">
|
||||
{t("notes.outline.emptyHint")}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
headings.map((item, index) => (
|
||||
<button
|
||||
key={item.id}
|
||||
type="button"
|
||||
data-note-outline-item={item.id}
|
||||
data-heading-level={item.level}
|
||||
className={`flex w-full items-center rounded-md py-1.5 pr-2 text-left text-xs leading-5 transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/60 ${
|
||||
activeHeadingId === item.id
|
||||
? "bg-secondary/60 font-medium text-foreground"
|
||||
: "text-muted-foreground hover:bg-secondary/40 hover:text-foreground"
|
||||
}`}
|
||||
style={{ paddingLeft: `${10 + Math.min(item.level - minimumHeadingLevel, 4) * 12}px` }}
|
||||
onClick={() => {
|
||||
setActiveHeadingId(item.id);
|
||||
onSelectHeading?.(item, index);
|
||||
}}
|
||||
title={item.text}
|
||||
>
|
||||
<span className="truncate">{item.text}</span>
|
||||
</button>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user