Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
4554ece
Add i18n pre-initialization to fix module-level macro timing
ophirbucai Apr 11, 2026
8ebe6a3
Apply Lingui t\`\` macro to module-level strings
ophirbucai Apr 11, 2026
3be395d
Extract module-level strings to locale catalogs
ophirbucai Apr 11, 2026
fb4ec9d
Add TypeScript declarations for compiled message catalogs
ophirbucai Apr 11, 2026
a5890c1
Format code with oxfmt and prettier
ophirbucai Apr 11, 2026
7638919
Revert non-module-level translations from MediaLibrary and MediaPicke…
ophirbucai Apr 11, 2026
f96e7c4
Re-extract locale catalogs after removing non-module-level strings
ophirbucai Apr 11, 2026
e53bf34
Clarify init.ts must be imported in both index.ts and admin.astro
ophirbucai Apr 11, 2026
4072071
Suppress lint warnings for intentional side-effect imports
ophirbucai Apr 11, 2026
056af28
Simplify init.ts import comment in index.ts
ophirbucai Apr 11, 2026
c134614
Add .js extension to init module import in admin.astro
ophirbucai Apr 11, 2026
655282e
Fix eager module-level t translations to be lazy/reactive
ophirbucai Apr 11, 2026
026f791
style: format
emdashbot[bot] Apr 11, 2026
09dfe78
Update locale catalog line number references after refactor
ophirbucai Apr 11, 2026
5097436
Use empty messages in init.ts to avoid dependency on compiled catalogs
ophirbucai Apr 11, 2026
c06c2ab
test: provide I18nProvider to all tests via custom render utility
ophirbucai Apr 11, 2026
4d4cb34
style: format
emdashbot[bot] Apr 11, 2026
37cf8c8
test: ensure i18n init in render utility for CI reliability
ophirbucai Apr 12, 2026
18ae5bc
test: revert render utility to JSX syntax
ophirbucai Apr 12, 2026
43c8346
test: fix render utility import paths to use .tsx extension
ophirbucai Apr 12, 2026
c650927
test: fix render import paths for tests in subdirectories
ophirbucai Apr 12, 2026
1eb60cd
test: fix router.test.tsx import path for render utility
ophirbucai Apr 12, 2026
75541ab
chore: simplify i18n comments and builder function docs
ophirbucai Apr 12, 2026
5fccf0a
refactor(admin): convert module-level i18n constants to lazy builder …
ophirbucai Apr 12, 2026
f20b603
style: format
emdashbot[bot] Apr 12, 2026
4a479db
refactor(admin): optimize role lookup in AllowedDomainsSettings
ophirbucai Apr 12, 2026
e8e0e21
chore(admin): remove unused messages.mjs.d.ts declaration file
ophirbucai Apr 12, 2026
0b3cf27
refactor(admin): remove i18n initialization and clean up imports
ophirbucai Apr 12, 2026
9fc0a5c
refactor(admin): migrate AdminCommandPalette to msg and MessageDescri…
ophirbucai Apr 12, 2026
1e45c49
refactor(admin): migrate ContentTypeEditor support and system fields …
ophirbucai Apr 12, 2026
5c3eb13
refactor(admin): PortableTextEditor slash commands use msg and t
ophirbucai Apr 12, 2026
11bc300
refactor(admin): migrate WelcomeModal to msg and useLingui
ophirbucai Apr 12, 2026
1eeccf3
refactor(admin): migrate Widgets built-in palette to msg and useLingui
ophirbucai Apr 12, 2026
9b083de
refactor(admin): simplify command palette nav item titles
ophirbucai Apr 12, 2026
04b9baa
fix(admin): align built-in widget default title with localized label
ophirbucai Apr 12, 2026
4b9ad17
refactor(admin): complete PortableTextEditor slash i18n and tests
ophirbucai Apr 12, 2026
4fd4089
refactor(admin): BlockMenu block transforms use msg + lazy t
ophirbucai Apr 12, 2026
fcd8299
refactor(admin): lazy Lingui pattern for AllowedDomainsSettings
ophirbucai Apr 12, 2026
e266a91
refactor(admin): lazy Lingui for ApiTokenSettings
ophirbucai Apr 12, 2026
1d22390
refactor(admin): centralize role labels with useRolesConfig
ophirbucai Apr 12, 2026
382dba0
refactor(admin): API token scopes as values-only in api-tokens
ophirbucai Apr 12, 2026
5c42849
refactor(admin): migrate ApiTokenSettings and scopes map, include a t…
ophirbucai Apr 12, 2026
95264fd
chore: import order formatting
ophirbucai Apr 12, 2026
24ab66b
chore(i18n): update German and English translations for various compo…
ophirbucai Apr 12, 2026
a4e0c94
style: format
emdashbot[bot] Apr 12, 2026
4545229
Merge branch 'main' into i18n/module-level-extractions
ophirbucai Apr 12, 2026
1f58a53
refactor(InviteUserModal, UserDetails): reuse roleLabels in select items
ophirbucai Apr 12, 2026
7520f12
fix: initialize i18n in test setup and use I18nProvider wrapper
ophirbucai Apr 12, 2026
cd03c0a
style: add void to floating render promises
ophirbucai Apr 12, 2026
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
106 changes: 55 additions & 51 deletions packages/admin/src/components/AdminCommandPalette.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
*/

import { CommandPalette } from "@cloudflare/kumo";
import type { MessageDescriptor } from "@lingui/core";
import { msg } from "@lingui/core/macro";
import { useLingui } from "@lingui/react/macro";
import {
SquaresFour,
FileText,
Expand All @@ -25,9 +28,15 @@ import { useNavigate } from "@tanstack/react-router";
import * as React from "react";
import { useHotkeys } from "react-hotkeys-hook";

import { apiFetch } from "../lib/api/client";
import { apiFetch, type AdminManifest } from "../lib/api/client.js";
import { useCurrentUser } from "../lib/api/current-user";

/** Subset of manifest fields used by the palette (matches `Shell` props shape). */
type CommandPaletteManifest = {
collections: Record<string, { label: string; labelSingular?: string }>;
plugins: AdminManifest["plugins"];
};

// Role levels (matching @emdash-cms/auth)
const ROLE_ADMIN = 50;
const ROLE_EDITOR = 40;
Expand Down Expand Up @@ -75,7 +84,7 @@ interface SearchResponse {

interface NavItem {
id: string;
title: string;
title: string | MessageDescriptor;
to: string;
params?: Record<string, string>;
icon: React.ElementType;
Expand All @@ -84,7 +93,8 @@ interface NavItem {
}

interface ResultGroup {
label: string;
id: string;
label: MessageDescriptor;
items: ResultItem[];
}

Expand All @@ -99,20 +109,7 @@ interface ResultItem {
}

interface AdminCommandPaletteProps {
manifest: {
collections: Record<string, { label: string; labelSingular?: string }>;
plugins: Record<
string,
{
package?: string;
enabled?: boolean;
adminPages?: Array<{
path: string;
label?: string;
}>;
}
>;
};
manifest: CommandPaletteManifest;
}

async function searchContent(query: string): Promise<SearchResponse> {
Expand All @@ -127,14 +124,11 @@ async function searchContent(query: string): Promise<SearchResponse> {
return body.data;
}

function buildNavItems(
manifest: AdminCommandPaletteProps["manifest"],
userRole: number,
): NavItem[] {
function buildNavItems(manifest: CommandPaletteManifest, userRole: number): NavItem[] {
const items: NavItem[] = [
{
id: "dashboard",
title: "Dashboard",
title: msg`Dashboard`,
to: "/",
icon: SquaresFour,
keywords: ["home", "overview"],
Expand All @@ -157,46 +151,46 @@ function buildNavItems(
items.push(
{
id: "media",
title: "Media Library",
title: msg`Media Library`,
to: "/media",
icon: Image,
keywords: ["images", "files", "uploads"],
},
{
id: "menus",
title: "Menus",
title: msg`Menus`,
to: "/menus",
icon: List,
minRole: ROLE_EDITOR,
keywords: ["navigation"],
},
{
id: "widgets",
title: "Widgets",
title: msg`Widgets`,
to: "/widgets",
icon: GridFour,
minRole: ROLE_EDITOR,
keywords: ["sidebar", "footer"],
},
{
id: "sections",
title: "Sections",
title: msg`Sections`,
to: "/sections",
icon: Stack,
minRole: ROLE_EDITOR,
keywords: ["page builder", "blocks"],
},
{
id: "content-types",
title: "Content Types",
title: msg`Content Types`,
to: "/content-types",
icon: Database,
minRole: ROLE_ADMIN,
keywords: ["schema", "collections"],
},
{
id: "categories",
title: "Categories",
title: msg`Categories`,
to: "/taxonomies/$taxonomy",
params: { taxonomy: "category" },
icon: FileText,
Expand All @@ -205,7 +199,7 @@ function buildNavItems(
},
{
id: "tags",
title: "Tags",
title: msg`Tags`,
to: "/taxonomies/$taxonomy",
params: { taxonomy: "tag" },
icon: FileText,
Expand All @@ -214,39 +208,39 @@ function buildNavItems(
},
{
id: "users",
title: "Users",
title: msg`Users`,
to: "/users",
icon: Users,
minRole: ROLE_ADMIN,
keywords: ["accounts", "team"],
},
{
id: "plugins",
title: "Plugins",
title: msg`Plugins`,
to: "/plugins-manager",
icon: PuzzlePiece,
minRole: ROLE_ADMIN,
keywords: ["extensions", "add-ons"],
},
{
id: "import",
title: "Import",
title: msg`Import`,
to: "/import/wordpress",
icon: Upload,
minRole: ROLE_ADMIN,
keywords: ["wordpress", "migrate"],
},
{
id: "settings",
title: "Settings",
title: msg`Settings`,
to: "/settings",
icon: Gear,
minRole: ROLE_ADMIN,
keywords: ["configuration", "preferences"],
},
{
id: "security",
title: "Security Settings",
title: msg`Security Settings`,
to: "/settings/security",
icon: Gear,
minRole: ROLE_ADMIN,
Expand Down Expand Up @@ -281,17 +275,23 @@ function buildNavItems(
return items.filter((item) => !item.minRole || userRole >= item.minRole);
}

function filterNavItems(items: NavItem[], query: string): NavItem[] {
function filterNavItems(
items: NavItem[],
query: string,
translate: (d: MessageDescriptor) => string,
): NavItem[] {
if (!query) return items;
const lowerQuery = query.toLowerCase();
return items.filter((item) => {
const titleMatch = item.title.toLowerCase().includes(lowerQuery);
const titleStr = typeof item.title === "string" ? item.title : translate(item.title);
const titleMatch = titleStr.toLowerCase().includes(lowerQuery);
const keywordMatch = item.keywords?.some((k) => k.toLowerCase().includes(lowerQuery));
return titleMatch || keywordMatch;
});
}

export function AdminCommandPalette({ manifest }: AdminCommandPaletteProps) {
const { t } = useLingui();
const [open, setOpen] = React.useState(false);
const [query, setQuery] = React.useState("");
const navigate = useNavigate();
Expand Down Expand Up @@ -320,8 +320,8 @@ export function AdminCommandPalette({ manifest }: AdminCommandPaletteProps) {

// Filter nav items based on query
const filteredNavItems = React.useMemo(
() => filterNavItems(allNavItems, query),
[allNavItems, query],
() => filterNavItems(allNavItems, query, t),
[allNavItems, query, t],
);

// Build result groups
Expand All @@ -331,10 +331,11 @@ export function AdminCommandPalette({ manifest }: AdminCommandPaletteProps) {
// Navigation group
if (filteredNavItems.length > 0) {
groups.push({
label: "Navigation",
id: "navigation",
label: msg`Navigation`,
items: filteredNavItems.map((item) => ({
id: item.id,
title: item.title,
title: typeof item.title === "string" ? item.title : t(item.title),
to: item.to,
params: item.params,
icon: <item.icon className="h-4 w-4" />,
Expand All @@ -346,25 +347,28 @@ export function AdminCommandPalette({ manifest }: AdminCommandPaletteProps) {
if (searchResults?.items && searchResults.items.length > 0) {
const contentItems = searchResults.items.map((result) => {
const collectionConfig = manifest.collections[result.collection];
const collectionLabel = collectionConfig?.label ?? result.collection;

return {
id: `content-${result.id}`,
title: result.title || result.slug,
to: "/content/$collection/$id",
params: { collection: result.collection, id: result.id },
icon: <FileText className="h-4 w-4" />,
description: collectionConfig?.label || result.collection,
description: collectionLabel,
collection: result.collection,
};
});

groups.push({
label: "Content",
id: "content",
label: msg`Content`,
items: contentItems,
});
}

return groups;
}, [filteredNavItems, searchResults, manifest.collections]);
}, [filteredNavItems, searchResults, manifest.collections, t]);

// Keyboard shortcut to open (Cmd+K / Ctrl+K)
useHotkeys("mod+k", (e) => {
Expand Down Expand Up @@ -413,12 +417,12 @@ export function AdminCommandPalette({ manifest }: AdminCommandPaletteProps) {
items={resultGroups}
value={query}
onValueChange={setQuery}
itemToStringValue={(group) => group.label}
itemToStringValue={(group) => t(group.label)}
onSelect={handleSelect}
getSelectableItems={(groups) => groups.flatMap((g) => g.items)}
>
<CommandPalette.Input
placeholder="Search pages and content..."
placeholder={t`Search pages and content...`}
leading={<MagnifyingGlass className="h-4 w-4 text-kumo-subtle" weight="bold" />}
/>
<CommandPalette.List>
Expand All @@ -428,8 +432,8 @@ export function AdminCommandPalette({ manifest }: AdminCommandPaletteProps) {
<>
<CommandPalette.Results>
{(group: ResultGroup) => (
<CommandPalette.Group key={group.label} items={group.items}>
<CommandPalette.GroupLabel>{group.label}</CommandPalette.GroupLabel>
<CommandPalette.Group key={group.id} items={group.items}>
<CommandPalette.GroupLabel>{t(group.label)}</CommandPalette.GroupLabel>
<CommandPalette.Items>
{(item: ResultItem) => (
<CommandPalette.ResultItem
Expand All @@ -445,25 +449,25 @@ export function AdminCommandPalette({ manifest }: AdminCommandPaletteProps) {
</CommandPalette.Group>
)}
</CommandPalette.Results>
<CommandPalette.Empty>No results found</CommandPalette.Empty>
<CommandPalette.Empty>{t`No results found`}</CommandPalette.Empty>
</>
)}
</CommandPalette.List>
<CommandPalette.Footer>
<div className="flex items-center gap-4 text-kumo-subtle">
<span className="flex items-center gap-1">
<kbd className="rounded bg-kumo-control px-1.5 py-0.5 text-xs">Enter</kbd>
<span>to select</span>
<span>{t`to select`}</span>
</span>
<span className="flex items-center gap-1">
<kbd className="rounded bg-kumo-control px-1.5 py-0.5 text-xs">
{IS_MAC ? "Cmd" : "Ctrl"}+Enter
</kbd>
<span>new tab</span>
<span>{t`new tab`}</span>
</span>
<span className="flex items-center gap-1">
<kbd className="rounded bg-kumo-control px-1.5 py-0.5 text-xs">Esc</kbd>
<span>to close</span>
<span>{t`to close`}</span>
</span>
</div>
</CommandPalette.Footer>
Expand Down
Loading
Loading