Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/editor/src/NoteList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export function NoteList({ entries, noteCount, fileCount, types, selectedPath, s
const virtualizer = useVirtualizer({ count: entries.length, getScrollElement: () => scrollRef.current, estimateSize: () => 76, overscan: 8 });
const typeIcons = useMemo(() => new Map(types.map((type) => [type.name, collectionTypeIcon(type)])), [types]);
return <section className="note-list-pane" aria-label="Notes and files">
<header className="list-header"><button className="mobile-collections icon-button" aria-label="Collections" onClick={onCollections}><PanelLeft aria-hidden="true" /></button>{leadingActions}<div><h1>{collectionName}</h1><p aria-live="polite">{browserCountLabel(noteCount, fileCount, entries.length, loading, structureLoading, filesLoading, contentIndexing, contentLoaded, total, contentTotal, Boolean(search.trim()), sort)}{contentError && <button className="list-retry" title={contentError} onClick={onRetryContent}>Retry search</button>}{fileError && <button className="list-retry" title={fileError} onClick={onRetryFiles}>Retry files</button>}</p></div>{trailingActions}<button className="icon-button new-note" aria-label="New note" onClick={onCreate}><FilePlus2 aria-hidden="true" /></button></header>
<header className="list-header"><button className="mobile-collections icon-button" aria-label="Collections" onClick={onCollections}><PanelLeft aria-hidden="true" /></button>{leadingActions}<div className="list-header-title"><h1>{collectionName}</h1><p aria-live="polite">{browserCountLabel(noteCount, fileCount, entries.length, loading, structureLoading, filesLoading, contentIndexing, contentLoaded, total, contentTotal, Boolean(search.trim()), sort)}{contentError && <button className="list-retry" title={contentError} onClick={onRetryContent}>Retry search</button>}{fileError && <button className="list-retry" title={fileError} onClick={onRetryFiles}>Retry files</button>}</p></div><div className="list-header-actions">{trailingActions}<button className="icon-button new-note" aria-label="New note" onClick={onCreate}><FilePlus2 aria-hidden="true" /></button></div></header>
<div className="note-list-controls">
<div className="search-field"><Search aria-hidden="true" /><label className="sr-only" htmlFor="note-search">Search notes and files</label><input id="note-search" value={search} onChange={(event) => onSearch(event.target.value)} placeholder="Search" />{search ? <button aria-label="Clear search" onClick={() => onSearch("")}><X aria-hidden="true" /></button> : <button className="quick-open-trigger" aria-label="Quick open" title="Quick open" onClick={onQuickOpen}><kbd>{navigator.platform.includes("Mac") ? "⌘" : "Ctrl"} P</kbd></button>}</div>
<NoteListViewOptions sort={sort} scopeLabel={scopeLabel} onSort={onSort} onClearScope={onClearScope} />
Expand Down
5 changes: 2 additions & 3 deletions apps/editor/src/TypeBrowser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,8 @@ export function TypeList({ types, selectedName, packsSelected = false, leadingAc
<header className="list-header">
<button className="mobile-collections icon-button" aria-label="Collections" onClick={onCollections}><PanelLeft aria-hidden="true" /></button>
{leadingActions}
<div><h1>Types</h1><p>{types.length} {types.length === 1 ? "definition" : "definitions"}</p></div>
{trailingActions}
<button className="icon-button new-type-button" aria-label="New type" title="New type" onClick={onCreate}><FilePlus2 aria-hidden="true" /></button>
<div className="list-header-title"><h1>Types</h1><p>{types.length} {types.length === 1 ? "definition" : "definitions"}</p></div>
<div className="list-header-actions">{trailingActions}<button className="icon-button new-type-button" aria-label="New type" title="New type" onClick={onCreate}><FilePlus2 aria-hidden="true" /></button></div>
</header>
<label className="search-field">
<Search aria-hidden="true" /><span className="sr-only">Search types</span>
Expand Down
16 changes: 14 additions & 2 deletions apps/editor/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -1374,9 +1374,18 @@ select:focus-visible {
padding: 14px 14px 10px 18px;
}

.list-header > div {
.list-header-title {
width: 0;
min-width: 0;
flex: 1;
flex: 1 1 0;
overflow: hidden;
}

.list-header-actions {
display: flex;
flex: 0 0 auto;
align-items: center;
gap: 10px;
}

.list-header h1 {
Expand All @@ -1390,9 +1399,12 @@ select:focus-visible {
}

.list-header p {
overflow: hidden;
margin: 2px 0 0;
color: var(--faint);
font-size: 11px;
text-overflow: ellipsis;
white-space: nowrap;
}

.list-header .list-retry {
Expand Down
50 changes: 50 additions & 0 deletions apps/editor/tests/editor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1124,6 +1124,56 @@ test("resizes, collapses, and restores the desktop sidebars", async ({ page }) =
expect(restored).toBeCloseTo(after, 0);
});

test("keeps note header actions reachable beside a long collection name", async ({ page }) => {
await page.setViewportSize({ width: 1440, height: 760 });
await page.goto("?demo=12");

for (const listWidth of [304, 240]) {
await page.evaluate((width) => {
localStorage.setItem("mdbase-editor:layout", JSON.stringify({
collectionWidth: 176,
listWidth: width,
inspectorWidth: 340,
collectionCollapsed: false,
listCollapsed: false
}));
}, listWidth);
await page.reload();

const heading = page.locator(".list-header h1");
await expect(heading).toHaveText("Writing");
await heading.evaluate((element) => {
element.textContent = "mdbase Reader literature fixture with a deliberately long collection name";
});

const hideNotes = page.getByRole("button", { name: "Hide notes sidebar" });
const newNote = page.getByRole("button", { name: "New note" });
await expect(hideNotes).toBeVisible();
await expect(newNote).toBeVisible();

const geometry = await page.locator(".list-header").evaluate((header) => {
const title = header.querySelector("h1");
const hide = header.querySelector<HTMLElement>('[aria-label="Hide notes sidebar"]');
const create = header.querySelector<HTMLElement>('[aria-label="New note"]');
const pane = header.closest(".note-list-pane");
if (!title || !hide || !create || !pane) throw new Error("Note header controls are missing.");
return {
title: title.getBoundingClientRect().toJSON(),
hide: hide.getBoundingClientRect().toJSON(),
create: create.getBoundingClientRect().toJSON(),
pane: pane.getBoundingClientRect().toJSON()
};
});
expect(geometry.title.right).toBeLessThanOrEqual(geometry.hide.left);
expect(geometry.hide.right).toBeLessThanOrEqual(geometry.create.left);
expect(geometry.create.right).toBeLessThanOrEqual(geometry.pane.right);

await newNote.click();
await expect(page.getByRole("button", { name: "Create note" })).toBeVisible();
await page.locator(".new-note-actions").getByRole("button", { name: "Cancel" }).click();
}
});

test("keeps the current note inspector open and resizable between note switches", async ({ page }) => {
await page.setViewportSize({ width: 1440, height: 760 });
await page.goto("?demo=12");
Expand Down
Loading