Skip to content
Merged
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
117 changes: 117 additions & 0 deletions templates/content/app/components/editor/database/sidebar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -840,4 +840,121 @@ describe("DatabaseSidebarView", () => {
expect(markup).toContain("group-hover:pointer-events-auto");
expect(markup).not.toContain("shadow-sm");
});

it("keeps the viewer add-child slot disabled beside the personal pin action", async () => {
const container = document.createElement("div");
document.body.append(container);
const root = createRoot(container);
const onToggleFavorite = vi.fn();
const onCreateChildPage = vi.fn();
const onCreateChildDatabase = vi.fn();

await act(async () => {
root.render(
<MemoryRouter>
<TooltipProvider>
<DatabaseSidebarView
groups={[
{
id: "all",
label: "All pages",
items: [
{
...item("shared", "Shared page"),
document: {
...item("shared", "Shared page").document,
accessRole: "viewer",
canEdit: false,
canManage: false,
},
},
],
property: null,
value: "all",
},
]}
grouped={false}
isLoading={false}
hasActiveConstraints={false}
openPagesIn="full_page"
noMatchesLabel="No rows match this view"
clearLabel="Clear"
navigationLabel="Database pages"
untitledLabel="Untitled"
onClearResultConstraints={() => {}}
onPreview={() => {}}
onCreateChildPage={onCreateChildPage}
onCreateChildDatabase={onCreateChildDatabase}
onDeleteItem={() => {}}
onToggleFavorite={onToggleFavorite}
/>
</TooltipProvider>
</MemoryRouter>,
);
});

const trigger = container.querySelector<HTMLButtonElement>(
'button[aria-label="More actions for Shared page"]',
);
expect(trigger).toBeTruthy();
expect(
container.querySelectorAll('button[aria-haspopup="menu"]'),
).toHaveLength(1);
const addChild = container.querySelector<HTMLButtonElement>(
'button[aria-label="Add child to Shared page"]',
);
if (!trigger || !addChild) {
throw new Error("Expected aligned viewer sidebar controls");
}
expect(addChild.disabled).toBe(true);
expect(addChild.className).toContain("size-6");
expect(addChild.className).toContain("text-muted-foreground/50");
expect(trigger.compareDocumentPosition(addChild)).toBe(
Node.DOCUMENT_POSITION_FOLLOWING,
);

addChild.focus();
addChild.click();
addChild.dispatchEvent(
new KeyboardEvent("keydown", { bubbles: true, key: "Enter" }),
);
addChild.dispatchEvent(
new KeyboardEvent("keydown", { bubbles: true, key: " " }),
);
expect(document.activeElement).not.toBe(addChild);
expect(onCreateChildPage).not.toHaveBeenCalled();
expect(onCreateChildDatabase).not.toHaveBeenCalled();

await act(async () => {
trigger.dispatchEvent(
new PointerEvent("pointerdown", {
bubbles: true,
button: 0,
pointerType: "mouse",
}),
);
await Promise.resolve();
});

const menuItems = Array.from(
document.querySelectorAll<HTMLElement>("[role=menuitem]"),
);
expect(menuItems.map((menuItem) => menuItem.textContent?.trim())).toEqual([
"Pin to sidebar",
]);

await act(async () => {
menuItems[0]?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
await Promise.resolve();
});
expect(onToggleFavorite).toHaveBeenCalledOnce();
expect(onToggleFavorite).toHaveBeenCalledWith(
expect.objectContaining({
document: expect.objectContaining({ id: "shared" }),
}),
);

act(() => root.unmount());
container.remove();
});
});
33 changes: 23 additions & 10 deletions templates/content/app/components/editor/database/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
import { useEffect, useState, type MouseEvent, type ReactNode } from "react";
import { Link } from "react-router";

import { documentSidebarActionAvailability } from "@/components/sidebar/document-sidebar-actions";
import { Button } from "@/components/ui/button";
import {
Collapsible,
Expand Down Expand Up @@ -717,14 +718,12 @@ function DatabaseSidebarRow({
};
}) {
const t = useT();
const canEdit = item.document.canEdit !== false;
const canManage =
item.document.canManage === true ||
item.document.accessRole === "owner" ||
item.document.accessRole === "admin";
const { canEdit, canManage, canFavorite, hasMenuActions } =
documentSidebarActionAvailability(item.document, {
favoriteAvailable: Boolean(onToggleFavorite),
manageAvailable: Boolean(onDeleteItem),
});
const canCreateChild = canEdit && Boolean(onCreateChildPage);
const hasMenuActions =
Boolean(onToggleFavorite) || (canManage && Boolean(onDeleteItem));
function handleClick(event: MouseEvent<HTMLAnchorElement>) {
if (
event.defaultPrevented ||
Expand Down Expand Up @@ -828,7 +827,7 @@ function DatabaseSidebarRow({
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-48">
{onToggleFavorite ? (
{canFavorite && onToggleFavorite ? (
<DropdownMenuItem onSelect={() => onToggleFavorite(item)}>
<IconStar
className={cn(
Expand All @@ -841,7 +840,10 @@ function DatabaseSidebarRow({
: t("sidebar.pinToSidebar")}
</DropdownMenuItem>
) : null}
{onToggleFavorite && canManage && onDeleteItem ? (
{canFavorite &&
onToggleFavorite &&
canManage &&
onDeleteItem ? (
<DropdownMenuSeparator />
) : null}
{canManage && onDeleteItem ? (
Expand All @@ -857,7 +859,7 @@ function DatabaseSidebarRow({
</DropdownMenu>
)}

{canCreateChild && (
{canCreateChild ? (
<DropdownMenu>
<Tooltip>
<TooltipTrigger asChild>
Expand All @@ -866,6 +868,7 @@ function DatabaseSidebarRow({
type="button"
className="flex size-6 items-center justify-center rounded text-foreground hover:bg-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
aria-label={t("sidebar.addChildTo", { title })}
data-sidebar-add-child
>
<IconPlus size={14} />
</button>
Expand All @@ -888,6 +891,16 @@ function DatabaseSidebarRow({
) : null}
</DropdownMenuContent>
</DropdownMenu>
) : (
<button
type="button"
className="flex size-6 cursor-not-allowed items-center justify-center rounded text-muted-foreground/50"
aria-label={t("sidebar.addChildTo", { title })}
data-sidebar-add-child
disabled
>
<IconPlus size={14} />
</button>
)}
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,9 @@ describe("document sidebar layout", () => {
it("gates page tree actions by document capabilities", () => {
const treeItem = readSidebarSource("./DocumentTreeItem.tsx");

expect(treeItem).toContain("const canEdit = node.canEdit !== false");
expect(treeItem).toContain("const canManage =");
expect(treeItem).toContain("{canEdit && (");
expect(treeItem).toContain("favoriteAvailable: true");
expect(treeItem).toContain("{canFavorite && (");
expect(treeItem).toContain("const canCreateChild = canEdit");
expect(treeItem).toContain("{canManage && (");
});

Expand Down
Loading
Loading