@gntik-ai/templates 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +47 -40
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/audit-log/Page.test.tsx +53 -0
- package/src/audit-log/Page.tsx +13 -5
- package/src/audit-log/template.meta.ts +1 -1
- package/src/help-home/Page.tsx +3 -1
- package/src/help-home/data.ts +3 -4
- package/src/incident-board/Page.tsx +6 -1
- package/src/incident-board/data.ts +9 -3
- package/src/keyboard-shortcuts/ShortcutSheet.tsx +3 -1
- package/src/keyboard-shortcuts/data.ts +4 -3
- package/src/live-event-stream/Page.test.tsx +8 -4
- package/src/resource-gallery/Page.tsx +3 -1
- package/src/resource-gallery/data.ts +3 -4
- package/src/resource-index/Page.tsx +3 -1
- package/src/resource-index/data.ts +7 -4
- package/src/search-results/Page.tsx +5 -5
- package/src/test/match-text.test.ts +51 -0
- package/src/usage-analytics/Page.tsx +3 -1
- package/src/usage-analytics/data.ts +9 -3
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { ActivityFeed, AppSidebarFooter, AppSidebarHeader, AppSidebarNav, AppTopbar, AuthHeader, BulkActionBar, ChartCard, CodePanel, CommentThread, ConfirmDestructive, CreateApiKeyDialog, DEPLOYMENT_COLUMNS, DEPLOYMENT_DETAILS, DEPLOYMENT_EVENTS, DEPLOYMENT_ROWS, DangerZone, DataTable, DateRangeFilter, DescriptionListCard, ErrorPanel, EvaluationScorecard, Faq, FeatureGrid, FileUploadPanel, FilterBar, FirstRunEmpty, FlowBuilder, ForgotPasswordForm, FormSection, Hero, InlineCallout, InviteMembersDialog, InvoiceTable, KPI_ITEMS, KpiRow, ListSkeleton, LogViewer, MembersTable, MfaChallenge, ModelKeysList, NoResultsEmpty, NotificationMatrix, PROJECT_ACTIVITY, PageHeader, PageToolbar, PaymentMethodCard, PendingInvitations, PlanCard, PricingTable, ProfileHeader, PromptEditor, QuotaMeters, RESOURCE_CARDS, ResetPasswordForm, ResourceCardGrid, SectionHeader, SessionsDevices, SettingsRow, SignInForm, SignUpForm, SsoButtons, StatusTimeline, StickyActionBar, SystemBanner, TokenCostCard, TraceWaterfall, TrendDelta, ValidationSummary, apiKeyExpiries, apiKeyScopes, appSidebarNavGroups, appSidebarUser, appSidebarUser as appSidebarUser$1, appSidebarWorkspaces, appTopbarNotifications, firstRunSteps, formatSpanDuration, generateDemoSecret, memberRoles, noAccessSample, notificationChannels, notificationDefaults, notificationEvents, sampleDeviceSessions, sampleInvitations, sampleInvoices, sampleMembers, samplePaymentMethod, samplePlan, samplePricingTiers, sampleProfile, sampleQuotas, ssoProviders, ssoProviders as ssoProviders$1 } from "@gntik-ai/blocks";
|
|
3
|
-
import { Alert, AuthLayout, Avatar, AvatarGroup, Badge, Blockquote, Breadcrumbs, Button, CanvasLayout, Card, CardBody, CardDescription, CardHeader, CardTitle, Checkbox, CheckboxGroup, ClickableCard, CodeBlock, ContextMenu, ContextMenuContent, ContextMenuTrigger, Dialog, DialogBody, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, DocsLayout, Drawer, DrawerBody, DrawerContent, DrawerDescription, DrawerHeader, DrawerTitle, EmptyState, Field, FieldDescription, FieldError, FieldLabel, Grid, GridItem, HStack, Heading, IconButton, Input, InspectorLayout, InspectorToggle, JsonViewer, KanbanBoard, Kbd, KbdCombo, Link, ListInput, LiveAnnouncer, Logo, MegaMenu, MenuItem, MenuSeparator, Meter, MobileNav, NavList, OtpInput, OverflowList, Page, PasswordInput, PowerSearch, Progress, Section, Select, SelectContent, SelectItem, SelectTrigger, SelectableCard, SelectableCardGroup, Separator, SettingsLayout, SidebarLayout, SimpleSelect, Skeleton, Spinner, SplitLayout, Stack, StackedLayout, StatusDot, StatusLayout, StatusTag, Switch, Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow, Tabs, TabsList, TabsPanel, TabsTab, Text, Textarea, ThemeSwitcher, Thumbnail, ThumbnailList, Timer, Timestamp, Toggle, ToggleGroup, TokenInput, TooltipProvider, TransferList, TreeList, VisuallyHidden, WizardLayout, cn, emptyPowerSearchQuery, formatPowerSearch, matchPowerSearch, megaMenuNavItems, moveKanbanCard, parsePowerSearch, useAnnounce, useHasLiveAnnouncer, useI18n, useInspectorLayout, useTheme } from "@gntik-ai/ui";
|
|
3
|
+
import { Alert, AuthLayout, Avatar, AvatarGroup, Badge, Blockquote, Breadcrumbs, Button, CanvasLayout, Card, CardBody, CardDescription, CardHeader, CardTitle, Checkbox, CheckboxGroup, ClickableCard, CodeBlock, ContextMenu, ContextMenuContent, ContextMenuTrigger, Dialog, DialogBody, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, DocsLayout, Drawer, DrawerBody, DrawerContent, DrawerDescription, DrawerHeader, DrawerTitle, EmptyState, Field, FieldDescription, FieldError, FieldLabel, Grid, GridItem, HStack, Heading, IconButton, Input, InspectorLayout, InspectorToggle, JsonViewer, KanbanBoard, Kbd, KbdCombo, Link, ListInput, LiveAnnouncer, Logo, MegaMenu, MenuItem, MenuSeparator, Meter, MobileNav, NavList, OtpInput, OverflowList, Page, PasswordInput, PowerSearch, Progress, Section, Select, SelectContent, SelectItem, SelectTrigger, SelectableCard, SelectableCardGroup, Separator, SettingsLayout, SidebarLayout, SimpleSelect, Skeleton, Spinner, SplitLayout, Stack, StackedLayout, StatusDot, StatusLayout, StatusTag, Switch, Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow, Tabs, TabsList, TabsPanel, TabsTab, Text, Textarea, ThemeSwitcher, Thumbnail, ThumbnailList, Timer, Timestamp, Toggle, ToggleGroup, TokenInput, TooltipProvider, TransferList, TreeList, VisuallyHidden, WizardLayout, cn, emptyPowerSearchQuery, formatPowerSearch, matchPowerSearch, matchText, megaMenuNavItems, moveKanbanCard, parsePowerSearch, useAnnounce, useHasLiveAnnouncer, useI18n, useInspectorLayout, useTheme } from "@gntik-ai/ui";
|
|
4
4
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
5
5
|
import { Fragment as Fragment$1, createElement, useEffect, useEffectEvent, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
6
6
|
import { ChatAnnouncer, ChatComposer, ChatLayout, ChatMessage, CitationList, Markdown, SuggestionChips, ToolCallCard } from "@gntik-ai/chat";
|
|
@@ -721,8 +721,8 @@ const DAY$1 = 864e5;
|
|
|
721
721
|
const startOfDay = (d) => new Date(d.getFullYear(), d.getMonth(), d.getDate());
|
|
722
722
|
const fieldValue$1 = (e, field) => field === "actor" ? e.actor.name : field === "resource" ? e.resource : field === "result" ? e.result : "";
|
|
723
723
|
/** Applies the search text, the filter chips (OR within a field, AND across fields) and the date range. */
|
|
724
|
-
function filterAuditEvents(events, query, filters, range) {
|
|
725
|
-
const q = query.trim()
|
|
724
|
+
function filterAuditEvents(events, query, filters, range, locale) {
|
|
725
|
+
const q = query.trim();
|
|
726
726
|
const fields = [...new Set(filters.map((f) => f.field))];
|
|
727
727
|
const from = range.start ? startOfDay(range.start).getTime() : -Infinity;
|
|
728
728
|
const to = range.end ? startOfDay(range.end).getTime() + DAY$1 : Infinity;
|
|
@@ -732,13 +732,17 @@ function filterAuditEvents(events, query, filters, range) {
|
|
|
732
732
|
e.actor.email,
|
|
733
733
|
e.action,
|
|
734
734
|
e.target
|
|
735
|
-
].some((v) => v
|
|
735
|
+
].some((v) => matchText(v, q, { locale }))) return false;
|
|
736
736
|
if (!fields.every((field) => filters.some((f) => f.field === field && f.value === fieldValue$1(e, field)))) return false;
|
|
737
737
|
const t = e.at.getTime();
|
|
738
738
|
return t >= from && t < to;
|
|
739
739
|
});
|
|
740
740
|
}
|
|
741
|
-
|
|
741
|
+
/** Neutralises leading spreadsheet formula triggers before applying CSV quoting. */
|
|
742
|
+
function escapeCsvCell(value) {
|
|
743
|
+
const safe = /^[=+@\t\r-]/.test(value) ? `'${value}` : value;
|
|
744
|
+
return /[",\n]/.test(safe) ? `"${safe.replace(/"/g, "\"\"")}"` : safe;
|
|
745
|
+
}
|
|
742
746
|
/** CSV of the events (time, actor, action, resource, target, ip, result). */
|
|
743
747
|
function auditEventsToCsv(events) {
|
|
744
748
|
return [[
|
|
@@ -759,7 +763,7 @@ function auditEventsToCsv(events) {
|
|
|
759
763
|
e.target,
|
|
760
764
|
e.ip,
|
|
761
765
|
e.result
|
|
762
|
-
])].map((r) => r.map(
|
|
766
|
+
])].map((r) => r.map(escapeCsvCell).join(",")).join("\n");
|
|
763
767
|
}
|
|
764
768
|
function downloadCsv(events) {
|
|
765
769
|
if (typeof URL.createObjectURL !== "function") return;
|
|
@@ -772,6 +776,7 @@ function downloadCsv(events) {
|
|
|
772
776
|
}
|
|
773
777
|
/** Audit log: ConsoleShell + Page with FilterBar + DateRangeFilter, an events DataTable and a diff drawer. */
|
|
774
778
|
function AuditLogPage(props) {
|
|
779
|
+
const { locale } = useI18n();
|
|
775
780
|
const { events = auditEvents, filterFields = auditFilterFields, defaultRange, today, onExport = downloadCsv, breadcrumbs = auditBreadcrumbs, currentHref = "/activity", title = "Audit log", description = "Every change made in this workspace: who did it, when, from where and what changed.", shell } = props;
|
|
776
781
|
const [query, setQuery] = useState("");
|
|
777
782
|
const [filters, setFilters] = useState([]);
|
|
@@ -784,7 +789,7 @@ function AuditLogPage(props) {
|
|
|
784
789
|
};
|
|
785
790
|
});
|
|
786
791
|
const [diffEvent, setDiffEvent] = useState(null);
|
|
787
|
-
const rows = filterAuditEvents(events, query, filters, range);
|
|
792
|
+
const rows = filterAuditEvents(events, query, filters, range, locale);
|
|
788
793
|
const columns = [
|
|
789
794
|
{
|
|
790
795
|
id: "at",
|
|
@@ -973,7 +978,7 @@ const meta$2 = {
|
|
|
973
978
|
family: "Work",
|
|
974
979
|
priority: "P2",
|
|
975
980
|
status: "beta",
|
|
976
|
-
description: "Workspace audit trail: FilterBar (search, actor / resource / result filters) and DateRangeFilter over a DataTable of events with actor, action, target and result; a drawer shows the change diff (CodeBlock); export to CSV.",
|
|
981
|
+
description: "Workspace audit trail: FilterBar (search, actor / resource / result filters) and DateRangeFilter over a DataTable of events with actor, action, target and result; a drawer shows the change diff (CodeBlock); export to CSV. CSV export neutralises leading spreadsheet formula triggers.",
|
|
977
982
|
layout: "SidebarLayout · Page",
|
|
978
983
|
blocks: [
|
|
979
984
|
"app-sidebar",
|
|
@@ -3541,19 +3546,18 @@ const galleryColumns = [
|
|
|
3541
3546
|
}
|
|
3542
3547
|
];
|
|
3543
3548
|
/** Case-insensitive search over name and description. */
|
|
3544
|
-
function searchResources(items, query) {
|
|
3545
|
-
|
|
3546
|
-
if (!q) return [...items];
|
|
3547
|
-
return items.filter((i) => i.name.toLowerCase().includes(q) || (i.description ?? "").toLowerCase().includes(q));
|
|
3549
|
+
function searchResources(items, query, locale) {
|
|
3550
|
+
return items.filter((i) => matchText(i.name, query, { locale }) || matchText(i.description ?? "", query, { locale }));
|
|
3548
3551
|
}
|
|
3549
3552
|
//#endregion
|
|
3550
3553
|
//#region src/resource-gallery/Page.tsx
|
|
3551
3554
|
/** Resource gallery: toolbar with search and a grid/table view toggle over the same resources. */
|
|
3552
3555
|
function ResourceGalleryPage({ title = "Resources", description = "Services, databases and jobs that make up this project.", breadcrumbs = galleryBreadcrumbs, items: initialItems = galleryItems, defaultView = "grid", onViewChange, createLabel = "New resource", onCreate, onAction, shell }) {
|
|
3556
|
+
const { locale } = useI18n();
|
|
3553
3557
|
const [items, setItems] = useState(initialItems);
|
|
3554
3558
|
const [view, setView] = useState(defaultView);
|
|
3555
3559
|
const [query, setQuery] = useState("");
|
|
3556
|
-
const visible = searchResources(items, query);
|
|
3560
|
+
const visible = searchResources(items, query, locale);
|
|
3557
3561
|
const handleAction = (action, item) => {
|
|
3558
3562
|
if (onAction) onAction(action, item);
|
|
3559
3563
|
else if (action === "delete") setItems((prev) => prev.filter((i) => i.id !== item.id));
|
|
@@ -3728,16 +3732,17 @@ const text = (v) => typeof v === "string" || typeof v === "number" ? String(v).t
|
|
|
3728
3732
|
* A filter field reads the column with the same id (its `accessor`), else the row property of that
|
|
3729
3733
|
* name, so statuses come from the columns.
|
|
3730
3734
|
*/
|
|
3731
|
-
function filterResources(rows, query, filters, columns) {
|
|
3732
|
-
const q = query.trim()
|
|
3735
|
+
function filterResources(rows, query, filters, columns, locale) {
|
|
3736
|
+
const q = query.trim();
|
|
3733
3737
|
const byField = /* @__PURE__ */ new Map();
|
|
3734
3738
|
for (const f of filters) byField.set(f.field, [...byField.get(f.field) ?? [], f.value.toLowerCase()]);
|
|
3735
3739
|
return rows.filter((row) => {
|
|
3736
3740
|
if (q) {
|
|
3737
3741
|
const r = row;
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
if (
|
|
3742
|
+
const searchText = (v) => typeof v === "string" || typeof v === "number" ? String(v) : void 0;
|
|
3743
|
+
let haystack = [r.name, r.id].map(searchText).filter((v) => v !== void 0);
|
|
3744
|
+
if (haystack.length === 0 && columns) haystack = columns.map((c) => searchText(c.accessor?.(row))).filter((v) => v !== void 0);
|
|
3745
|
+
if (!haystack.some((v) => matchText(v, q, { locale }))) return false;
|
|
3741
3746
|
}
|
|
3742
3747
|
for (const [field, values] of byField) {
|
|
3743
3748
|
const value = text(fieldValue(row, field, columns));
|
|
@@ -3758,11 +3763,12 @@ const defaultArchive = (row) => "status" in row ? {
|
|
|
3758
3763
|
} : row;
|
|
3759
3764
|
/** Resource index: header, filter bar, selectable sortable table with pagination, bulk action bar and a no-results state. */
|
|
3760
3765
|
function ResourceIndexPage({ title = "Deployments", description = "Every service deployed in this workspace, across regions.", breadcrumbs = resourceBreadcrumbs, rows: initialRows = resourceRows, columns = resourceColumns, filterFields = resourceFilterFields, savedViews = resourceSavedViews, getRowId = defaultRowId, getRowHref, onRowClick, linkColumnId, noun = ["deployment", "deployments"], createLabel = "New deployment", onCreate, onOpen, onBulkAction, archiveRow = defaultArchive, loading = false, shell }) {
|
|
3766
|
+
const { locale } = useI18n();
|
|
3761
3767
|
const [rows, setRows] = useState(initialRows);
|
|
3762
3768
|
const [query, setQuery] = useState("");
|
|
3763
3769
|
const [filters, setFilters] = useState([]);
|
|
3764
3770
|
const [selected, setSelected] = useState([]);
|
|
3765
|
-
const visible = filterResources(rows, query, filters, columns);
|
|
3771
|
+
const visible = filterResources(rows, query, filters, columns, locale);
|
|
3766
3772
|
const visibleSelected = selected.filter((id) => visible.some((r) => getRowId(r) === id));
|
|
3767
3773
|
const tableColumns = useMemo(() => linkColumns(columns, linkColumnId ?? columns[0]?.id, getRowHref, onRowClick), [
|
|
3768
3774
|
columns,
|
|
@@ -4048,19 +4054,18 @@ const EMPTY_SELECTION = {
|
|
|
4048
4054
|
};
|
|
4049
4055
|
const facetValue = (hit, facet) => facet === "type" ? hit.type : hit.owner;
|
|
4050
4056
|
/** Hits whose title or snippet contain the query (case-insensitive). */
|
|
4051
|
-
function matchQuery(hits, query) {
|
|
4052
|
-
|
|
4053
|
-
return q ? hits.filter((h) => `${h.title} ${h.snippet}`.toLowerCase().includes(q)) : [...hits];
|
|
4057
|
+
function matchQuery(hits, query, locale) {
|
|
4058
|
+
return hits.filter((h) => matchText(`${h.title} ${h.snippet}`, query, { locale }));
|
|
4054
4059
|
}
|
|
4055
4060
|
/** Search results: ConsoleShell + Page with Checkbox-group facets and results grouped by type. */
|
|
4056
4061
|
function SearchResultsPage(props) {
|
|
4057
|
-
const { t } = useI18n();
|
|
4062
|
+
const { t, locale } = useI18n();
|
|
4058
4063
|
const { hits = searchHits, facets = searchFacets, defaultQuery = searchDefaultQuery, onQueryChange, onSelectionChange, breadcrumbs = searchBreadcrumbs, currentHref = "/search", shell } = props;
|
|
4059
4064
|
const id = useId();
|
|
4060
4065
|
const resultsRef = useRef(null);
|
|
4061
4066
|
const [query, setQuery] = useState(defaultQuery);
|
|
4062
4067
|
const [selection, setSelection] = useState(EMPTY_SELECTION);
|
|
4063
|
-
const matched = matchQuery(hits, query);
|
|
4068
|
+
const matched = matchQuery(hits, query, locale);
|
|
4064
4069
|
const results = matched.filter((h) => Object.keys(selection).every((f) => selection[f].length === 0 || selection[f].includes(facetValue(h, f))));
|
|
4065
4070
|
const groups = searchTypeOrder.map((type) => ({
|
|
4066
4071
|
type,
|
|
@@ -7158,12 +7163,12 @@ const usageSavedViews = [
|
|
|
7158
7163
|
* Search by project name; filters match the row field named by the filter id (same field OR,
|
|
7159
7164
|
* across fields AND). Filters on fields the row does not have are ignored.
|
|
7160
7165
|
*/
|
|
7161
|
-
function filterUsage(rows, query, filters) {
|
|
7162
|
-
const q = query.trim()
|
|
7166
|
+
function filterUsage(rows, query, filters, locale) {
|
|
7167
|
+
const q = query.trim();
|
|
7163
7168
|
const byField = /* @__PURE__ */ new Map();
|
|
7164
7169
|
for (const f of filters) byField.set(f.field, [...byField.get(f.field) ?? [], f.value]);
|
|
7165
7170
|
return rows.filter((row) => {
|
|
7166
|
-
if (q && !row.project
|
|
7171
|
+
if (q && !matchText(row.project, q, { locale })) return false;
|
|
7167
7172
|
for (const [field, values] of byField) {
|
|
7168
7173
|
const value = row[field];
|
|
7169
7174
|
if (value !== void 0 && value !== null && !values.includes(String(value))) return false;
|
|
@@ -7286,6 +7291,7 @@ const TRAFFIC_CHART = {
|
|
|
7286
7291
|
};
|
|
7287
7292
|
/** Usage analytics: date range + filters, two charts and a sortable breakdown table with export. */
|
|
7288
7293
|
function UsageAnalyticsPage({ title = "Usage analytics", description = "Traffic, errors and spend by project and environment.", breadcrumbs = usageBreadcrumbs, rows = usageRows, columns = usageColumns, filterFields = usageFilterFields, searchPlaceholder = "Search projects…", savedViews = usageSavedViews, primaryChart, trafficByRange: traffic = trafficByRange, trafficRanges: ranges = trafficRanges, secondaryChart, onRangeChange, onExport, shell }) {
|
|
7294
|
+
const { locale } = useI18n();
|
|
7289
7295
|
const secondary = {
|
|
7290
7296
|
...TRAFFIC_CHART,
|
|
7291
7297
|
...secondaryChart
|
|
@@ -7293,7 +7299,7 @@ function UsageAnalyticsPage({ title = "Usage analytics", description = "Traffic,
|
|
|
7293
7299
|
const [range, setRange] = useState(null);
|
|
7294
7300
|
const [query, setQuery] = useState("");
|
|
7295
7301
|
const [filters, setFilters] = useState([]);
|
|
7296
|
-
const visible = filterUsage(rows, query, filters);
|
|
7302
|
+
const visible = filterUsage(rows, query, filters, locale);
|
|
7297
7303
|
const clearFilters = () => setFilters([]);
|
|
7298
7304
|
const exportAction = {
|
|
7299
7305
|
label: "Export CSV",
|
|
@@ -12412,15 +12418,14 @@ const helpArticles = [
|
|
|
12412
12418
|
}
|
|
12413
12419
|
];
|
|
12414
12420
|
/** Articles whose title or excerpt contains the query. */
|
|
12415
|
-
function searchArticles(articles, query) {
|
|
12416
|
-
|
|
12417
|
-
if (!q) return [...articles];
|
|
12418
|
-
return articles.filter((a) => a.title.toLowerCase().includes(q) || a.excerpt.toLowerCase().includes(q));
|
|
12421
|
+
function searchArticles(articles, query, locale) {
|
|
12422
|
+
return articles.filter((a) => matchText(a.title, query, { locale }) || matchText(a.excerpt, query, { locale }));
|
|
12419
12423
|
}
|
|
12420
12424
|
//#endregion
|
|
12421
12425
|
//#region src/help-home/Page.tsx
|
|
12422
12426
|
/** Help center home on DocsLayout: search, category cards and popular articles. */
|
|
12423
12427
|
function HelpHomePage(props) {
|
|
12428
|
+
const { locale } = useI18n();
|
|
12424
12429
|
const { brand = /* @__PURE__ */ jsx(Logo, {
|
|
12425
12430
|
size: 22,
|
|
12426
12431
|
wordmark: true
|
|
@@ -12438,7 +12443,7 @@ function HelpHomePage(props) {
|
|
|
12438
12443
|
}) } = props;
|
|
12439
12444
|
const [query, setQuery] = useState("");
|
|
12440
12445
|
const ids = useId();
|
|
12441
|
-
const results = searchArticles(articles, query);
|
|
12446
|
+
const results = searchArticles(articles, query, locale);
|
|
12442
12447
|
const searching = query.trim() !== "";
|
|
12443
12448
|
const categoryTitle = (id) => categories.find((c) => c.id === id)?.title ?? id;
|
|
12444
12449
|
const submit = (e) => {
|
|
@@ -13371,21 +13376,22 @@ const shortcutGroups = [
|
|
|
13371
13376
|
}
|
|
13372
13377
|
];
|
|
13373
13378
|
/** Groups with only the shortcuts whose description or keys match the query. */
|
|
13374
|
-
function filterShortcuts(groups, query) {
|
|
13379
|
+
function filterShortcuts(groups, query, locale) {
|
|
13375
13380
|
const q = query.trim().toLowerCase();
|
|
13376
13381
|
if (!q) return [...groups];
|
|
13377
13382
|
return groups.map((g) => ({
|
|
13378
13383
|
...g,
|
|
13379
|
-
shortcuts: g.shortcuts.filter((s) => s.description
|
|
13384
|
+
shortcuts: g.shortcuts.filter((s) => matchText(s.description, query, { locale }) || matchText(g.title, query, { locale }) || [...s.keys, ...s.then ?? []].join(" ").toLowerCase() === q)
|
|
13380
13385
|
})).filter((g) => g.shortcuts.length > 0);
|
|
13381
13386
|
}
|
|
13382
13387
|
//#endregion
|
|
13383
13388
|
//#region src/keyboard-shortcuts/ShortcutSheet.tsx
|
|
13384
13389
|
/** Searchable shortcut sheet shared by the page and the dialog. */
|
|
13385
13390
|
function ShortcutSheet({ groups, headingLevel = 2, columns = 2, autoFocus }) {
|
|
13391
|
+
const { locale } = useI18n();
|
|
13386
13392
|
const [query, setQuery] = useState("");
|
|
13387
13393
|
const countId = useId();
|
|
13388
|
-
const visible = filterShortcuts(groups, query);
|
|
13394
|
+
const visible = filterShortcuts(groups, query, locale);
|
|
13389
13395
|
const total = visible.reduce((n, g) => n + g.shortcuts.length, 0);
|
|
13390
13396
|
return /* @__PURE__ */ jsxs(Stack, {
|
|
13391
13397
|
gap: 5,
|
|
@@ -14508,14 +14514,14 @@ function triageFieldValue(i, field) {
|
|
|
14508
14514
|
if (field === "owner") return i.owner;
|
|
14509
14515
|
}
|
|
14510
14516
|
/** True when the incident matches the search text and every filtered field (OR within a field). */
|
|
14511
|
-
function matchesTriage(i, query, filters) {
|
|
14512
|
-
const q = query.trim()
|
|
14517
|
+
function matchesTriage(i, query, filters, locale) {
|
|
14518
|
+
const q = query.trim();
|
|
14513
14519
|
if (q && ![
|
|
14514
14520
|
i.id,
|
|
14515
14521
|
i.title,
|
|
14516
14522
|
i.service,
|
|
14517
14523
|
i.owner
|
|
14518
|
-
].some((v) => v
|
|
14524
|
+
].some((v) => matchText(v, q, { locale }))) return false;
|
|
14519
14525
|
const byField = /* @__PURE__ */ new Map();
|
|
14520
14526
|
for (const f of filters) byField.set(f.field, [...byField.get(f.field) ?? [], f.value]);
|
|
14521
14527
|
return [...byField].every(([field, values]) => values.includes(triageFieldValue(i, field) ?? ""));
|
|
@@ -14619,6 +14625,7 @@ function IncidentCard({ incident, now }) {
|
|
|
14619
14625
|
});
|
|
14620
14626
|
}
|
|
14621
14627
|
function IncidentBoard({ title, description, defaultColumns, filterFields, defaultFilters, now, onMove, defaultOpenId, onDeclare, onExport }) {
|
|
14628
|
+
const { locale } = useI18n();
|
|
14622
14629
|
const announce = useAnnounce();
|
|
14623
14630
|
const [columns, setColumns] = useState(defaultColumns);
|
|
14624
14631
|
const [query, setQuery] = useState("");
|
|
@@ -14626,7 +14633,7 @@ function IncidentBoard({ title, description, defaultColumns, filterFields, defau
|
|
|
14626
14633
|
const [openId, setOpenId] = useState(defaultOpenId);
|
|
14627
14634
|
const visible = columns.map((c) => ({
|
|
14628
14635
|
...c,
|
|
14629
|
-
cards: c.cards.filter((i) => matchesTriage(i, query, filters))
|
|
14636
|
+
cards: c.cards.filter((i) => matchesTriage(i, query, filters, locale))
|
|
14630
14637
|
}));
|
|
14631
14638
|
const shown = visible.reduce((n, c) => n + c.cards.length, 0);
|
|
14632
14639
|
const openColumn = columns.find((c) => c.cards.some((i) => i.id === openId));
|