@emdash-cms/admin 0.16.1 → 0.17.1
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/{LocaleDirectionProvider-CFKQ39Tn.js → LocaleDirectionProvider-LB7Qjnv1.js} +29 -17
- package/dist/LocaleDirectionProvider-LB7Qjnv1.js.map +1 -0
- package/dist/{config-ZP4qGbGk.d.ts → config-76UcBWeH.d.ts} +1 -1
- package/dist/{config-ZP4qGbGk.d.ts.map → config-76UcBWeH.d.ts.map} +1 -1
- package/dist/index.d.ts +142 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1777 -203
- package/dist/index.js.map +1 -1
- package/dist/locales/ar/messages.mjs +1 -1
- package/dist/locales/de/messages.mjs +1 -1
- package/dist/locales/en/messages.mjs +1 -1
- package/dist/locales/es-419/messages.mjs +1 -1
- package/dist/locales/es-ES/messages.mjs +1 -1
- package/dist/locales/eu/messages.mjs +1 -1
- package/dist/locales/fa/messages.mjs +1 -1
- package/dist/locales/fr/messages.mjs +1 -1
- package/dist/locales/id/messages.mjs +1 -1
- package/dist/locales/index.d.ts +1 -1
- package/dist/locales/index.js +1 -1
- package/dist/locales/ja/messages.mjs +1 -1
- package/dist/locales/ko/messages.mjs +1 -1
- package/dist/locales/nb/messages.mjs +1 -0
- package/dist/locales/pl/messages.mjs +1 -1
- package/dist/locales/pseudo/messages.mjs +1 -1
- package/dist/locales/pt-BR/messages.mjs +1 -1
- package/dist/locales/th/messages.mjs +1 -0
- package/dist/locales/zh-CN/messages.mjs +1 -1
- package/dist/locales/zh-TW/messages.mjs +1 -1
- package/dist/messages-BKmEGNNr.js +6 -0
- package/dist/messages-BKmEGNNr.js.map +1 -0
- package/dist/messages-BLyCd3bH.js +6 -0
- package/dist/messages-BLyCd3bH.js.map +1 -0
- package/dist/messages-BPzwf13U.js +6 -0
- package/dist/messages-BPzwf13U.js.map +1 -0
- package/dist/messages-BunNlQic.js +6 -0
- package/dist/messages-BunNlQic.js.map +1 -0
- package/dist/messages-C3wS1VMq.js +6 -0
- package/dist/messages-C3wS1VMq.js.map +1 -0
- package/dist/messages-C4ONkHEI.js +6 -0
- package/dist/messages-C4ONkHEI.js.map +1 -0
- package/dist/messages-C55KydOS.js +6 -0
- package/dist/messages-C55KydOS.js.map +1 -0
- package/dist/messages-CjirHcRw.js +6 -0
- package/dist/messages-CjirHcRw.js.map +1 -0
- package/dist/messages-CkDIrR_T.js +6 -0
- package/dist/messages-CkDIrR_T.js.map +1 -0
- package/dist/messages-ClrLd7GR.js +6 -0
- package/dist/messages-ClrLd7GR.js.map +1 -0
- package/dist/messages-DM8WFh_S.js +6 -0
- package/dist/messages-DM8WFh_S.js.map +1 -0
- package/dist/messages-DW5uspYc.js +6 -0
- package/dist/messages-DW5uspYc.js.map +1 -0
- package/dist/messages-DrMj1Hby.js +6 -0
- package/dist/messages-DrMj1Hby.js.map +1 -0
- package/dist/messages-Du-_Uh8B.js +6 -0
- package/dist/messages-Du-_Uh8B.js.map +1 -0
- package/dist/messages-DzQ203Bb.js +6 -0
- package/dist/messages-DzQ203Bb.js.map +1 -0
- package/dist/messages-amIeQ8HY.js +6 -0
- package/dist/messages-amIeQ8HY.js.map +1 -0
- package/dist/messages-ngvpRuG5.js +6 -0
- package/dist/messages-ngvpRuG5.js.map +1 -0
- package/dist/messages-rxZmXkNz.js +6 -0
- package/dist/messages-rxZmXkNz.js.map +1 -0
- package/dist/styles.css +1 -1
- package/package.json +3 -3
- package/dist/LocaleDirectionProvider-CFKQ39Tn.js.map +0 -1
- package/dist/messages-6iBQvP4l.js +0 -6
- package/dist/messages-6iBQvP4l.js.map +0 -1
- package/dist/messages-B3qgKgae.js +0 -6
- package/dist/messages-B3qgKgae.js.map +0 -1
- package/dist/messages-B8kb5Oko.js +0 -6
- package/dist/messages-B8kb5Oko.js.map +0 -1
- package/dist/messages-BhSzAEGL.js +0 -6
- package/dist/messages-BhSzAEGL.js.map +0 -1
- package/dist/messages-C0wfBdmD.js +0 -6
- package/dist/messages-C0wfBdmD.js.map +0 -1
- package/dist/messages-CboPeBM0.js +0 -6
- package/dist/messages-CboPeBM0.js.map +0 -1
- package/dist/messages-CehVxd9q.js +0 -6
- package/dist/messages-CehVxd9q.js.map +0 -1
- package/dist/messages-D4ctBYFE.js +0 -6
- package/dist/messages-D4ctBYFE.js.map +0 -1
- package/dist/messages-DHha7f8K.js +0 -6
- package/dist/messages-DHha7f8K.js.map +0 -1
- package/dist/messages-DIehX9BP.js +0 -6
- package/dist/messages-DIehX9BP.js.map +0 -1
- package/dist/messages-DKdkwHEw.js +0 -6
- package/dist/messages-DKdkwHEw.js.map +0 -1
- package/dist/messages-DMJobUj_.js +0 -6
- package/dist/messages-DMJobUj_.js.map +0 -1
- package/dist/messages-Ddgurz-D.js +0 -6
- package/dist/messages-Ddgurz-D.js.map +0 -1
- package/dist/messages-LeijTgr3.js +0 -6
- package/dist/messages-LeijTgr3.js.map +0 -1
- package/dist/messages-S5UPBqIz.js +0 -6
- package/dist/messages-S5UPBqIz.js.map +0 -1
- package/dist/messages-vQ-B-9ct.js +0 -6
- package/dist/messages-vQ-B-9ct.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { c as fetchAuthMode, d as throwResponseError, i as fetchPlugins, l as fetchManifest, n as enablePlugin, o as API_BASE, r as fetchPlugin, s as apiFetch, t as disablePlugin, u as parseApiResponse } from "./plugins-BZzztFdK.js";
|
|
2
|
-
import { a as SUPPORTED_LOCALES, i as DEFAULT_LOCALE, l as resolveLocale, n as useLocale, o as SUPPORTED_LOCALE_CODES, t as LocaleDirectionProvider } from "./LocaleDirectionProvider-
|
|
2
|
+
import { a as SUPPORTED_LOCALES, i as DEFAULT_LOCALE, l as resolveLocale, n as useLocale, o as SUPPORTED_LOCALE_CODES, t as LocaleDirectionProvider } from "./LocaleDirectionProvider-LB7Qjnv1.js";
|
|
3
3
|
import "./locales/index.js";
|
|
4
4
|
import { Autocomplete, Badge, Button, Checkbox, CommandPalette, Dialog, Input, InputArea, Label, LinkButton, Loader, Popover, Select, Sidebar as KumoSidebar, Switch, Tabs, Toast, Toasty, Tooltip, buttonVariants, useSidebar } from "@cloudflare/kumo";
|
|
5
5
|
import { i18n } from "@lingui/core";
|
|
6
6
|
import { I18nProvider, Trans, useLingui } from "@lingui/react";
|
|
7
|
-
import { QueryClient, QueryClientProvider, useInfiniteQuery, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
7
|
+
import { QueryClient, QueryClientProvider, keepPreviousData, useInfiniteQuery, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
8
8
|
import { Link, Link as Link$1, Outlet, RouterProvider, createRootRouteWithContext, createRoute, createRouter, useLocation, useNavigate, useNavigate as useNavigate$1, useParams, useParams as useParams$1, useSearch } from "@tanstack/react-router";
|
|
9
9
|
import * as React$1 from "react";
|
|
10
10
|
import React, { createContext, useCallback, useContext, useEffect, useState } from "react";
|
|
11
11
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
12
12
|
import { Dialog as Dialog$1 } from "@cloudflare/kumo/primitives";
|
|
13
|
-
import { ArrowCounterClockwise, ArrowRight, ArrowRightIcon, ArrowSquareOut, ArrowUUpLeft, ArrowUUpRight, ArrowsClockwise, ArrowsInSimple, ArrowsLeftRight, ArrowsOutSimple, BracketsCurly, Calendar, CalendarBlank, CaretDown, CaretRightIcon, CaretUp, CaretUpDown, ChatCircle, Check, CheckCircle, CircleDashed, CircleNotch, ClockCounterClockwise, Cloud, Code, CodeBlock, Columns, Copy, Cube, Database, DeviceMobile, DotsSixVertical, DownloadSimple, Envelope, EnvelopeSimple, Eye, EyeSlash, File, FileArrowDown, FileText, FloppyDisk, FolderOpen, Gear, GithubLogo, Globe, GlobeSimple, GridFour, HardDrive, Hash, Image as Image$1, ImageBroken, ImageSquare, Info, Key, Link as Link$2, LinkBreak, LinkSimple, List, ListBullets, ListChecks, ListNumbers, MagnifyingGlass, Minus, Monitor, Moon, Palette, PaperPlaneTilt, Paperclip, Paragraph, Pencil, PencilSimple, PlugsConnected, Plus, Prohibit, PuzzlePiece, Quotes, Rows, Ruler, ShareNetwork, Shield, ShieldCheck, ShieldWarning, SignOut, SlidersHorizontal, Sparkle, SquaresFour, Stack, Storefront, Sun, Table, TextAlignCenter, TextAlignLeft, TextAlignRight, TextB, TextHOne, TextHThree, TextHTwo, TextItalic, TextStrikethrough, TextT, TextUnderline, ToggleLeft, Trash, Upload, User, UserCircle, UserPlus, Users, Warning, WarningCircle, WebhooksLogo, X, YoutubeLogo } from "@phosphor-icons/react";
|
|
13
|
+
import { ArrowCounterClockwise, ArrowDown, ArrowRight, ArrowRightIcon, ArrowSquareOut, ArrowUUpLeft, ArrowUUpRight, ArrowUp, ArrowsClockwise, ArrowsInSimple, ArrowsLeftRight, ArrowsOutSimple, Bell, BookOpen, BracketsAngle, BracketsCurly, Calendar, CalendarBlank, CaretDown, CaretRightIcon, CaretUp, CaretUpDown, ChartBar, ChartLine, ChatCircle, Check, CheckCircle, CircleDashed, CircleNotch, ClockCounterClockwise, Cloud, Code, CodeBlock, Columns, Copy, Crop, Cube, Database, DeviceMobile, DotsSixVertical, DownloadSimple, Envelope, EnvelopeSimple, Eye, EyeSlash, File, FileArrowDown, FileText, FloppyDisk, Folder, FolderOpen, Gear, GithubLogo, Globe, GlobeSimple, GridFour, HardDrive, Hash, IdentificationCard, Image as Image$1, ImageBroken, ImageSquare, Info, Key, Link as Link$2, LinkBreak, LinkSimple, List, ListBullets, ListChecks, ListNumbers, MagnifyingGlass, Medal, Minus, Monitor, Moon, Palette, PaperPlaneTilt, Paperclip, Paragraph, Pencil, PencilSimple, Plug, PlugsConnected, Plus, Prohibit, PuzzlePiece, Quotes, Rows, Ruler, ShareNetwork, Shield, ShieldCheck, ShieldWarning, SignOut, SlidersHorizontal, Sparkle, SquaresFour, Stack, Star, Storefront, Sun, Table, Tag, TextAlignCenter, TextAlignLeft, TextAlignRight, TextB, TextHOne, TextHThree, TextHTwo, TextItalic, TextStrikethrough, TextT, TextUnderline, ToggleLeft, Trash, Trophy, Upload, User, UserCircle, UserPlus, Users, Warning, WarningCircle, WebhooksLogo, X, YoutubeLogo } from "@phosphor-icons/react";
|
|
14
14
|
import { clsx } from "clsx";
|
|
15
15
|
import { twMerge } from "tailwind-merge";
|
|
16
16
|
import { DndContext, DragOverlay, KeyboardSensor, PointerSensor, closestCenter, rectIntersection, useDraggable, useDroppable, useSensor, useSensors } from "@dnd-kit/core";
|
|
@@ -30,7 +30,7 @@ import Typography from "@tiptap/extension-typography";
|
|
|
30
30
|
import { EditorContent, Node as Node$2, NodeViewContent, NodeViewWrapper, ReactNodeViewRenderer, mergeAttributes as mergeAttributes$1, useEditor, useEditorState } from "@tiptap/react";
|
|
31
31
|
import { BubbleMenu } from "@tiptap/react/menus";
|
|
32
32
|
import StarterKit from "@tiptap/starter-kit";
|
|
33
|
-
import { Plugin, PluginKey } from "@tiptap/pm/state";
|
|
33
|
+
import { NodeSelection, Plugin, PluginKey } from "@tiptap/pm/state";
|
|
34
34
|
import { Decoration, DecorationSet } from "@tiptap/pm/view";
|
|
35
35
|
import { createPortal } from "react-dom";
|
|
36
36
|
import CodeBlock$1 from "@tiptap/extension-code-block";
|
|
@@ -1178,6 +1178,7 @@ async function fetchContentList(collection, options) {
|
|
|
1178
1178
|
if (options?.locale) params.set("locale", options.locale);
|
|
1179
1179
|
if (options?.orderBy) params.set("orderBy", options.orderBy);
|
|
1180
1180
|
if (options?.order) params.set("order", options.order);
|
|
1181
|
+
if (options?.search) params.set("q", options.search);
|
|
1181
1182
|
return parseApiResponse(await apiFetch(`${API_BASE}/content/${collection}${params.toString() ? `?${params}` : ""}`), "Failed to fetch content");
|
|
1182
1183
|
}
|
|
1183
1184
|
/**
|
|
@@ -1372,6 +1373,15 @@ async function restoreRevision(revisionId) {
|
|
|
1372
1373
|
* Media upload, list, delete, and provider APIs
|
|
1373
1374
|
*/
|
|
1374
1375
|
/**
|
|
1376
|
+
* Maximum length of the media filename search term. Mirrors the server-side
|
|
1377
|
+
* zod schema (`q: z.string().trim().min(1).max(200)`); keep in sync.
|
|
1378
|
+
*/
|
|
1379
|
+
const MEDIA_SEARCH_MAX_LENGTH = 200;
|
|
1380
|
+
/** Trim and clamp a search term to the server-accepted range. */
|
|
1381
|
+
function normalizeMediaSearch(value) {
|
|
1382
|
+
return (value ?? "").trim().slice(0, MEDIA_SEARCH_MAX_LENGTH);
|
|
1383
|
+
}
|
|
1384
|
+
/**
|
|
1375
1385
|
* Fetch media list
|
|
1376
1386
|
*/
|
|
1377
1387
|
async function fetchMediaList(options) {
|
|
@@ -1382,6 +1392,10 @@ async function fetchMediaList(options) {
|
|
|
1382
1392
|
const value = Array.isArray(options.mimeType) ? options.mimeType.join(",") : options.mimeType;
|
|
1383
1393
|
if (value) params.set("mimeType", value);
|
|
1384
1394
|
}
|
|
1395
|
+
if (options?.search) {
|
|
1396
|
+
const q = normalizeMediaSearch(options.search);
|
|
1397
|
+
if (q) params.set("q", q);
|
|
1398
|
+
}
|
|
1385
1399
|
return parseApiResponse(await apiFetch(`${API_BASE}/media${params.toString() ? `?${params}` : ""}`), "Failed to fetch media");
|
|
1386
1400
|
}
|
|
1387
1401
|
/**
|
|
@@ -2008,6 +2022,70 @@ async function createBylineTranslation(id, input) {
|
|
|
2008
2022
|
}), "Failed to create byline translation");
|
|
2009
2023
|
}
|
|
2010
2024
|
|
|
2025
|
+
//#endregion
|
|
2026
|
+
//#region src/lib/api/byline-fields.ts
|
|
2027
|
+
/**
|
|
2028
|
+
* Byline custom-field schema management API (Discussion #1174, Phase 4).
|
|
2029
|
+
*
|
|
2030
|
+
* Mirrors the server-side admin endpoints at
|
|
2031
|
+
* `/_emdash/api/admin/byline-fields/*`. Mutation responses use the
|
|
2032
|
+
* shared `ApiResult<T>` envelope; this client unwraps it via
|
|
2033
|
+
* `parseApiResponse` and surfaces typed errors through
|
|
2034
|
+
* `throwResponseError` (so the admin client sees the registry's
|
|
2035
|
+
* `FIELD_EXISTS` / `TRANSLATABLE_LOCKED` / `REORDER_MISMATCH` messages
|
|
2036
|
+
* verbatim).
|
|
2037
|
+
*/
|
|
2038
|
+
const BASE = `${API_BASE}/admin/byline-fields`;
|
|
2039
|
+
async function listBylineFields() {
|
|
2040
|
+
return parseApiResponse(await apiFetch(BASE), i18n._({
|
|
2041
|
+
id: "rLmrr+",
|
|
2042
|
+
message: "Failed to list byline fields"
|
|
2043
|
+
}));
|
|
2044
|
+
}
|
|
2045
|
+
async function getBylineFieldUsage(slug) {
|
|
2046
|
+
return parseApiResponse(await apiFetch(`${BASE}/${encodeURIComponent(slug)}/usage`), i18n._({
|
|
2047
|
+
id: "DwFS6c",
|
|
2048
|
+
message: "Failed to read byline field usage"
|
|
2049
|
+
}));
|
|
2050
|
+
}
|
|
2051
|
+
async function createBylineField(input) {
|
|
2052
|
+
return parseApiResponse(await apiFetch(BASE, {
|
|
2053
|
+
method: "POST",
|
|
2054
|
+
headers: { "Content-Type": "application/json" },
|
|
2055
|
+
body: JSON.stringify(input)
|
|
2056
|
+
}), i18n._({
|
|
2057
|
+
id: "cPkE9I",
|
|
2058
|
+
message: "Failed to create byline field"
|
|
2059
|
+
}));
|
|
2060
|
+
}
|
|
2061
|
+
async function updateBylineField(slug, input) {
|
|
2062
|
+
return parseApiResponse(await apiFetch(`${BASE}/${encodeURIComponent(slug)}`, {
|
|
2063
|
+
method: "PATCH",
|
|
2064
|
+
headers: { "Content-Type": "application/json" },
|
|
2065
|
+
body: JSON.stringify(input)
|
|
2066
|
+
}), i18n._({
|
|
2067
|
+
id: "1dl1C5",
|
|
2068
|
+
message: "Failed to update byline field"
|
|
2069
|
+
}));
|
|
2070
|
+
}
|
|
2071
|
+
async function deleteBylineField(slug) {
|
|
2072
|
+
const response = await apiFetch(`${BASE}/${encodeURIComponent(slug)}`, { method: "DELETE" });
|
|
2073
|
+
if (!response.ok) await throwResponseError(response, i18n._({
|
|
2074
|
+
id: "MxBmlg",
|
|
2075
|
+
message: "Failed to delete byline field"
|
|
2076
|
+
}));
|
|
2077
|
+
}
|
|
2078
|
+
async function reorderBylineFields(slugs) {
|
|
2079
|
+
return parseApiResponse(await apiFetch(`${BASE}/reorder`, {
|
|
2080
|
+
method: "POST",
|
|
2081
|
+
headers: { "Content-Type": "application/json" },
|
|
2082
|
+
body: JSON.stringify({ slugs })
|
|
2083
|
+
}), i18n._({
|
|
2084
|
+
id: "J/UlAe",
|
|
2085
|
+
message: "Failed to reorder byline fields"
|
|
2086
|
+
}));
|
|
2087
|
+
}
|
|
2088
|
+
|
|
2011
2089
|
//#endregion
|
|
2012
2090
|
//#region src/lib/api/menus.ts
|
|
2013
2091
|
/**
|
|
@@ -2961,6 +3039,35 @@ function fromDatetimeLocalInputValue(value) {
|
|
|
2961
3039
|
return value === "" ? "" : `${value}:00.000Z`;
|
|
2962
3040
|
}
|
|
2963
3041
|
|
|
3042
|
+
//#endregion
|
|
3043
|
+
//#region src/lib/hooks.ts
|
|
3044
|
+
/**
|
|
3045
|
+
* Returns a stable function reference that always calls the latest version
|
|
3046
|
+
* of the provided callback. Useful for event listeners in effects where
|
|
3047
|
+
* you don't want the listener to be torn down and re-added when the
|
|
3048
|
+
* callback identity changes.
|
|
3049
|
+
*/
|
|
3050
|
+
function useStableCallback(callback) {
|
|
3051
|
+
const ref = React$1.useRef(callback);
|
|
3052
|
+
React$1.useLayoutEffect(() => {
|
|
3053
|
+
ref.current = callback;
|
|
3054
|
+
});
|
|
3055
|
+
return React$1.useCallback((...args) => ref.current(...args), []);
|
|
3056
|
+
}
|
|
3057
|
+
/**
|
|
3058
|
+
* Returns a debounced version of a value that only updates after the
|
|
3059
|
+
* specified delay has elapsed since the last change. Useful for search
|
|
3060
|
+
* inputs that trigger API calls.
|
|
3061
|
+
*/
|
|
3062
|
+
function useDebouncedValue$1(value, delay) {
|
|
3063
|
+
const [debouncedValue, setDebouncedValue] = React$1.useState(value);
|
|
3064
|
+
React$1.useEffect(() => {
|
|
3065
|
+
const timer = setTimeout(setDebouncedValue, delay, value);
|
|
3066
|
+
return () => clearTimeout(timer);
|
|
3067
|
+
}, [value, delay]);
|
|
3068
|
+
return debouncedValue;
|
|
3069
|
+
}
|
|
3070
|
+
|
|
2964
3071
|
//#endregion
|
|
2965
3072
|
//#region src/lib/media-utils.ts
|
|
2966
3073
|
function providerItemToMediaItem(providerId, item) {
|
|
@@ -3178,6 +3285,7 @@ function MediaPickerModal({ open, onOpenChange, onSelect, mimeTypeFilter = "imag
|
|
|
3178
3285
|
const [selectedItem, setSelectedItem] = React$1.useState(null);
|
|
3179
3286
|
const [activeProvider, setActiveProvider] = React$1.useState("local");
|
|
3180
3287
|
const [searchQuery, setSearchQuery] = React$1.useState("");
|
|
3288
|
+
const debouncedSearch = useDebouncedValue$1(searchQuery, 300);
|
|
3181
3289
|
const fileInputRef = React$1.useRef(null);
|
|
3182
3290
|
const [imageUrl, setImageUrl] = React$1.useState("");
|
|
3183
3291
|
const [isProbing, setIsProbing] = React$1.useState(false);
|
|
@@ -3214,12 +3322,18 @@ function MediaPickerModal({ open, onOpenChange, onSelect, mimeTypeFilter = "imag
|
|
|
3214
3322
|
};
|
|
3215
3323
|
return providers?.find((p) => p.id === activeProvider);
|
|
3216
3324
|
}, [activeProvider, providers]);
|
|
3325
|
+
const mediaQueryKey = [
|
|
3326
|
+
"media",
|
|
3327
|
+
filters?.join(",") ?? "",
|
|
3328
|
+
debouncedSearch.trim()
|
|
3329
|
+
];
|
|
3217
3330
|
const { data: localData, isLoading: localLoading, fetchNextPage: fetchNextLocalPage, hasNextPage: hasNextLocalPage, isFetchingNextPage: isFetchingNextLocalPage } = useInfiniteQuery({
|
|
3218
|
-
queryKey:
|
|
3331
|
+
queryKey: mediaQueryKey,
|
|
3219
3332
|
queryFn: ({ pageParam }) => fetchMediaList({
|
|
3220
3333
|
mimeType: filters,
|
|
3221
3334
|
cursor: pageParam,
|
|
3222
|
-
limit: 100
|
|
3335
|
+
limit: 100,
|
|
3336
|
+
search: debouncedSearch.trim() || void 0
|
|
3223
3337
|
}),
|
|
3224
3338
|
initialPageParam: void 0,
|
|
3225
3339
|
getNextPageParam: (lastPage) => lastPage.nextCursor,
|
|
@@ -3277,7 +3391,7 @@ function MediaPickerModal({ open, onOpenChange, onSelect, mimeTypeFilter = "imag
|
|
|
3277
3391
|
height
|
|
3278
3392
|
}),
|
|
3279
3393
|
onSuccess: (_updated, { id, width, height }) => {
|
|
3280
|
-
queryClient.setQueryData(
|
|
3394
|
+
queryClient.setQueryData(mediaQueryKey, (old) => {
|
|
3281
3395
|
if (!old) return old;
|
|
3282
3396
|
return {
|
|
3283
3397
|
...old,
|
|
@@ -3555,11 +3669,14 @@ function MediaPickerModal({ open, onOpenChange, onSelect, mimeTypeFilter = "imag
|
|
|
3555
3669
|
}),
|
|
3556
3670
|
/* @__PURE__ */ jsxs("div", {
|
|
3557
3671
|
className: "flex items-center justify-between pb-3 gap-4",
|
|
3558
|
-
children: [canSearch ? /* @__PURE__ */ jsxs("div", {
|
|
3672
|
+
children: [canSearch || activeProvider === "local" ? /* @__PURE__ */ jsxs("div", {
|
|
3559
3673
|
className: "relative flex-1 max-w-xs",
|
|
3560
3674
|
children: [/* @__PURE__ */ jsx(MagnifyingGlass, { className: "absolute start-3 top-1/2 -translate-y-1/2 h-4 w-4 text-kumo-subtle" }), /* @__PURE__ */ jsx(Input, {
|
|
3561
3675
|
type: "search",
|
|
3562
|
-
placeholder: _t({
|
|
3676
|
+
placeholder: activeProvider === "local" ? _t({
|
|
3677
|
+
id: "Fwo2VZ",
|
|
3678
|
+
message: "Search by filename..."
|
|
3679
|
+
}) : _t({
|
|
3563
3680
|
id: "YIix5Y",
|
|
3564
3681
|
message: "Search..."
|
|
3565
3682
|
}),
|
|
@@ -3569,6 +3686,7 @@ function MediaPickerModal({ open, onOpenChange, onSelect, mimeTypeFilter = "imag
|
|
|
3569
3686
|
}),
|
|
3570
3687
|
value: searchQuery,
|
|
3571
3688
|
onChange: (e) => setSearchQuery(e.target.value),
|
|
3689
|
+
maxLength: MEDIA_SEARCH_MAX_LENGTH,
|
|
3572
3690
|
className: "ps-9"
|
|
3573
3691
|
})]
|
|
3574
3692
|
}) : /* @__PURE__ */ jsx("p", {
|
|
@@ -4596,35 +4714,6 @@ function renderIcon(icon) {
|
|
|
4596
4714
|
return /* @__PURE__ */ jsx(icon, {});
|
|
4597
4715
|
}
|
|
4598
4716
|
|
|
4599
|
-
//#endregion
|
|
4600
|
-
//#region src/lib/hooks.ts
|
|
4601
|
-
/**
|
|
4602
|
-
* Returns a stable function reference that always calls the latest version
|
|
4603
|
-
* of the provided callback. Useful for event listeners in effects where
|
|
4604
|
-
* you don't want the listener to be torn down and re-added when the
|
|
4605
|
-
* callback identity changes.
|
|
4606
|
-
*/
|
|
4607
|
-
function useStableCallback(callback) {
|
|
4608
|
-
const ref = React$1.useRef(callback);
|
|
4609
|
-
React$1.useLayoutEffect(() => {
|
|
4610
|
-
ref.current = callback;
|
|
4611
|
-
});
|
|
4612
|
-
return React$1.useCallback((...args) => ref.current(...args), []);
|
|
4613
|
-
}
|
|
4614
|
-
/**
|
|
4615
|
-
* Returns a debounced version of a value that only updates after the
|
|
4616
|
-
* specified delay has elapsed since the last change. Useful for search
|
|
4617
|
-
* inputs that trigger API calls.
|
|
4618
|
-
*/
|
|
4619
|
-
function useDebouncedValue$1(value, delay) {
|
|
4620
|
-
const [debouncedValue, setDebouncedValue] = React$1.useState(value);
|
|
4621
|
-
React$1.useEffect(() => {
|
|
4622
|
-
const timer = setTimeout(setDebouncedValue, delay, value);
|
|
4623
|
-
return () => clearTimeout(timer);
|
|
4624
|
-
}, [value, delay]);
|
|
4625
|
-
return debouncedValue;
|
|
4626
|
-
}
|
|
4627
|
-
|
|
4628
4717
|
//#endregion
|
|
4629
4718
|
//#region src/components/editor/ImageDetailPanel.tsx
|
|
4630
4719
|
/**
|
|
@@ -6086,19 +6175,25 @@ function BlockMenu({ editor, anchorElement, isOpen, onClose }) {
|
|
|
6086
6175
|
if (!isOpen) setShowTransforms(false);
|
|
6087
6176
|
}, [isOpen]);
|
|
6088
6177
|
const handleDuplicate = () => {
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
const
|
|
6178
|
+
if (!(editor.state.selection instanceof NodeSelection)) {
|
|
6179
|
+
onClose();
|
|
6180
|
+
return;
|
|
6181
|
+
}
|
|
6182
|
+
const { selection, doc } = editor.state;
|
|
6183
|
+
const { from, to } = selection;
|
|
6184
|
+
const slice = doc.slice(from, to);
|
|
6094
6185
|
editor.chain().focus().command(({ tr }) => {
|
|
6095
|
-
tr.insert(
|
|
6186
|
+
tr.insert(to, slice.content);
|
|
6096
6187
|
return true;
|
|
6097
6188
|
}).run();
|
|
6098
6189
|
onClose();
|
|
6099
6190
|
};
|
|
6100
6191
|
const handleDelete = () => {
|
|
6101
|
-
|
|
6192
|
+
if (!(editor.state.selection instanceof NodeSelection)) {
|
|
6193
|
+
onClose();
|
|
6194
|
+
return;
|
|
6195
|
+
}
|
|
6196
|
+
editor.chain().focus().deleteSelection().run();
|
|
6102
6197
|
onClose();
|
|
6103
6198
|
};
|
|
6104
6199
|
const handleTransform = (transform) => {
|
|
@@ -6239,6 +6334,173 @@ function DragHandleWrapper({ editor }) {
|
|
|
6239
6334
|
})] });
|
|
6240
6335
|
}
|
|
6241
6336
|
|
|
6337
|
+
//#endregion
|
|
6338
|
+
//#region src/components/editor/HtmlBlockNode.tsx
|
|
6339
|
+
/**
|
|
6340
|
+
* HTML block node for the admin editor.
|
|
6341
|
+
*
|
|
6342
|
+
* Renders a first-class `htmlBlock` in the Portable Text editor with:
|
|
6343
|
+
* - A textarea for editing raw HTML source
|
|
6344
|
+
* - Selection ring, drag handle, and delete action
|
|
6345
|
+
*
|
|
6346
|
+
* Modeled on `PluginBlockNode.tsx` (atom node with React node view) and
|
|
6347
|
+
* the existing `{ _type: "htmlBlock", _key, html }` Portable Text shape
|
|
6348
|
+
* used by the WordPress and Contentful importers.
|
|
6349
|
+
*/
|
|
6350
|
+
/**
|
|
6351
|
+
* True when focus is inside a text input or textarea (e.g. the HTML block's
|
|
6352
|
+
* source <textarea>). Editor-level keyboard shortcuts must defer to native
|
|
6353
|
+
* field editing in that case.
|
|
6354
|
+
*/
|
|
6355
|
+
function isEditingFormField() {
|
|
6356
|
+
if (typeof document === "undefined") return false;
|
|
6357
|
+
const active = document.activeElement;
|
|
6358
|
+
if (!active) return false;
|
|
6359
|
+
const tag = active.tagName;
|
|
6360
|
+
return tag === "TEXTAREA" || tag === "INPUT";
|
|
6361
|
+
}
|
|
6362
|
+
function HtmlBlockNodeView({ node, updateAttributes, selected, deleteNode }) {
|
|
6363
|
+
const { _: _t } = useLingui();
|
|
6364
|
+
const html = typeof node.attrs.html === "string" ? node.attrs.html : "";
|
|
6365
|
+
const [draft, setDraft] = React$1.useState(html);
|
|
6366
|
+
const textareaRef = React$1.useRef(null);
|
|
6367
|
+
React$1.useEffect(() => {
|
|
6368
|
+
setDraft(html);
|
|
6369
|
+
}, [html]);
|
|
6370
|
+
React$1.useEffect(() => {
|
|
6371
|
+
const el = textareaRef.current;
|
|
6372
|
+
if (el) {
|
|
6373
|
+
el.style.height = "auto";
|
|
6374
|
+
el.style.height = `${el.scrollHeight}px`;
|
|
6375
|
+
}
|
|
6376
|
+
}, [draft]);
|
|
6377
|
+
const commitHtml = React$1.useCallback((value) => {
|
|
6378
|
+
updateAttributes({ html: value });
|
|
6379
|
+
}, [updateAttributes]);
|
|
6380
|
+
const handleChange = React$1.useCallback((e) => {
|
|
6381
|
+
setDraft(e.target.value);
|
|
6382
|
+
commitHtml(e.target.value);
|
|
6383
|
+
}, [commitHtml]);
|
|
6384
|
+
return /* @__PURE__ */ jsx(NodeViewWrapper, {
|
|
6385
|
+
className: cn("html-block relative my-3", selected && "ring-2 ring-kumo-brand ring-offset-2 rounded-lg"),
|
|
6386
|
+
contentEditable: false,
|
|
6387
|
+
"data-drag-handle": true,
|
|
6388
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
6389
|
+
className: "relative group",
|
|
6390
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
6391
|
+
className: cn("absolute -start-8 top-1/2 -translate-y-1/2 opacity-0 group-hover:opacity-100 transition-opacity cursor-grab active:cursor-grabbing", selected && "opacity-100"),
|
|
6392
|
+
"data-drag-handle": true,
|
|
6393
|
+
children: /* @__PURE__ */ jsx(DotsSixVertical, { className: "h-5 w-5 text-kumo-subtle/50" })
|
|
6394
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
6395
|
+
className: cn("rounded-lg border bg-kumo-base transition-colors overflow-hidden", selected ? "border-kumo-brand/50 bg-kumo-tint/30" : "hover:border-kumo-line"),
|
|
6396
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
6397
|
+
className: "flex items-center gap-3 px-4 py-3",
|
|
6398
|
+
children: [
|
|
6399
|
+
/* @__PURE__ */ jsx("div", {
|
|
6400
|
+
className: "flex-shrink-0 w-10 h-10 rounded-lg bg-kumo-tint flex items-center justify-center text-kumo-subtle",
|
|
6401
|
+
children: /* @__PURE__ */ jsx(BracketsAngle, { className: "h-5 w-5" })
|
|
6402
|
+
}),
|
|
6403
|
+
/* @__PURE__ */ jsx("div", {
|
|
6404
|
+
className: "flex-1 min-w-0",
|
|
6405
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
6406
|
+
className: "text-sm font-medium",
|
|
6407
|
+
children: _t({
|
|
6408
|
+
id: "FBknns",
|
|
6409
|
+
message: "HTML"
|
|
6410
|
+
})
|
|
6411
|
+
})
|
|
6412
|
+
}),
|
|
6413
|
+
/* @__PURE__ */ jsx("div", {
|
|
6414
|
+
className: cn("flex items-center gap-1 transition-opacity", selected ? "opacity-100" : "opacity-0 group-hover:opacity-100"),
|
|
6415
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
6416
|
+
type: "button",
|
|
6417
|
+
variant: "ghost",
|
|
6418
|
+
shape: "square",
|
|
6419
|
+
className: "h-8 w-8 text-kumo-danger hover:text-kumo-danger hover:bg-kumo-danger/10",
|
|
6420
|
+
onClick: () => deleteNode(),
|
|
6421
|
+
title: _t({
|
|
6422
|
+
id: "cnGeoo",
|
|
6423
|
+
message: "Delete"
|
|
6424
|
+
}),
|
|
6425
|
+
"aria-label": _t({
|
|
6426
|
+
id: "p8SVsk",
|
|
6427
|
+
message: "Delete HTML block"
|
|
6428
|
+
}),
|
|
6429
|
+
children: /* @__PURE__ */ jsx(Trash, { className: "h-4 w-4" })
|
|
6430
|
+
})
|
|
6431
|
+
})
|
|
6432
|
+
]
|
|
6433
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
6434
|
+
className: "px-4 pb-4",
|
|
6435
|
+
children: /* @__PURE__ */ jsx("textarea", {
|
|
6436
|
+
ref: textareaRef,
|
|
6437
|
+
value: draft,
|
|
6438
|
+
onChange: handleChange,
|
|
6439
|
+
placeholder: _t({
|
|
6440
|
+
id: "cquA1F",
|
|
6441
|
+
message: "Enter HTML..."
|
|
6442
|
+
}),
|
|
6443
|
+
className: "w-full min-h-[100px] resize-y rounded-md border bg-kumo-overlay p-3 font-mono text-sm text-kumo-strong placeholder:text-kumo-subtle focus:outline-none focus:ring-2 focus:ring-kumo-brand",
|
|
6444
|
+
spellCheck: false,
|
|
6445
|
+
"aria-label": _t({
|
|
6446
|
+
id: "bbJ01O",
|
|
6447
|
+
message: "HTML source"
|
|
6448
|
+
})
|
|
6449
|
+
})
|
|
6450
|
+
})]
|
|
6451
|
+
})]
|
|
6452
|
+
})
|
|
6453
|
+
});
|
|
6454
|
+
}
|
|
6455
|
+
/**
|
|
6456
|
+
* TipTap extension: first-class HTML block.
|
|
6457
|
+
*
|
|
6458
|
+
* An atom node that stores raw HTML in a `html` attribute. Round-trips
|
|
6459
|
+
* through Portable Text as `{ _type: "htmlBlock", _key, html }`.
|
|
6460
|
+
*/
|
|
6461
|
+
const HtmlBlockExtension = Node$1.create({
|
|
6462
|
+
name: "htmlBlock",
|
|
6463
|
+
group: "block",
|
|
6464
|
+
atom: true,
|
|
6465
|
+
draggable: true,
|
|
6466
|
+
selectable: true,
|
|
6467
|
+
addAttributes() {
|
|
6468
|
+
return { html: {
|
|
6469
|
+
default: "",
|
|
6470
|
+
parseHTML: (element) => element.getAttribute("data-html-content") ?? "",
|
|
6471
|
+
renderHTML: (attributes) => {
|
|
6472
|
+
const html = typeof attributes.html === "string" ? attributes.html : "";
|
|
6473
|
+
if (!html) return {};
|
|
6474
|
+
return { "data-html-content": html };
|
|
6475
|
+
}
|
|
6476
|
+
} };
|
|
6477
|
+
},
|
|
6478
|
+
parseHTML() {
|
|
6479
|
+
return [{ tag: "div[data-html-block]" }];
|
|
6480
|
+
},
|
|
6481
|
+
renderHTML({ HTMLAttributes }) {
|
|
6482
|
+
return ["div", mergeAttributes(HTMLAttributes, { "data-html-block": "" })];
|
|
6483
|
+
},
|
|
6484
|
+
addNodeView() {
|
|
6485
|
+
return ReactNodeViewRenderer(HtmlBlockNodeView);
|
|
6486
|
+
},
|
|
6487
|
+
addKeyboardShortcuts() {
|
|
6488
|
+
const deleteHtmlBlock = () => {
|
|
6489
|
+
if (isEditingFormField()) return false;
|
|
6490
|
+
const { selection } = this.editor.state;
|
|
6491
|
+
if (this.editor.state.doc.nodeAt(selection.from)?.type.name === "htmlBlock") {
|
|
6492
|
+
this.editor.commands.deleteSelection();
|
|
6493
|
+
return true;
|
|
6494
|
+
}
|
|
6495
|
+
return false;
|
|
6496
|
+
};
|
|
6497
|
+
return {
|
|
6498
|
+
Backspace: deleteHtmlBlock,
|
|
6499
|
+
Delete: deleteHtmlBlock
|
|
6500
|
+
};
|
|
6501
|
+
}
|
|
6502
|
+
});
|
|
6503
|
+
|
|
6242
6504
|
//#endregion
|
|
6243
6505
|
//#region src/components/editor/ImageNode.tsx
|
|
6244
6506
|
/**
|
|
@@ -7191,6 +7453,14 @@ function convertPMNode(node) {
|
|
|
7191
7453
|
language: typeof rawLanguage === "string" ? rawLanguage : void 0
|
|
7192
7454
|
};
|
|
7193
7455
|
}
|
|
7456
|
+
case "htmlBlock": {
|
|
7457
|
+
const rawHtml = node.attrs?.html;
|
|
7458
|
+
return {
|
|
7459
|
+
_type: "htmlBlock",
|
|
7460
|
+
_key: generateKey(),
|
|
7461
|
+
html: typeof rawHtml === "string" ? rawHtml : ""
|
|
7462
|
+
};
|
|
7463
|
+
}
|
|
7194
7464
|
case "image": {
|
|
7195
7465
|
const attrs = node.attrs ?? {};
|
|
7196
7466
|
const provider = attrStr(attrs.provider);
|
|
@@ -7449,6 +7719,10 @@ function convertPTBlock(block) {
|
|
|
7449
7719
|
};
|
|
7450
7720
|
}
|
|
7451
7721
|
case "break": return { type: "horizontalRule" };
|
|
7722
|
+
case "htmlBlock": return {
|
|
7723
|
+
type: "htmlBlock",
|
|
7724
|
+
attrs: { html: block.html || "" }
|
|
7725
|
+
};
|
|
7452
7726
|
case "table": {
|
|
7453
7727
|
const tableBlock = block;
|
|
7454
7728
|
const tableMarkDefs = tableBlock.markDefs || [];
|
|
@@ -7688,6 +7962,29 @@ const defaultSlashCommands = [
|
|
|
7688
7962
|
editor.chain().focus().deleteRange(range).toggleCodeBlock().run();
|
|
7689
7963
|
}
|
|
7690
7964
|
},
|
|
7965
|
+
{
|
|
7966
|
+
id: "htmlBlock",
|
|
7967
|
+
title: {
|
|
7968
|
+
id: "FBknns",
|
|
7969
|
+
message: "HTML"
|
|
7970
|
+
},
|
|
7971
|
+
description: {
|
|
7972
|
+
id: "iHrC09",
|
|
7973
|
+
message: "Insert raw HTML"
|
|
7974
|
+
},
|
|
7975
|
+
icon: BracketsAngle,
|
|
7976
|
+
aliases: [
|
|
7977
|
+
"html",
|
|
7978
|
+
"raw",
|
|
7979
|
+
"markup"
|
|
7980
|
+
],
|
|
7981
|
+
command: ({ editor, range }) => {
|
|
7982
|
+
editor.chain().focus().deleteRange(range).insertContent({
|
|
7983
|
+
type: "htmlBlock",
|
|
7984
|
+
attrs: { html: "" }
|
|
7985
|
+
}).run();
|
|
7986
|
+
}
|
|
7987
|
+
},
|
|
7691
7988
|
{
|
|
7692
7989
|
id: "divider",
|
|
7693
7990
|
title: {
|
|
@@ -8569,6 +8866,7 @@ function PortableTextEditor({ value, onChange, placeholder = "Start writing...",
|
|
|
8569
8866
|
underline: {}
|
|
8570
8867
|
}),
|
|
8571
8868
|
CodeBlockExtension,
|
|
8869
|
+
HtmlBlockExtension,
|
|
8572
8870
|
ImageExtension,
|
|
8573
8871
|
MarkdownLinkExtension,
|
|
8574
8872
|
PluginBlockExtension,
|
|
@@ -8723,7 +9021,7 @@ function PortableTextEditor({ value, onChange, placeholder = "Start writing...",
|
|
|
8723
9021
|
})
|
|
8724
9022
|
});
|
|
8725
9023
|
return /* @__PURE__ */ jsxs("div", {
|
|
8726
|
-
className: cn("border rounded-lg overflow-
|
|
9024
|
+
className: cn("border rounded-lg overflow-clip", minimal && "border-0 rounded-none -mx-4", focusMode === "spotlight" && "spotlight-mode", className),
|
|
8727
9025
|
"aria-labelledby": ariaLabelledby,
|
|
8728
9026
|
children: [
|
|
8729
9027
|
!minimal && /* @__PURE__ */ jsx(EditorToolbar, {
|
|
@@ -9136,7 +9434,7 @@ function EditorToolbar({ editor, focusMode, onFocusModeChange }) {
|
|
|
9136
9434
|
id: "maNyuq",
|
|
9137
9435
|
message: "Text formatting"
|
|
9138
9436
|
}),
|
|
9139
|
-
className: "border-b bg-kumo-tint
|
|
9437
|
+
className: "sticky -top-6 z-10 border-b bg-kumo-tint p-1 flex flex-wrap gap-0.5",
|
|
9140
9438
|
onKeyDown: handleKeyDown,
|
|
9141
9439
|
children: [
|
|
9142
9440
|
/* @__PURE__ */ jsxs(ToolbarGroup, { children: [
|
|
@@ -10326,10 +10624,11 @@ async function fetchTaxonomyDefs$1() {
|
|
|
10326
10624
|
return (await parseApiResponse(await apiFetch(`/_emdash/api/taxonomies`), "Failed to fetch taxonomies")).taxonomies;
|
|
10327
10625
|
}
|
|
10328
10626
|
/**
|
|
10329
|
-
* Fetch terms for a taxonomy
|
|
10627
|
+
* Fetch terms for a taxonomy, scoped to the entry's locale so only the matching
|
|
10628
|
+
* translation variants are offered.
|
|
10330
10629
|
*/
|
|
10331
|
-
async function fetchTerms$1(taxonomyName) {
|
|
10332
|
-
return (await parseApiResponse(await apiFetch(`/_emdash/api/taxonomies/${taxonomyName}/terms
|
|
10630
|
+
async function fetchTerms$1(taxonomyName, locale) {
|
|
10631
|
+
return (await parseApiResponse(await apiFetch(withLocale(`/_emdash/api/taxonomies/${taxonomyName}/terms`, locale)), i18n._({
|
|
10333
10632
|
id: "uvkjA+",
|
|
10334
10633
|
message: "Failed to fetch terms"
|
|
10335
10634
|
}))).terms;
|
|
@@ -10337,8 +10636,8 @@ async function fetchTerms$1(taxonomyName) {
|
|
|
10337
10636
|
/**
|
|
10338
10637
|
* Fetch entry terms
|
|
10339
10638
|
*/
|
|
10340
|
-
async function fetchEntryTerms(collection, entryId, taxonomy) {
|
|
10341
|
-
return (await parseApiResponse(await apiFetch(`/_emdash/api/content/${collection}/${entryId}/terms/${taxonomy}
|
|
10639
|
+
async function fetchEntryTerms(collection, entryId, taxonomy, locale) {
|
|
10640
|
+
return (await parseApiResponse(await apiFetch(withLocale(`/_emdash/api/content/${collection}/${entryId}/terms/${taxonomy}`, locale)), i18n._({
|
|
10342
10641
|
id: "pbqrlw",
|
|
10343
10642
|
message: "Failed to fetch entry terms"
|
|
10344
10643
|
}))).terms;
|
|
@@ -10346,8 +10645,8 @@ async function fetchEntryTerms(collection, entryId, taxonomy) {
|
|
|
10346
10645
|
/**
|
|
10347
10646
|
* Set entry terms
|
|
10348
10647
|
*/
|
|
10349
|
-
async function setEntryTerms(collection, entryId, taxonomy, termIds) {
|
|
10350
|
-
const res = await apiFetch(`/_emdash/api/content/${collection}/${entryId}/terms/${taxonomy}`, {
|
|
10648
|
+
async function setEntryTerms(collection, entryId, taxonomy, termIds, locale) {
|
|
10649
|
+
const res = await apiFetch(withLocale(`/_emdash/api/content/${collection}/${entryId}/terms/${taxonomy}`, locale), {
|
|
10351
10650
|
method: "POST",
|
|
10352
10651
|
headers: { "Content-Type": "application/json" },
|
|
10353
10652
|
body: JSON.stringify({ termIds })
|
|
@@ -10495,40 +10794,46 @@ function TagInput({ terms, selectedIds, onAdd, onRemove, onCreate, isCreating, l
|
|
|
10495
10794
|
/**
|
|
10496
10795
|
* Single taxonomy section
|
|
10497
10796
|
*/
|
|
10498
|
-
function TaxonomySection({ taxonomy, collection, entryId, onChange }) {
|
|
10797
|
+
function TaxonomySection({ taxonomy, collection, entryId, entryLocale, onChange }) {
|
|
10499
10798
|
const { _: _t2 } = useLingui();
|
|
10500
10799
|
const queryClient = useQueryClient();
|
|
10501
10800
|
const toastManager = Toast.useToastManager();
|
|
10502
10801
|
const [newCategoryLabel, setNewCategoryLabel] = React$1.useState("");
|
|
10503
10802
|
const [showCategoryInput, setShowCategoryInput] = React$1.useState(false);
|
|
10504
10803
|
const { data: terms = EMPTY_TERMS } = useQuery({
|
|
10505
|
-
queryKey: [
|
|
10506
|
-
|
|
10804
|
+
queryKey: [
|
|
10805
|
+
"taxonomy-terms",
|
|
10806
|
+
taxonomy.name,
|
|
10807
|
+
entryLocale
|
|
10808
|
+
],
|
|
10809
|
+
queryFn: () => fetchTerms$1(taxonomy.name, entryLocale)
|
|
10507
10810
|
});
|
|
10508
10811
|
const { data: entryTerms = EMPTY_TERMS } = useQuery({
|
|
10509
10812
|
queryKey: [
|
|
10510
10813
|
"entry-terms",
|
|
10511
10814
|
collection,
|
|
10512
10815
|
entryId,
|
|
10513
|
-
taxonomy.name
|
|
10816
|
+
taxonomy.name,
|
|
10817
|
+
entryLocale
|
|
10514
10818
|
],
|
|
10515
10819
|
queryFn: () => {
|
|
10516
10820
|
if (!entryId) return [];
|
|
10517
|
-
return fetchEntryTerms(collection, entryId, taxonomy.name);
|
|
10821
|
+
return fetchEntryTerms(collection, entryId, taxonomy.name, entryLocale);
|
|
10518
10822
|
},
|
|
10519
10823
|
enabled: !!entryId
|
|
10520
10824
|
});
|
|
10521
10825
|
const saveMutation = useMutation({
|
|
10522
10826
|
mutationFn: (termIds) => {
|
|
10523
10827
|
if (!entryId) throw new Error("No entry ID");
|
|
10524
|
-
return setEntryTerms(collection, entryId, taxonomy.name, termIds);
|
|
10828
|
+
return setEntryTerms(collection, entryId, taxonomy.name, termIds, entryLocale);
|
|
10525
10829
|
},
|
|
10526
10830
|
onSuccess: () => {
|
|
10527
10831
|
queryClient.invalidateQueries({ queryKey: [
|
|
10528
10832
|
"entry-terms",
|
|
10529
10833
|
collection,
|
|
10530
10834
|
entryId,
|
|
10531
|
-
taxonomy.name
|
|
10835
|
+
taxonomy.name,
|
|
10836
|
+
entryLocale
|
|
10532
10837
|
] });
|
|
10533
10838
|
toastManager.add({ title: _t2({
|
|
10534
10839
|
id: "KDffey",
|
|
@@ -10554,10 +10859,15 @@ function TaxonomySection({ taxonomy, collection, entryId, onChange }) {
|
|
|
10554
10859
|
const createTermMutation = useMutation({
|
|
10555
10860
|
mutationFn: (label) => createTerm(taxonomy.name, {
|
|
10556
10861
|
slug: slugify(label),
|
|
10557
|
-
label
|
|
10862
|
+
label,
|
|
10863
|
+
...entryLocale ? { locale: entryLocale } : {}
|
|
10558
10864
|
}),
|
|
10559
10865
|
onSuccess: (newTerm) => {
|
|
10560
|
-
queryClient.invalidateQueries({ queryKey: [
|
|
10866
|
+
queryClient.invalidateQueries({ queryKey: [
|
|
10867
|
+
"taxonomy-terms",
|
|
10868
|
+
taxonomy.name,
|
|
10869
|
+
entryLocale
|
|
10870
|
+
] });
|
|
10561
10871
|
const newSelected = new Set(selectedIds);
|
|
10562
10872
|
newSelected.add(newTerm.id);
|
|
10563
10873
|
setSelectedIds(newSelected);
|
|
@@ -10677,7 +10987,7 @@ function TaxonomySection({ taxonomy, collection, entryId, onChange }) {
|
|
|
10677
10987
|
/**
|
|
10678
10988
|
* Main TaxonomySidebar component
|
|
10679
10989
|
*/
|
|
10680
|
-
function TaxonomySidebar({ collection, entryId, onChange }) {
|
|
10990
|
+
function TaxonomySidebar({ collection, entryId, entryLocale, onChange }) {
|
|
10681
10991
|
const { _: _t3 } = useLingui();
|
|
10682
10992
|
const { data: taxonomies = [] } = useQuery({
|
|
10683
10993
|
queryKey: ["taxonomy-defs"],
|
|
@@ -10699,6 +11009,7 @@ function TaxonomySidebar({ collection, entryId, onChange }) {
|
|
|
10699
11009
|
taxonomy,
|
|
10700
11010
|
collection,
|
|
10701
11011
|
entryId,
|
|
11012
|
+
entryLocale,
|
|
10702
11013
|
onChange: (termIds) => onChange?.(taxonomy.name, termIds)
|
|
10703
11014
|
}, taxonomy.name))
|
|
10704
11015
|
})] })
|
|
@@ -11499,6 +11810,7 @@ function ContentEditor({ collection, collectionLabel, item, fields, isNew, entry
|
|
|
11499
11810
|
}), /* @__PURE__ */ jsx(BylineCreditsEditor, {
|
|
11500
11811
|
credits: activeBylines,
|
|
11501
11812
|
bylines: availableBylines ?? [],
|
|
11813
|
+
selectedBylineDetails: item?.bylines?.map((entry) => entry.byline),
|
|
11502
11814
|
bylinesLoaded: availableBylinesLoaded,
|
|
11503
11815
|
onChange: handleBylinesChange,
|
|
11504
11816
|
onQuickCreate: onQuickCreateByline,
|
|
@@ -11528,7 +11840,8 @@ function ContentEditor({ collection, collectionLabel, item, fields, isNew, entry
|
|
|
11528
11840
|
className: "p-4 border-t",
|
|
11529
11841
|
children: /* @__PURE__ */ jsx(TaxonomySidebar, {
|
|
11530
11842
|
collection,
|
|
11531
|
-
entryId: item.id
|
|
11843
|
+
entryId: item.id,
|
|
11844
|
+
entryLocale: item.locale ?? entryLocale
|
|
11532
11845
|
})
|
|
11533
11846
|
}),
|
|
11534
11847
|
hasSeo && !isNew && onSeoChange && /* @__PURE__ */ jsxs("div", {
|
|
@@ -12161,9 +12474,10 @@ function FileFieldRenderer({ id, label, value, onChange, required, allowedMimeTy
|
|
|
12161
12474
|
]
|
|
12162
12475
|
});
|
|
12163
12476
|
}
|
|
12164
|
-
function BylineCreditsEditor({ credits, bylines, onChange, onQuickCreate, onQuickEdit, entryLocale, i18n, bylinesLoaded = true }) {
|
|
12477
|
+
function BylineCreditsEditor({ credits, bylines, selectedBylineDetails, onChange, onQuickCreate, onQuickEdit, entryLocale, i18n, bylinesLoaded = true }) {
|
|
12165
12478
|
const { _: _t7 } = useLingui();
|
|
12166
|
-
const [
|
|
12479
|
+
const [search, setSearch] = React$1.useState("");
|
|
12480
|
+
const debouncedSearch = useDebouncedValue$1(search, 300);
|
|
12167
12481
|
const [quickName, setQuickName] = React$1.useState("");
|
|
12168
12482
|
const [quickSlug, setQuickSlug] = React$1.useState("");
|
|
12169
12483
|
const [quickError, setQuickError] = React$1.useState(null);
|
|
@@ -12173,8 +12487,44 @@ function BylineCreditsEditor({ credits, bylines, onChange, onQuickCreate, onQuic
|
|
|
12173
12487
|
const [editSlug, setEditSlug] = React$1.useState("");
|
|
12174
12488
|
const [editError, setEditError] = React$1.useState(null);
|
|
12175
12489
|
const [isEditing, setIsEditing] = React$1.useState(false);
|
|
12176
|
-
const
|
|
12177
|
-
const
|
|
12490
|
+
const trimmedSearch = debouncedSearch.trim();
|
|
12491
|
+
const searchEnabled = trimmedSearch.length > 0;
|
|
12492
|
+
const searchResults = useQuery({
|
|
12493
|
+
queryKey: [
|
|
12494
|
+
"bylines",
|
|
12495
|
+
"credit-picker",
|
|
12496
|
+
entryLocale ?? null,
|
|
12497
|
+
trimmedSearch
|
|
12498
|
+
],
|
|
12499
|
+
queryFn: () => fetchBylines({
|
|
12500
|
+
search: trimmedSearch,
|
|
12501
|
+
locale: entryLocale ?? void 0,
|
|
12502
|
+
limit: 20
|
|
12503
|
+
}),
|
|
12504
|
+
enabled: searchEnabled,
|
|
12505
|
+
placeholderData: keepPreviousData
|
|
12506
|
+
});
|
|
12507
|
+
const resultPool = searchEnabled ? searchResults.data?.items ?? [] : bylines;
|
|
12508
|
+
const hasMoreResults = searchEnabled ? !!searchResults.data?.nextCursor : bylines.length >= 100;
|
|
12509
|
+
const bylineMap = React$1.useMemo(() => {
|
|
12510
|
+
const map = /* @__PURE__ */ new Map();
|
|
12511
|
+
for (const b of selectedBylineDetails ?? []) map.set(b.id, b);
|
|
12512
|
+
for (const b of bylines) map.set(b.id, b);
|
|
12513
|
+
for (const b of searchResults.data?.items ?? []) map.set(b.id, b);
|
|
12514
|
+
return map;
|
|
12515
|
+
}, [
|
|
12516
|
+
selectedBylineDetails,
|
|
12517
|
+
bylines,
|
|
12518
|
+
searchResults.data?.items
|
|
12519
|
+
]);
|
|
12520
|
+
const availableToAdd = resultPool.filter((b) => !credits.some((c) => c.bylineId === b.id));
|
|
12521
|
+
const addByline = (bylineId) => {
|
|
12522
|
+
if (credits.some((c) => c.bylineId === bylineId)) return;
|
|
12523
|
+
onChange([...credits, {
|
|
12524
|
+
bylineId,
|
|
12525
|
+
roleLabel: null
|
|
12526
|
+
}]);
|
|
12527
|
+
};
|
|
12178
12528
|
const move = (index, direction) => {
|
|
12179
12529
|
const target = index + direction;
|
|
12180
12530
|
if (target < 0 || target >= credits.length) return;
|
|
@@ -12226,39 +12576,70 @@ function BylineCreditsEditor({ credits, bylines, onChange, onQuickCreate, onQuic
|
|
|
12226
12576
|
})]
|
|
12227
12577
|
}),
|
|
12228
12578
|
/* @__PURE__ */ jsxs("div", {
|
|
12229
|
-
className: "
|
|
12230
|
-
children: [
|
|
12231
|
-
|
|
12232
|
-
|
|
12233
|
-
|
|
12234
|
-
|
|
12235
|
-
id: "
|
|
12236
|
-
message: "
|
|
12579
|
+
className: "space-y-2",
|
|
12580
|
+
children: [
|
|
12581
|
+
/* @__PURE__ */ jsx(Input, {
|
|
12582
|
+
value: search,
|
|
12583
|
+
onChange: (e) => setSearch(e.target.value),
|
|
12584
|
+
placeholder: _t7({
|
|
12585
|
+
id: "96Um6i",
|
|
12586
|
+
message: "Search bylines to add..."
|
|
12237
12587
|
}),
|
|
12238
|
-
|
|
12239
|
-
|
|
12240
|
-
|
|
12241
|
-
|
|
12242
|
-
message: "Select byline"
|
|
12588
|
+
"aria-label": _t7({
|
|
12589
|
+
id: "4L2LjM",
|
|
12590
|
+
message: "Search bylines"
|
|
12591
|
+
})
|
|
12243
12592
|
}),
|
|
12244
|
-
|
|
12245
|
-
|
|
12246
|
-
|
|
12247
|
-
|
|
12248
|
-
|
|
12249
|
-
|
|
12250
|
-
|
|
12251
|
-
|
|
12252
|
-
|
|
12253
|
-
|
|
12254
|
-
|
|
12255
|
-
|
|
12256
|
-
|
|
12257
|
-
|
|
12258
|
-
|
|
12259
|
-
|
|
12593
|
+
searchEnabled && searchResults.isLoading ? /* @__PURE__ */ jsx("p", {
|
|
12594
|
+
className: "text-sm text-kumo-subtle",
|
|
12595
|
+
children: _t7({
|
|
12596
|
+
id: "Du+zn+",
|
|
12597
|
+
message: "Searching..."
|
|
12598
|
+
})
|
|
12599
|
+
}) : availableToAdd.length > 0 ? /* @__PURE__ */ jsx("ul", {
|
|
12600
|
+
className: "max-h-48 divide-y overflow-y-auto rounded border",
|
|
12601
|
+
children: availableToAdd.map((b) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("button", {
|
|
12602
|
+
type: "button",
|
|
12603
|
+
className: "flex w-full items-center justify-between gap-2 p-2 text-start hover:bg-kumo-tint",
|
|
12604
|
+
onClick: () => addByline(b.id),
|
|
12605
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
12606
|
+
className: "min-w-0",
|
|
12607
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
12608
|
+
className: "block truncate text-sm font-medium",
|
|
12609
|
+
children: b.displayName
|
|
12610
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
12611
|
+
className: "block truncate text-xs text-kumo-subtle",
|
|
12612
|
+
children: b.slug
|
|
12613
|
+
})]
|
|
12614
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
12615
|
+
className: "text-xs text-kumo-subtle",
|
|
12616
|
+
children: _t7({
|
|
12617
|
+
id: "m16xKo",
|
|
12618
|
+
message: "Add"
|
|
12619
|
+
})
|
|
12620
|
+
})]
|
|
12621
|
+
}) }, b.id))
|
|
12622
|
+
}) : searchEnabled && searchResults.isError ? /* @__PURE__ */ jsx("p", {
|
|
12623
|
+
className: "text-sm text-kumo-danger",
|
|
12624
|
+
children: _t7({
|
|
12625
|
+
id: "617r+T",
|
|
12626
|
+
message: "Couldn't search bylines. Please try again."
|
|
12627
|
+
})
|
|
12628
|
+
}) : searchEnabled ? /* @__PURE__ */ jsx("p", {
|
|
12629
|
+
className: "text-sm text-kumo-subtle",
|
|
12630
|
+
children: _t7({
|
|
12631
|
+
id: "ncgkI9",
|
|
12632
|
+
message: "No matching bylines."
|
|
12633
|
+
})
|
|
12634
|
+
}) : null,
|
|
12635
|
+
hasMoreResults && /* @__PURE__ */ jsx("p", {
|
|
12636
|
+
className: "text-xs text-kumo-subtle",
|
|
12637
|
+
children: _t7({
|
|
12638
|
+
id: "7Jpj2/",
|
|
12639
|
+
message: "Keep typing to narrow down more bylines."
|
|
12640
|
+
})
|
|
12260
12641
|
})
|
|
12261
|
-
|
|
12642
|
+
]
|
|
12262
12643
|
}),
|
|
12263
12644
|
credits.length > 0 ? /* @__PURE__ */ jsx("div", {
|
|
12264
12645
|
className: "space-y-2",
|
|
@@ -12637,33 +13018,44 @@ function getItemTitle$1(item) {
|
|
|
12637
13018
|
/**
|
|
12638
13019
|
* Content list view with table display and trash tab
|
|
12639
13020
|
*/
|
|
12640
|
-
function ContentList({ collection, collectionLabel, items, trashedItems = [], isLoading, isTrashedLoading, onDelete, onDuplicate, onRestore, onPermanentDelete, onLoadMore, onLoadMoreTrashed, hasMore, hasMoreTrashed, trashedCount = 0, i18n, activeLocale, onLocaleChange, urlPattern, sort, onSortChange, total }) {
|
|
13021
|
+
function ContentList({ collection, collectionLabel, items, trashedItems = [], isLoading, isTrashedLoading, onDelete, onDuplicate, onRestore, onPermanentDelete, onLoadMore, onLoadMoreTrashed, hasMore, hasMoreTrashed, trashedCount = 0, i18n, activeLocale, onLocaleChange, urlPattern, sort, onSortChange, total, onSearchChange }) {
|
|
12641
13022
|
const { _: _t } = useLingui();
|
|
12642
13023
|
const [activeTab, setActiveTab] = React$1.useState("all");
|
|
12643
13024
|
const [searchQuery, setSearchQuery] = React$1.useState("");
|
|
12644
13025
|
const [page, setPage] = React$1.useState(0);
|
|
13026
|
+
const serverSearch = !!onSearchChange;
|
|
13027
|
+
const debouncedSearch = useDebouncedValue$1(searchQuery, 300);
|
|
13028
|
+
React$1.useEffect(() => {
|
|
13029
|
+
if (onSearchChange) onSearchChange(debouncedSearch.trim());
|
|
13030
|
+
}, [debouncedSearch, onSearchChange]);
|
|
12645
13031
|
const handleSearchChange = (e) => {
|
|
12646
13032
|
setSearchQuery(e.target.value);
|
|
12647
13033
|
setPage(0);
|
|
12648
13034
|
};
|
|
12649
13035
|
const filteredItems = React$1.useMemo(() => {
|
|
12650
|
-
if (!searchQuery) return items;
|
|
13036
|
+
if (serverSearch || !searchQuery) return items;
|
|
12651
13037
|
const query = searchQuery.toLowerCase();
|
|
12652
13038
|
return items.filter((item) => getItemTitle$1(item).toLowerCase().includes(query));
|
|
12653
|
-
}, [
|
|
12654
|
-
|
|
13039
|
+
}, [
|
|
13040
|
+
items,
|
|
13041
|
+
searchQuery,
|
|
13042
|
+
serverSearch
|
|
13043
|
+
]);
|
|
13044
|
+
const activeSearch = serverSearch ? debouncedSearch.trim() : searchQuery;
|
|
13045
|
+
const effectiveTotal = typeof total === "number" && (serverSearch || !searchQuery) ? total : filteredItems.length;
|
|
12655
13046
|
const totalPages = Math.max(1, Math.ceil(effectiveTotal / PAGE_SIZE));
|
|
12656
13047
|
const clampedPage = Math.min(page, totalPages - 1);
|
|
12657
13048
|
const paginatedItems = filteredItems.slice(clampedPage * PAGE_SIZE, (clampedPage + 1) * PAGE_SIZE);
|
|
12658
13049
|
React$1.useEffect(() => {
|
|
12659
|
-
if (!hasMore || !onLoadMore || searchQuery) return;
|
|
13050
|
+
if (!hasMore || !onLoadMore || !serverSearch && searchQuery) return;
|
|
12660
13051
|
if (clampedPage >= Math.ceil(filteredItems.length / PAGE_SIZE) - 1) onLoadMore();
|
|
12661
13052
|
}, [
|
|
12662
13053
|
clampedPage,
|
|
12663
13054
|
filteredItems.length,
|
|
12664
13055
|
hasMore,
|
|
12665
13056
|
onLoadMore,
|
|
12666
|
-
searchQuery
|
|
13057
|
+
searchQuery,
|
|
13058
|
+
serverSearch
|
|
12667
13059
|
]);
|
|
12668
13060
|
return /* @__PURE__ */ jsxs("div", {
|
|
12669
13061
|
className: "space-y-4",
|
|
@@ -12693,7 +13085,7 @@ function ContentList({ collection, collectionLabel, items, trashedItems = [], is
|
|
|
12693
13085
|
})
|
|
12694
13086
|
})]
|
|
12695
13087
|
}),
|
|
12696
|
-
items.length > 0 && /* @__PURE__ */ jsxs("div", {
|
|
13088
|
+
(serverSearch || items.length > 0) && /* @__PURE__ */ jsxs("div", {
|
|
12697
13089
|
className: "relative max-w-sm",
|
|
12698
13090
|
children: [/* @__PURE__ */ jsx(MagnifyingGlass, { className: "absolute start-3 top-1/2 -translate-y-1/2 h-4 w-4 text-kumo-subtle" }), /* @__PURE__ */ jsx(Input, {
|
|
12699
13091
|
type: "search",
|
|
@@ -12810,10 +13202,14 @@ function ContentList({ collection, collectionLabel, items, trashedItems = [], is
|
|
|
12810
13202
|
message: "Loading..."
|
|
12811
13203
|
})]
|
|
12812
13204
|
})
|
|
12813
|
-
}) }) : items.length === 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */
|
|
13205
|
+
}) }) : items.length === 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", {
|
|
12814
13206
|
colSpan: i18n ? 5 : 4,
|
|
12815
13207
|
className: "px-4 py-8 text-center text-kumo-subtle",
|
|
12816
|
-
children:
|
|
13208
|
+
children: activeSearch ? _t({
|
|
13209
|
+
id: "QpOp1w",
|
|
13210
|
+
message: "No results for \"{activeSearch}\"",
|
|
13211
|
+
values: { activeSearch }
|
|
13212
|
+
}) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
12817
13213
|
_t({
|
|
12818
13214
|
id: "yHHMoK",
|
|
12819
13215
|
message: "No {0} yet.",
|
|
@@ -12830,14 +13226,14 @@ function ContentList({ collection, collectionLabel, items, trashedItems = [], is
|
|
|
12830
13226
|
message: "Create your first one"
|
|
12831
13227
|
})
|
|
12832
13228
|
})
|
|
12833
|
-
]
|
|
13229
|
+
] })
|
|
12834
13230
|
}) }) : paginatedItems.length === 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", {
|
|
12835
13231
|
colSpan: i18n ? 5 : 4,
|
|
12836
13232
|
className: "px-4 py-8 text-center text-kumo-subtle",
|
|
12837
13233
|
children: _t({
|
|
12838
|
-
id: "
|
|
12839
|
-
message: "No results for \"{
|
|
12840
|
-
values: {
|
|
13234
|
+
id: "QpOp1w",
|
|
13235
|
+
message: "No results for \"{activeSearch}\"",
|
|
13236
|
+
values: { activeSearch }
|
|
12841
13237
|
})
|
|
12842
13238
|
}) }) : paginatedItems.map((item) => /* @__PURE__ */ jsx(ContentListItem, {
|
|
12843
13239
|
item,
|
|
@@ -12855,10 +13251,11 @@ function ContentList({ collection, collectionLabel, items, trashedItems = [], is
|
|
|
12855
13251
|
children: [/* @__PURE__ */ jsx("span", {
|
|
12856
13252
|
className: "text-sm text-kumo-subtle",
|
|
12857
13253
|
children: renderItemCount({
|
|
12858
|
-
searchQuery,
|
|
13254
|
+
searchQuery: activeSearch,
|
|
12859
13255
|
filteredCount: filteredItems.length,
|
|
12860
13256
|
total,
|
|
12861
|
-
hasMore
|
|
13257
|
+
hasMore,
|
|
13258
|
+
serverSearch
|
|
12862
13259
|
})
|
|
12863
13260
|
}), /* @__PURE__ */ jsxs("div", {
|
|
12864
13261
|
className: "flex items-center gap-2",
|
|
@@ -13041,21 +13438,26 @@ function SortableTh({ field, sort, onSortChange, label }) {
|
|
|
13041
13438
|
}
|
|
13042
13439
|
/**
|
|
13043
13440
|
* Render the row-count line above pagination. The rules are:
|
|
13044
|
-
* - A search query always wins — say how many matches there are.
|
|
13441
|
+
* - A search query always wins — say how many matches there are. In
|
|
13442
|
+
* server-search mode the server reports the full match count via `total`;
|
|
13443
|
+
* `filteredCount` is only the loaded page, so it would undercount.
|
|
13045
13444
|
* - When the server reported a total, use it (no `+` suffix needed —
|
|
13046
13445
|
* we know the count).
|
|
13047
13446
|
* - Otherwise fall back to the pre-refactor behavior: loaded count,
|
|
13048
13447
|
* with `+` when there are more pages the user hasn't fetched yet.
|
|
13049
13448
|
*/
|
|
13050
|
-
function renderItemCount({ searchQuery, filteredCount, total, hasMore }) {
|
|
13051
|
-
if (searchQuery)
|
|
13052
|
-
|
|
13053
|
-
|
|
13054
|
-
|
|
13055
|
-
|
|
13056
|
-
|
|
13057
|
-
|
|
13058
|
-
|
|
13449
|
+
function renderItemCount({ searchQuery, filteredCount, total, hasMore, serverSearch }) {
|
|
13450
|
+
if (searchQuery) {
|
|
13451
|
+
const matchCount = serverSearch && typeof total === "number" ? total : filteredCount;
|
|
13452
|
+
return i18n._({
|
|
13453
|
+
id: "UFFm2d",
|
|
13454
|
+
message: "{matchCount, plural, one {# item matching \"{searchQuery}\"} other {# items matching \"{searchQuery}\"}}",
|
|
13455
|
+
values: {
|
|
13456
|
+
matchCount,
|
|
13457
|
+
searchQuery
|
|
13458
|
+
}
|
|
13459
|
+
});
|
|
13460
|
+
}
|
|
13059
13461
|
if (typeof total === "number") return i18n._({
|
|
13060
13462
|
id: "Q/6//K",
|
|
13061
13463
|
message: "{total, plural, one {# item} other {# items}}",
|
|
@@ -13593,8 +13995,8 @@ function AllowedTypesEditor({ value, onChange }) {
|
|
|
13593
13995
|
|
|
13594
13996
|
//#endregion
|
|
13595
13997
|
//#region src/components/FieldEditor.tsx
|
|
13596
|
-
const SLUG_INVALID_CHARS_REGEX = /[^a-z0-9]+/g;
|
|
13597
|
-
const SLUG_LEADING_TRAILING_REGEX = /^_|_$/g;
|
|
13998
|
+
const SLUG_INVALID_CHARS_REGEX$1 = /[^a-z0-9]+/g;
|
|
13999
|
+
const SLUG_LEADING_TRAILING_REGEX$1 = /^_|_$/g;
|
|
13598
14000
|
function getInitialFormState(field) {
|
|
13599
14001
|
if (field) return {
|
|
13600
14002
|
step: "config",
|
|
@@ -13846,7 +14248,7 @@ function FieldEditor({ open, onOpenChange, field, onSave, isSaving }) {
|
|
|
13846
14248
|
];
|
|
13847
14249
|
const handleLabelChange = (value) => {
|
|
13848
14250
|
setField("label", value);
|
|
13849
|
-
if (!field) setField("slug", value.toLowerCase().replace(SLUG_INVALID_CHARS_REGEX, "_").replace(SLUG_LEADING_TRAILING_REGEX, ""));
|
|
14251
|
+
if (!field) setField("slug", value.toLowerCase().replace(SLUG_INVALID_CHARS_REGEX$1, "_").replace(SLUG_LEADING_TRAILING_REGEX$1, ""));
|
|
13850
14252
|
};
|
|
13851
14253
|
const handleTypeSelect = (type) => {
|
|
13852
14254
|
setFormState((prev) => ({
|
|
@@ -14205,7 +14607,7 @@ function FieldEditor({ open, onOpenChange, field, onSave, isSaving }) {
|
|
|
14205
14607
|
updated[i] = {
|
|
14206
14608
|
...sf,
|
|
14207
14609
|
label: e.target.value,
|
|
14208
|
-
slug: e.target.value.toLowerCase().replace(SLUG_INVALID_CHARS_REGEX, "_").replace(SLUG_LEADING_TRAILING_REGEX, "")
|
|
14610
|
+
slug: e.target.value.toLowerCase().replace(SLUG_INVALID_CHARS_REGEX$1, "_").replace(SLUG_LEADING_TRAILING_REGEX$1, "")
|
|
14209
14611
|
};
|
|
14210
14612
|
setFormState((prev) => ({
|
|
14211
14613
|
...prev,
|
|
@@ -14992,7 +15394,7 @@ function ContentTypeEditor({ collection, isNew, isSaving, onSave, onAddField, on
|
|
|
14992
15394
|
strategy: verticalListSortingStrategy,
|
|
14993
15395
|
children: /* @__PURE__ */ jsx("div", {
|
|
14994
15396
|
className: "divide-y",
|
|
14995
|
-
children: fields.map((field) => /* @__PURE__ */ jsx(FieldRow, {
|
|
15397
|
+
children: fields.map((field) => /* @__PURE__ */ jsx(FieldRow$1, {
|
|
14996
15398
|
field,
|
|
14997
15399
|
isFromCode,
|
|
14998
15400
|
onEdit: () => handleEditField(field),
|
|
@@ -15044,7 +15446,7 @@ function ContentTypeEditor({ collection, isNew, isSaving, onSave, onAddField, on
|
|
|
15044
15446
|
]
|
|
15045
15447
|
});
|
|
15046
15448
|
}
|
|
15047
|
-
function FieldRow({ field, isFromCode, onEdit, onDelete }) {
|
|
15449
|
+
function FieldRow$1({ field, isFromCode, onEdit, onDelete }) {
|
|
15048
15450
|
const { _: _t2 } = useLingui();
|
|
15049
15451
|
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
|
15050
15452
|
id: field.id,
|
|
@@ -20221,15 +20623,34 @@ function formatDate$1(isoString) {
|
|
|
20221
20623
|
|
|
20222
20624
|
//#endregion
|
|
20223
20625
|
//#region src/components/MediaLibrary.tsx
|
|
20626
|
+
/** Maps a coarse type-filter choice to the media list's `mimeType` filter. */
|
|
20627
|
+
function mimeForTypeFilter(value) {
|
|
20628
|
+
switch (value) {
|
|
20629
|
+
case "image": return "image/";
|
|
20630
|
+
case "video": return "video/";
|
|
20631
|
+
case "audio": return "audio/";
|
|
20632
|
+
case "document": return ["application/", "text/"];
|
|
20633
|
+
default: return;
|
|
20634
|
+
}
|
|
20635
|
+
}
|
|
20224
20636
|
/**
|
|
20225
20637
|
* Media library component with upload, provider tabs, and grid view
|
|
20226
20638
|
*/
|
|
20227
|
-
function MediaLibrary({ items = [], isLoading, onUpload, onDelete, onItemUpdated, hasMore, onLoadMore }) {
|
|
20639
|
+
function MediaLibrary({ items = [], isLoading, onUpload, onDelete, onItemUpdated, hasMore, onLoadMore, onLocalSearchChange, onLocalMimeFilterChange }) {
|
|
20228
20640
|
const { _: _t } = useLingui();
|
|
20229
20641
|
const [viewMode, setViewMode] = React$1.useState("grid");
|
|
20230
20642
|
const [selectedItem, setSelectedItem] = React$1.useState(null);
|
|
20231
20643
|
const [activeProvider, setActiveProvider] = React$1.useState("local");
|
|
20232
20644
|
const [searchQuery, setSearchQuery] = React$1.useState("");
|
|
20645
|
+
const [localTypeFilter, setLocalTypeFilter] = React$1.useState("all");
|
|
20646
|
+
const debouncedSearch = useDebouncedValue$1(searchQuery, 300);
|
|
20647
|
+
React$1.useEffect(() => {
|
|
20648
|
+
if (activeProvider === "local" && onLocalSearchChange) onLocalSearchChange(debouncedSearch.trim());
|
|
20649
|
+
}, [
|
|
20650
|
+
debouncedSearch,
|
|
20651
|
+
activeProvider,
|
|
20652
|
+
onLocalSearchChange
|
|
20653
|
+
]);
|
|
20233
20654
|
const [uploadState, setUploadState] = React$1.useState({ status: "idle" });
|
|
20234
20655
|
const fileInputRef = React$1.useRef(null);
|
|
20235
20656
|
const [loadedDimensions, setLoadedDimensions] = React$1.useState({});
|
|
@@ -20549,17 +20970,61 @@ function MediaLibrary({ items = [], isLoading, onUpload, onDelete, onItemUpdated
|
|
|
20549
20970
|
]
|
|
20550
20971
|
})]
|
|
20551
20972
|
}),
|
|
20552
|
-
canSearch && /* @__PURE__ */ jsxs("div", {
|
|
20553
|
-
className: "
|
|
20554
|
-
children: [/* @__PURE__ */
|
|
20555
|
-
|
|
20556
|
-
|
|
20557
|
-
|
|
20558
|
-
|
|
20559
|
-
|
|
20560
|
-
|
|
20561
|
-
|
|
20562
|
-
|
|
20973
|
+
(canSearch || activeProvider === "local") && /* @__PURE__ */ jsxs("div", {
|
|
20974
|
+
className: "flex flex-wrap items-center gap-3",
|
|
20975
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
20976
|
+
className: "relative max-w-sm flex-1",
|
|
20977
|
+
children: [/* @__PURE__ */ jsx(MagnifyingGlass, { className: "absolute start-3 top-1/2 -translate-y-1/2 h-4 w-4 text-kumo-subtle" }), /* @__PURE__ */ jsx(Input, {
|
|
20978
|
+
type: "search",
|
|
20979
|
+
placeholder: activeProvider === "local" ? _t({
|
|
20980
|
+
id: "Fwo2VZ",
|
|
20981
|
+
message: "Search by filename..."
|
|
20982
|
+
}) : _t({
|
|
20983
|
+
id: "YIix5Y",
|
|
20984
|
+
message: "Search..."
|
|
20985
|
+
}),
|
|
20986
|
+
"aria-label": _t({
|
|
20987
|
+
id: "GzM+mT",
|
|
20988
|
+
message: "Search media"
|
|
20989
|
+
}),
|
|
20990
|
+
value: searchQuery,
|
|
20991
|
+
onChange: (e) => setSearchQuery(e.target.value),
|
|
20992
|
+
maxLength: MEDIA_SEARCH_MAX_LENGTH,
|
|
20993
|
+
className: "ps-9"
|
|
20994
|
+
})]
|
|
20995
|
+
}), activeProvider === "local" && /* @__PURE__ */ jsx(Select, {
|
|
20996
|
+
value: localTypeFilter,
|
|
20997
|
+
onValueChange: (v) => {
|
|
20998
|
+
const next = v ?? "all";
|
|
20999
|
+
setLocalTypeFilter(next);
|
|
21000
|
+
onLocalMimeFilterChange?.(mimeForTypeFilter(next));
|
|
21001
|
+
},
|
|
21002
|
+
items: {
|
|
21003
|
+
all: _t({
|
|
21004
|
+
id: "h6zvpR",
|
|
21005
|
+
message: "All types"
|
|
21006
|
+
}),
|
|
21007
|
+
image: _t({
|
|
21008
|
+
id: "an5hVd",
|
|
21009
|
+
message: "Images"
|
|
21010
|
+
}),
|
|
21011
|
+
video: _t({
|
|
21012
|
+
id: "vSJd18",
|
|
21013
|
+
message: "Video"
|
|
21014
|
+
}),
|
|
21015
|
+
audio: _t({
|
|
21016
|
+
id: "/rTz0M",
|
|
21017
|
+
message: "Audio"
|
|
21018
|
+
}),
|
|
21019
|
+
document: _t({
|
|
21020
|
+
id: "E/muDO",
|
|
21021
|
+
message: "Documents"
|
|
21022
|
+
})
|
|
21023
|
+
},
|
|
21024
|
+
"aria-label": _t({
|
|
21025
|
+
id: "mP+cvB",
|
|
21026
|
+
message: "Filter by type"
|
|
21027
|
+
})
|
|
20563
21028
|
})]
|
|
20564
21029
|
}),
|
|
20565
21030
|
currentLoading && currentItems.length === 0 && currentProviderItems.length === 0 ? /* @__PURE__ */ jsx("div", {
|
|
@@ -21371,8 +21836,9 @@ function MenuEditor() {
|
|
|
21371
21836
|
});
|
|
21372
21837
|
};
|
|
21373
21838
|
const handleAddContent = (item) => {
|
|
21839
|
+
const type = item.collection === "pages" ? "page" : item.collection === "posts" ? "post" : "collection";
|
|
21374
21840
|
createMutation.mutate({
|
|
21375
|
-
type
|
|
21841
|
+
type,
|
|
21376
21842
|
label: item.title,
|
|
21377
21843
|
referenceCollection: item.collection,
|
|
21378
21844
|
referenceId: item.id
|
|
@@ -28589,7 +29055,7 @@ function SetupWizard() {
|
|
|
28589
29055
|
* Quick navigation and search across the admin interface.
|
|
28590
29056
|
* Opens with Cmd+K (Mac) or Ctrl+K (Windows/Linux).
|
|
28591
29057
|
*/
|
|
28592
|
-
const ROLE_ADMIN$
|
|
29058
|
+
const ROLE_ADMIN$3 = 50;
|
|
28593
29059
|
const ROLE_EDITOR$2 = 40;
|
|
28594
29060
|
const ROUTE_PARAM_REGEX = /\$(\w+)/g;
|
|
28595
29061
|
const SEARCH_DEBOUNCE_MS = 300;
|
|
@@ -28691,7 +29157,7 @@ function buildNavItems(manifest, userRole) {
|
|
|
28691
29157
|
},
|
|
28692
29158
|
to: "/content-types",
|
|
28693
29159
|
icon: Database,
|
|
28694
|
-
minRole: ROLE_ADMIN$
|
|
29160
|
+
minRole: ROLE_ADMIN$3,
|
|
28695
29161
|
keywords: ["schema", "collections"]
|
|
28696
29162
|
}, {
|
|
28697
29163
|
id: "categories",
|
|
@@ -28723,7 +29189,7 @@ function buildNavItems(manifest, userRole) {
|
|
|
28723
29189
|
},
|
|
28724
29190
|
to: "/users",
|
|
28725
29191
|
icon: Users,
|
|
28726
|
-
minRole: ROLE_ADMIN$
|
|
29192
|
+
minRole: ROLE_ADMIN$3,
|
|
28727
29193
|
keywords: ["accounts", "team"]
|
|
28728
29194
|
}, {
|
|
28729
29195
|
id: "plugins",
|
|
@@ -28733,7 +29199,7 @@ function buildNavItems(manifest, userRole) {
|
|
|
28733
29199
|
},
|
|
28734
29200
|
to: "/plugins-manager",
|
|
28735
29201
|
icon: PuzzlePiece,
|
|
28736
|
-
minRole: ROLE_ADMIN$
|
|
29202
|
+
minRole: ROLE_ADMIN$3,
|
|
28737
29203
|
keywords: ["extensions", "add-ons"]
|
|
28738
29204
|
}, {
|
|
28739
29205
|
id: "import",
|
|
@@ -28743,7 +29209,7 @@ function buildNavItems(manifest, userRole) {
|
|
|
28743
29209
|
},
|
|
28744
29210
|
to: "/import/wordpress",
|
|
28745
29211
|
icon: Upload,
|
|
28746
|
-
minRole: ROLE_ADMIN$
|
|
29212
|
+
minRole: ROLE_ADMIN$3,
|
|
28747
29213
|
keywords: ["wordpress", "migrate"]
|
|
28748
29214
|
}, {
|
|
28749
29215
|
id: "settings",
|
|
@@ -28753,7 +29219,7 @@ function buildNavItems(manifest, userRole) {
|
|
|
28753
29219
|
},
|
|
28754
29220
|
to: "/settings",
|
|
28755
29221
|
icon: Gear,
|
|
28756
|
-
minRole: ROLE_ADMIN$
|
|
29222
|
+
minRole: ROLE_ADMIN$3,
|
|
28757
29223
|
keywords: ["configuration", "preferences"]
|
|
28758
29224
|
}, {
|
|
28759
29225
|
id: "security",
|
|
@@ -28763,7 +29229,7 @@ function buildNavItems(manifest, userRole) {
|
|
|
28763
29229
|
},
|
|
28764
29230
|
to: "/settings/security",
|
|
28765
29231
|
icon: Gear,
|
|
28766
|
-
minRole: ROLE_ADMIN$
|
|
29232
|
+
minRole: ROLE_ADMIN$3,
|
|
28767
29233
|
keywords: ["passkeys", "authentication"]
|
|
28768
29234
|
});
|
|
28769
29235
|
for (const [pluginId, config] of Object.entries(manifest.plugins)) {
|
|
@@ -28953,9 +29419,124 @@ function AdminCommandPalette({ manifest }) {
|
|
|
28953
29419
|
|
|
28954
29420
|
//#endregion
|
|
28955
29421
|
//#region src/components/Sidebar.tsx
|
|
28956
|
-
const ROLE_ADMIN$
|
|
29422
|
+
const ROLE_ADMIN$2 = 50;
|
|
28957
29423
|
const ROLE_EDITOR$1 = 40;
|
|
28958
29424
|
/**
|
|
29425
|
+
* Static invariants for nav entries that have AC-level visibility
|
|
29426
|
+
* requirements (Phase 5 of Discussion #1174: "Admin sees the 'Byline
|
|
29427
|
+
* Schema' entry; Editor does not").
|
|
29428
|
+
*
|
|
29429
|
+
* Exported as plain data so a unit test can assert the route + role
|
|
29430
|
+
* pairing without mounting Kumo's Sidebar primitive — which portals
|
|
29431
|
+
* its rendered content to `document.body` and applies collapse-state
|
|
29432
|
+
* CSS (`display:none` on labels at narrow viewports), making
|
|
29433
|
+
* full-DOM tests of role filtering brittle. The runtime `adminItems`
|
|
29434
|
+
* array below references these constants directly so the test
|
|
29435
|
+
* effectively guards the production list.
|
|
29436
|
+
*/
|
|
29437
|
+
const BYLINE_SCHEMA_NAV_ITEM = {
|
|
29438
|
+
to: "/byline-schema",
|
|
29439
|
+
minRole: ROLE_ADMIN$2
|
|
29440
|
+
};
|
|
29441
|
+
/**
|
|
29442
|
+
* Filter a nav-items list by user role. Pure function — exported so
|
|
29443
|
+
* tests can verify the role gate without rendering the sidebar. An
|
|
29444
|
+
* item passes when it has no `minRole` (public) or the user is at
|
|
29445
|
+
* least the required level.
|
|
29446
|
+
*/
|
|
29447
|
+
function filterNavItemsByRole(items, userRole) {
|
|
29448
|
+
return items.filter((item) => !item.minRole || userRole >= item.minRole);
|
|
29449
|
+
}
|
|
29450
|
+
/**
|
|
29451
|
+
* Static map of common plugin admin-page icon names to Phosphor components.
|
|
29452
|
+
*
|
|
29453
|
+
* Plugins declare `adminPages: [{ path, label, icon }]`, where `icon` is a
|
|
29454
|
+
* lower/kebab name. This table covers the names used across the EmDash
|
|
29455
|
+
* docs/templates (including lucide-style names like `settings`/`chart` that
|
|
29456
|
+
* don't match Phosphor's own naming) plus common nav glyphs. These are
|
|
29457
|
+
* statically imported, so the everyday case resolves *synchronously* and the
|
|
29458
|
+
* handful of components ship in the main bundle — the full Phosphor set is
|
|
29459
|
+
* never pulled in for them. Any name not listed here is resolved lazily
|
|
29460
|
+
* (see `resolveNavIcon`), so there is no hard ceiling.
|
|
29461
|
+
*/
|
|
29462
|
+
const NAV_ICON_MAP = {
|
|
29463
|
+
settings: Gear,
|
|
29464
|
+
gear: Gear,
|
|
29465
|
+
chart: ChartBar,
|
|
29466
|
+
"chart-line": ChartLine,
|
|
29467
|
+
dashboard: SquaresFour,
|
|
29468
|
+
history: ClockCounterClockwise,
|
|
29469
|
+
image: Image$1,
|
|
29470
|
+
award: Medal,
|
|
29471
|
+
trophy: Trophy,
|
|
29472
|
+
grid: GridFour,
|
|
29473
|
+
crop: Crop,
|
|
29474
|
+
book: BookOpen,
|
|
29475
|
+
plug: Plug,
|
|
29476
|
+
code: Code,
|
|
29477
|
+
file: FileText,
|
|
29478
|
+
document: FileText,
|
|
29479
|
+
users: Users,
|
|
29480
|
+
database: Database,
|
|
29481
|
+
list: List,
|
|
29482
|
+
calendar: CalendarBlank,
|
|
29483
|
+
bell: Bell,
|
|
29484
|
+
folder: Folder,
|
|
29485
|
+
star: Star,
|
|
29486
|
+
tag: Tag,
|
|
29487
|
+
link: LinkSimple,
|
|
29488
|
+
search: MagnifyingGlass,
|
|
29489
|
+
palette: Palette,
|
|
29490
|
+
upload: Upload
|
|
29491
|
+
};
|
|
29492
|
+
/** Word separators in icon names: kebab, snake, or whitespace. */
|
|
29493
|
+
const ICON_NAME_SEPARATOR = /[-_\s]+/;
|
|
29494
|
+
/**
|
|
29495
|
+
* Convert a kebab/snake/space icon name to Phosphor's PascalCase component
|
|
29496
|
+
* name (`chart-bar` → `ChartBar`). Exported for unit testing the pure mapping.
|
|
29497
|
+
*/
|
|
29498
|
+
function toPhosphorIconName(name) {
|
|
29499
|
+
return name.split(ICON_NAME_SEPARATOR).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
|
|
29500
|
+
}
|
|
29501
|
+
/**
|
|
29502
|
+
* Cache of lazily-loaded icon components, keyed by Phosphor component name.
|
|
29503
|
+
* `React.lazy` must return a stable identity across renders (a fresh lazy
|
|
29504
|
+
* component on every render would remount and re-suspend), so memoize here.
|
|
29505
|
+
*/
|
|
29506
|
+
const lazyIconCache = /* @__PURE__ */ new Map();
|
|
29507
|
+
/**
|
|
29508
|
+
* Resolve a plugin page's `icon` name to a component.
|
|
29509
|
+
*
|
|
29510
|
+
* Resolution order:
|
|
29511
|
+
* 1. No icon → `PuzzlePiece` (the common icon-less page never suspends).
|
|
29512
|
+
* 2. A name in `NAV_ICON_MAP` → its statically-imported component (sync,
|
|
29513
|
+
* already in the main bundle — no extra chunk for everyday icons).
|
|
29514
|
+
* 3. Anything else → the matching `@phosphor-icons/react` component, loaded
|
|
29515
|
+
* lazily from a code-split chunk the first time it's used. This gives
|
|
29516
|
+
* access to the entire Phosphor set without pulling it into the main
|
|
29517
|
+
* bundle, and only loads when a plugin uses an icon outside the map.
|
|
29518
|
+
* Names that don't exist in Phosphor fall back to `PuzzlePiece`.
|
|
29519
|
+
*
|
|
29520
|
+
* Case 3 returns a `React.lazy` component, so call sites must render the
|
|
29521
|
+
* result inside a `<React.Suspense>` boundary (see `NavMenuLink`). Exported
|
|
29522
|
+
* so a unit test can assert resolution without mounting the portal-heavy
|
|
29523
|
+
* Kumo Sidebar.
|
|
29524
|
+
*/
|
|
29525
|
+
function resolveNavIcon(name) {
|
|
29526
|
+
if (!name) return PuzzlePiece;
|
|
29527
|
+
const mapped = NAV_ICON_MAP[name];
|
|
29528
|
+
if (mapped) return mapped;
|
|
29529
|
+
const componentName = toPhosphorIconName(name);
|
|
29530
|
+
let icon = lazyIconCache.get(componentName);
|
|
29531
|
+
if (!icon) {
|
|
29532
|
+
icon = React$1.lazy(async () => {
|
|
29533
|
+
return { default: (await import("@phosphor-icons/react"))[componentName] ?? PuzzlePiece };
|
|
29534
|
+
});
|
|
29535
|
+
lazyIconCache.set(componentName, icon);
|
|
29536
|
+
}
|
|
29537
|
+
return icon;
|
|
29538
|
+
}
|
|
29539
|
+
/**
|
|
28959
29540
|
* Navigation item rendered as a TanStack Router <Link> inside kumo's
|
|
28960
29541
|
* Sidebar.MenuItem. Styled to match kumo MenuButton appearance.
|
|
28961
29542
|
* This approach guarantees client-side navigation works correctly.
|
|
@@ -28963,6 +29544,7 @@ const ROLE_EDITOR$1 = 40;
|
|
|
28963
29544
|
function NavMenuLink({ item, isActive }) {
|
|
28964
29545
|
const { state } = useSidebar();
|
|
28965
29546
|
const Icon = item.icon;
|
|
29547
|
+
const iconClassName = cn("emdash-nav-icon size-[18px] shrink-0 transition-colors duration-200", isActive ? "text-white" : "text-white/60 group-hover/menu-button:text-white/90");
|
|
28966
29548
|
const link = /* @__PURE__ */ jsxs(Link$1, {
|
|
28967
29549
|
to: item.to,
|
|
28968
29550
|
params: item.params,
|
|
@@ -28970,9 +29552,15 @@ function NavMenuLink({ item, isActive }) {
|
|
|
28970
29552
|
"data-active": isActive || void 0,
|
|
28971
29553
|
"data-sidebar": "menu-button",
|
|
28972
29554
|
className: cn("emdash-nav-link group/menu-button flex w-full min-w-0 items-center gap-2.5 rounded-md no-underline outline-none cursor-pointer", "min-h-[36px] px-3 py-1.5 text-[13px]", "transition-all duration-200 ease-out", isActive ? "bg-kumo-brand text-white" : "text-white/70 hover:text-white hover:bg-white/8", "focus-visible:ring-2 focus-visible:ring-kumo-brand/50"),
|
|
28973
|
-
children: [/* @__PURE__ */ jsx(
|
|
28974
|
-
|
|
28975
|
-
|
|
29555
|
+
children: [/* @__PURE__ */ jsx(React$1.Suspense, {
|
|
29556
|
+
fallback: /* @__PURE__ */ jsx(PuzzlePiece, {
|
|
29557
|
+
className: iconClassName,
|
|
29558
|
+
"aria-hidden": "true"
|
|
29559
|
+
}),
|
|
29560
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
29561
|
+
className: iconClassName,
|
|
29562
|
+
"aria-hidden": "true"
|
|
29563
|
+
})
|
|
28976
29564
|
}), /* @__PURE__ */ jsxs("span", {
|
|
28977
29565
|
className: "emdash-nav-label flex flex-1 items-center min-w-0 text-start overflow-hidden",
|
|
28978
29566
|
children: [item.label, item.badge != null && item.badge > 0 && /* @__PURE__ */ jsx(KumoSidebar.MenuBadge, { children: item.badge })]
|
|
@@ -29060,7 +29648,7 @@ function SidebarNav({ manifest }) {
|
|
|
29060
29648
|
message: "Redirects"
|
|
29061
29649
|
}),
|
|
29062
29650
|
icon: ArrowsLeftRight,
|
|
29063
|
-
minRole: ROLE_ADMIN$
|
|
29651
|
+
minRole: ROLE_ADMIN$2
|
|
29064
29652
|
},
|
|
29065
29653
|
{
|
|
29066
29654
|
to: "/widgets",
|
|
@@ -29105,7 +29693,7 @@ function SidebarNav({ manifest }) {
|
|
|
29105
29693
|
message: "Content Types"
|
|
29106
29694
|
}),
|
|
29107
29695
|
icon: Database,
|
|
29108
|
-
minRole: ROLE_ADMIN$
|
|
29696
|
+
minRole: ROLE_ADMIN$2
|
|
29109
29697
|
},
|
|
29110
29698
|
{
|
|
29111
29699
|
to: "/users",
|
|
@@ -29114,7 +29702,7 @@ function SidebarNav({ manifest }) {
|
|
|
29114
29702
|
message: "Users"
|
|
29115
29703
|
}),
|
|
29116
29704
|
icon: Users,
|
|
29117
|
-
minRole: ROLE_ADMIN$
|
|
29705
|
+
minRole: ROLE_ADMIN$2
|
|
29118
29706
|
},
|
|
29119
29707
|
{
|
|
29120
29708
|
to: "/plugins-manager",
|
|
@@ -29123,7 +29711,7 @@ function SidebarNav({ manifest }) {
|
|
|
29123
29711
|
message: "Plugins"
|
|
29124
29712
|
}),
|
|
29125
29713
|
icon: PuzzlePiece,
|
|
29126
|
-
minRole: ROLE_ADMIN$
|
|
29714
|
+
minRole: ROLE_ADMIN$2
|
|
29127
29715
|
}
|
|
29128
29716
|
];
|
|
29129
29717
|
if (manifest.registry) adminItems.push({
|
|
@@ -29133,7 +29721,7 @@ function SidebarNav({ manifest }) {
|
|
|
29133
29721
|
message: "Registry"
|
|
29134
29722
|
}),
|
|
29135
29723
|
icon: Storefront,
|
|
29136
|
-
minRole: ROLE_ADMIN$
|
|
29724
|
+
minRole: ROLE_ADMIN$2
|
|
29137
29725
|
});
|
|
29138
29726
|
else if (manifest.marketplace) adminItems.push({
|
|
29139
29727
|
to: "/plugins/marketplace",
|
|
@@ -29142,7 +29730,7 @@ function SidebarNav({ manifest }) {
|
|
|
29142
29730
|
message: "Marketplace"
|
|
29143
29731
|
}),
|
|
29144
29732
|
icon: Storefront,
|
|
29145
|
-
minRole: ROLE_ADMIN$
|
|
29733
|
+
minRole: ROLE_ADMIN$2
|
|
29146
29734
|
});
|
|
29147
29735
|
if (manifest.marketplace) adminItems.push({
|
|
29148
29736
|
to: "/themes/marketplace",
|
|
@@ -29151,7 +29739,7 @@ function SidebarNav({ manifest }) {
|
|
|
29151
29739
|
message: "Themes"
|
|
29152
29740
|
}),
|
|
29153
29741
|
icon: Palette,
|
|
29154
|
-
minRole: ROLE_ADMIN$
|
|
29742
|
+
minRole: ROLE_ADMIN$2
|
|
29155
29743
|
});
|
|
29156
29744
|
adminItems.push({
|
|
29157
29745
|
to: "/import/wordpress",
|
|
@@ -29160,7 +29748,7 @@ function SidebarNav({ manifest }) {
|
|
|
29160
29748
|
message: "Import"
|
|
29161
29749
|
}),
|
|
29162
29750
|
icon: Upload,
|
|
29163
|
-
minRole: ROLE_ADMIN$
|
|
29751
|
+
minRole: ROLE_ADMIN$2
|
|
29164
29752
|
}, {
|
|
29165
29753
|
to: "/settings",
|
|
29166
29754
|
label: _t({
|
|
@@ -29168,7 +29756,7 @@ function SidebarNav({ manifest }) {
|
|
|
29168
29756
|
message: "Settings"
|
|
29169
29757
|
}),
|
|
29170
29758
|
icon: Gear,
|
|
29171
|
-
minRole: ROLE_ADMIN$
|
|
29759
|
+
minRole: ROLE_ADMIN$2
|
|
29172
29760
|
});
|
|
29173
29761
|
const pluginItems = [];
|
|
29174
29762
|
for (const [pluginId, config] of Object.entries(manifest.plugins)) {
|
|
@@ -29182,16 +29770,15 @@ function SidebarNav({ manifest }) {
|
|
|
29182
29770
|
pluginItems.push({
|
|
29183
29771
|
to: `/plugins/${pluginId}${page.path}`,
|
|
29184
29772
|
label,
|
|
29185
|
-
icon:
|
|
29773
|
+
icon: resolveNavIcon(page.icon)
|
|
29186
29774
|
});
|
|
29187
29775
|
}
|
|
29188
29776
|
}
|
|
29189
29777
|
}
|
|
29190
|
-
const
|
|
29191
|
-
const
|
|
29192
|
-
const
|
|
29193
|
-
const
|
|
29194
|
-
const visiblePlugins = filterByRole(pluginItems);
|
|
29778
|
+
const visibleContent = filterNavItemsByRole(contentItems, userRole);
|
|
29779
|
+
const visibleManage = filterNavItemsByRole(manageItems, userRole);
|
|
29780
|
+
const visibleAdmin = filterNavItemsByRole(adminItems, userRole);
|
|
29781
|
+
const visiblePlugins = filterNavItemsByRole(pluginItems, userRole);
|
|
29195
29782
|
function renderNavItems(items) {
|
|
29196
29783
|
return items.map((item, index) => {
|
|
29197
29784
|
return /* @__PURE__ */ jsx(NavMenuLink, {
|
|
@@ -35288,6 +35875,858 @@ function AuthorMappingStep({ authorMappings, emdashUsers, onMappingChange, onCon
|
|
|
35288
35875
|
});
|
|
35289
35876
|
}
|
|
35290
35877
|
|
|
35878
|
+
//#endregion
|
|
35879
|
+
//#region src/components/BylineFieldEditor.tsx
|
|
35880
|
+
/**
|
|
35881
|
+
* BylineFieldEditor (Phase 5 of Discussion #1174).
|
|
35882
|
+
*
|
|
35883
|
+
* Purpose-built create/edit dialog for byline custom-field definitions.
|
|
35884
|
+
* Constrains the type select to the five v1 types declared by the
|
|
35885
|
+
* `BylineFieldType` union — narrower than the content-field universe —
|
|
35886
|
+
* and exposes the `translatable` toggle (the storage-split flag) that
|
|
35887
|
+
* the content `FieldEditor` doesn't have.
|
|
35888
|
+
*
|
|
35889
|
+
* Single-step form (not the type-picker → config flow used by
|
|
35890
|
+
* `FieldEditor`): with only five types a `<Select>` is faster than a
|
|
35891
|
+
* grid of cards and the dialog stays under ~200 LOC.
|
|
35892
|
+
*
|
|
35893
|
+
* The `translatable` toggle is locked when editing a field that already
|
|
35894
|
+
* has stored values — the server returns `TRANSLATABLE_LOCKED` (409) for
|
|
35895
|
+
* the flip, but disabling the input + showing a help message saves a
|
|
35896
|
+
* round-trip and is friendlier than a toast. Usage is passed in by the
|
|
35897
|
+
* parent (which fetches `getBylineFieldUsage` on edit-open) so this
|
|
35898
|
+
* component stays a pure controlled form.
|
|
35899
|
+
*/
|
|
35900
|
+
const SLUG_INVALID_CHARS_REGEX = /[^a-z0-9]+/g;
|
|
35901
|
+
const SLUG_LEADING_TRAILING_REGEX = /^_|_$/g;
|
|
35902
|
+
/**
|
|
35903
|
+
* The five v1 byline field types. Kept in lock-step with the server-side
|
|
35904
|
+
* `BYLINE_FIELD_TYPES` constant (`packages/core/src/schema/types.ts`).
|
|
35905
|
+
*
|
|
35906
|
+
* `MessageDescriptor`s are resolved with `t(descriptor)` inside the
|
|
35907
|
+
* component so the strings extract correctly to the Lingui catalog.
|
|
35908
|
+
*/
|
|
35909
|
+
const TYPE_OPTIONS = [
|
|
35910
|
+
{
|
|
35911
|
+
value: "string",
|
|
35912
|
+
label: {
|
|
35913
|
+
id: "q13dKg",
|
|
35914
|
+
message: "Short text"
|
|
35915
|
+
}
|
|
35916
|
+
},
|
|
35917
|
+
{
|
|
35918
|
+
value: "text",
|
|
35919
|
+
label: {
|
|
35920
|
+
id: "g+zzQs",
|
|
35921
|
+
message: "Long text"
|
|
35922
|
+
}
|
|
35923
|
+
},
|
|
35924
|
+
{
|
|
35925
|
+
value: "url",
|
|
35926
|
+
label: {
|
|
35927
|
+
id: "IagCbF",
|
|
35928
|
+
message: "URL"
|
|
35929
|
+
}
|
|
35930
|
+
},
|
|
35931
|
+
{
|
|
35932
|
+
value: "boolean",
|
|
35933
|
+
label: {
|
|
35934
|
+
id: "bOwPjT",
|
|
35935
|
+
message: "Boolean"
|
|
35936
|
+
}
|
|
35937
|
+
},
|
|
35938
|
+
{
|
|
35939
|
+
value: "select",
|
|
35940
|
+
label: {
|
|
35941
|
+
id: "rG3WVm",
|
|
35942
|
+
message: "Select"
|
|
35943
|
+
}
|
|
35944
|
+
}
|
|
35945
|
+
];
|
|
35946
|
+
function initialFormState(field) {
|
|
35947
|
+
if (!field) return {
|
|
35948
|
+
slug: "",
|
|
35949
|
+
label: "",
|
|
35950
|
+
type: "string",
|
|
35951
|
+
required: false,
|
|
35952
|
+
translatable: true,
|
|
35953
|
+
options: ""
|
|
35954
|
+
};
|
|
35955
|
+
return {
|
|
35956
|
+
slug: field.slug,
|
|
35957
|
+
label: field.label,
|
|
35958
|
+
type: field.type,
|
|
35959
|
+
required: field.required,
|
|
35960
|
+
translatable: field.translatable,
|
|
35961
|
+
options: field.validation?.options?.join("\n") ?? ""
|
|
35962
|
+
};
|
|
35963
|
+
}
|
|
35964
|
+
function BylineFieldEditor({ open, onOpenChange, field, usageTotal = 0, onSave, isSaving, error }) {
|
|
35965
|
+
const { _: _t } = useLingui();
|
|
35966
|
+
const isEdit = !!field;
|
|
35967
|
+
const [state, setState] = React$1.useState(() => initialFormState(field));
|
|
35968
|
+
React$1.useEffect(() => {
|
|
35969
|
+
if (open) setState(initialFormState(field));
|
|
35970
|
+
}, [open, field]);
|
|
35971
|
+
const setField = (key, value) => setState((prev) => ({
|
|
35972
|
+
...prev,
|
|
35973
|
+
[key]: value
|
|
35974
|
+
}));
|
|
35975
|
+
const handleLabelChange = (value) => {
|
|
35976
|
+
setField("label", value);
|
|
35977
|
+
if (!isEdit) setField("slug", value.toLowerCase().replace(SLUG_INVALID_CHARS_REGEX, "_").replace(SLUG_LEADING_TRAILING_REGEX, ""));
|
|
35978
|
+
};
|
|
35979
|
+
const parsedSelectOptions = state.type === "select" ? state.options.split("\n").map((o) => o.trim()).filter(Boolean) : [];
|
|
35980
|
+
const handleSave = () => {
|
|
35981
|
+
if (!state.label.trim()) return;
|
|
35982
|
+
if (!isEdit && !state.slug.trim()) return;
|
|
35983
|
+
if (state.type === "select" && parsedSelectOptions.length === 0) return;
|
|
35984
|
+
const validation = state.type === "select" ? { options: parsedSelectOptions } : null;
|
|
35985
|
+
if (isEdit) onSave({
|
|
35986
|
+
label: state.label,
|
|
35987
|
+
required: state.required,
|
|
35988
|
+
translatable: field && state.translatable !== field.translatable ? state.translatable : void 0,
|
|
35989
|
+
validation
|
|
35990
|
+
});
|
|
35991
|
+
else onSave({
|
|
35992
|
+
slug: state.slug,
|
|
35993
|
+
label: state.label,
|
|
35994
|
+
type: state.type,
|
|
35995
|
+
required: state.required,
|
|
35996
|
+
translatable: state.translatable,
|
|
35997
|
+
validation
|
|
35998
|
+
});
|
|
35999
|
+
};
|
|
36000
|
+
const translatableLocked = isEdit && usageTotal > 0;
|
|
36001
|
+
const canSave = state.label.trim().length > 0 && (isEdit || state.slug.trim().length > 0) && (state.type !== "select" || parsedSelectOptions.length > 0);
|
|
36002
|
+
return /* @__PURE__ */ jsx(Dialog.Root, {
|
|
36003
|
+
open,
|
|
36004
|
+
onOpenChange,
|
|
36005
|
+
children: /* @__PURE__ */ jsxs(Dialog, {
|
|
36006
|
+
className: "p-6 max-w-xl",
|
|
36007
|
+
size: "lg",
|
|
36008
|
+
children: [
|
|
36009
|
+
/* @__PURE__ */ jsxs("div", {
|
|
36010
|
+
className: "flex items-start justify-between gap-4 mb-4",
|
|
36011
|
+
children: [/* @__PURE__ */ jsx(Dialog.Title, {
|
|
36012
|
+
className: "text-lg font-semibold leading-none tracking-tight",
|
|
36013
|
+
children: isEdit ? _t({
|
|
36014
|
+
id: "hcfOYc",
|
|
36015
|
+
message: "Edit byline field"
|
|
36016
|
+
}) : _t({
|
|
36017
|
+
id: "hT2KP5",
|
|
36018
|
+
message: "New byline field"
|
|
36019
|
+
})
|
|
36020
|
+
}), /* @__PURE__ */ jsx(Dialog.Close, {
|
|
36021
|
+
"aria-label": _t({
|
|
36022
|
+
id: "yz7wBu",
|
|
36023
|
+
message: "Close"
|
|
36024
|
+
}),
|
|
36025
|
+
render: (props) => /* @__PURE__ */ jsxs(Button, {
|
|
36026
|
+
...props,
|
|
36027
|
+
variant: "ghost",
|
|
36028
|
+
shape: "square",
|
|
36029
|
+
"aria-label": _t({
|
|
36030
|
+
id: "yz7wBu",
|
|
36031
|
+
message: "Close"
|
|
36032
|
+
}),
|
|
36033
|
+
className: "absolute end-4 top-4",
|
|
36034
|
+
children: [/* @__PURE__ */ jsx(X, { className: "h-4 w-4" }), /* @__PURE__ */ jsx("span", {
|
|
36035
|
+
className: "sr-only",
|
|
36036
|
+
children: _t({
|
|
36037
|
+
id: "yz7wBu",
|
|
36038
|
+
message: "Close"
|
|
36039
|
+
})
|
|
36040
|
+
})]
|
|
36041
|
+
})
|
|
36042
|
+
})]
|
|
36043
|
+
}),
|
|
36044
|
+
/* @__PURE__ */ jsxs("div", {
|
|
36045
|
+
className: "space-y-5",
|
|
36046
|
+
children: [
|
|
36047
|
+
/* @__PURE__ */ jsxs("div", {
|
|
36048
|
+
className: "grid grid-cols-2 gap-4",
|
|
36049
|
+
children: [/* @__PURE__ */ jsx(Input, {
|
|
36050
|
+
label: _t({
|
|
36051
|
+
id: "87a/t/",
|
|
36052
|
+
message: "Label"
|
|
36053
|
+
}),
|
|
36054
|
+
value: state.label,
|
|
36055
|
+
onChange: (e) => handleLabelChange(e.target.value),
|
|
36056
|
+
placeholder: _t({
|
|
36057
|
+
id: "qMgKul",
|
|
36058
|
+
message: "Job title"
|
|
36059
|
+
})
|
|
36060
|
+
}), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Input, {
|
|
36061
|
+
label: _t({
|
|
36062
|
+
id: "L85WcV",
|
|
36063
|
+
message: "Slug"
|
|
36064
|
+
}),
|
|
36065
|
+
value: state.slug,
|
|
36066
|
+
onChange: (e) => setField("slug", e.target.value),
|
|
36067
|
+
placeholder: _t({
|
|
36068
|
+
id: "gddbae",
|
|
36069
|
+
message: "job_title"
|
|
36070
|
+
}),
|
|
36071
|
+
disabled: isEdit
|
|
36072
|
+
}), isEdit && /* @__PURE__ */ jsx("p", {
|
|
36073
|
+
className: "text-xs text-kumo-subtle mt-2",
|
|
36074
|
+
children: _t({
|
|
36075
|
+
id: "5OXS9U",
|
|
36076
|
+
message: "Slugs cannot be changed after the field is created."
|
|
36077
|
+
})
|
|
36078
|
+
})] })]
|
|
36079
|
+
}),
|
|
36080
|
+
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Select, {
|
|
36081
|
+
label: _t({
|
|
36082
|
+
id: "+zy2Nq",
|
|
36083
|
+
message: "Type"
|
|
36084
|
+
}),
|
|
36085
|
+
value: state.type,
|
|
36086
|
+
onValueChange: (v) => setField("type", v ?? "string"),
|
|
36087
|
+
items: Object.fromEntries(TYPE_OPTIONS.map(({ value, label }) => [value, _t(label)])),
|
|
36088
|
+
disabled: isEdit
|
|
36089
|
+
}), isEdit && /* @__PURE__ */ jsx("p", {
|
|
36090
|
+
className: "text-xs text-kumo-subtle mt-2",
|
|
36091
|
+
children: _t({
|
|
36092
|
+
id: "NF9uuv",
|
|
36093
|
+
message: "Field type cannot be changed after creation."
|
|
36094
|
+
})
|
|
36095
|
+
})] }),
|
|
36096
|
+
/* @__PURE__ */ jsxs("div", {
|
|
36097
|
+
className: "flex flex-wrap items-center gap-6",
|
|
36098
|
+
children: [/* @__PURE__ */ jsx(Switch, {
|
|
36099
|
+
checked: state.required,
|
|
36100
|
+
onCheckedChange: (checked) => setField("required", checked),
|
|
36101
|
+
label: /* @__PURE__ */ jsx("span", {
|
|
36102
|
+
className: "text-sm",
|
|
36103
|
+
children: _t({
|
|
36104
|
+
id: "TMLAx2",
|
|
36105
|
+
message: "Required"
|
|
36106
|
+
})
|
|
36107
|
+
})
|
|
36108
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
36109
|
+
className: "flex flex-col gap-1",
|
|
36110
|
+
children: [/* @__PURE__ */ jsx(Switch, {
|
|
36111
|
+
checked: state.translatable,
|
|
36112
|
+
onCheckedChange: (checked) => setField("translatable", checked),
|
|
36113
|
+
disabled: translatableLocked,
|
|
36114
|
+
label: /* @__PURE__ */ jsx("span", {
|
|
36115
|
+
className: "text-sm",
|
|
36116
|
+
children: _t({
|
|
36117
|
+
id: "O/SdND",
|
|
36118
|
+
message: "Translatable"
|
|
36119
|
+
})
|
|
36120
|
+
})
|
|
36121
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
36122
|
+
className: "text-xs text-kumo-subtle max-w-xs",
|
|
36123
|
+
children: translatableLocked ? _t({
|
|
36124
|
+
id: "3GPtwD",
|
|
36125
|
+
message: "Locked because this field has stored values. Delete the values (or the field) to change this."
|
|
36126
|
+
}) : state.translatable ? _t({
|
|
36127
|
+
id: "ONAVuM",
|
|
36128
|
+
message: "Stored per locale — each translation of a byline gets its own value."
|
|
36129
|
+
}) : _t({
|
|
36130
|
+
id: "msPOkF",
|
|
36131
|
+
message: "Shared across all translations of the same byline."
|
|
36132
|
+
})
|
|
36133
|
+
})]
|
|
36134
|
+
})]
|
|
36135
|
+
}),
|
|
36136
|
+
state.type === "select" && /* @__PURE__ */ jsx(InputArea, {
|
|
36137
|
+
label: _t({
|
|
36138
|
+
id: "okW6+j",
|
|
36139
|
+
message: "Options (one per line)"
|
|
36140
|
+
}),
|
|
36141
|
+
value: state.options,
|
|
36142
|
+
onChange: (e) => setField("options", e.target.value),
|
|
36143
|
+
placeholder: _t({
|
|
36144
|
+
id: "Y9cJqL",
|
|
36145
|
+
message: "Editor\nReporter\nPhotographer"
|
|
36146
|
+
}),
|
|
36147
|
+
rows: 5
|
|
36148
|
+
}),
|
|
36149
|
+
/* @__PURE__ */ jsx(DialogError, {
|
|
36150
|
+
message: getMutationError(error),
|
|
36151
|
+
className: "mt-1"
|
|
36152
|
+
})
|
|
36153
|
+
]
|
|
36154
|
+
}),
|
|
36155
|
+
/* @__PURE__ */ jsxs("div", {
|
|
36156
|
+
className: "mt-6 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
|
36157
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
36158
|
+
variant: "outline",
|
|
36159
|
+
onClick: () => onOpenChange(false),
|
|
36160
|
+
disabled: isSaving,
|
|
36161
|
+
children: _t({
|
|
36162
|
+
id: "dEgA5A",
|
|
36163
|
+
message: "Cancel"
|
|
36164
|
+
})
|
|
36165
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
36166
|
+
onClick: handleSave,
|
|
36167
|
+
disabled: !canSave || isSaving,
|
|
36168
|
+
children: isSaving ? _t({
|
|
36169
|
+
id: "K/F6pa",
|
|
36170
|
+
message: "Saving…"
|
|
36171
|
+
}) : isEdit ? _t({
|
|
36172
|
+
id: "y3aU20",
|
|
36173
|
+
message: "Save changes"
|
|
36174
|
+
}) : _t({
|
|
36175
|
+
id: "A3eIjV",
|
|
36176
|
+
message: "Create field"
|
|
36177
|
+
})
|
|
36178
|
+
})]
|
|
36179
|
+
})
|
|
36180
|
+
]
|
|
36181
|
+
})
|
|
36182
|
+
});
|
|
36183
|
+
}
|
|
36184
|
+
|
|
36185
|
+
//#endregion
|
|
36186
|
+
//#region src/routes/byline-schema.tsx
|
|
36187
|
+
/**
|
|
36188
|
+
* Byline custom-field schema management (Phase 5 of Discussion #1174).
|
|
36189
|
+
*
|
|
36190
|
+
* Admin can create / edit / delete / reorder byline custom-field
|
|
36191
|
+
* definitions. Modelled on the content-type management UX but uses the
|
|
36192
|
+
* purpose-built `BylineFieldEditor` (5-type subset + `translatable`
|
|
36193
|
+
* toggle).
|
|
36194
|
+
*
|
|
36195
|
+
* Reorder uses ↑/↓ buttons per row rather than drag-and-drop — the v1
|
|
36196
|
+
* registry caps the field set at a small number (admins typically
|
|
36197
|
+
* register 2–5 custom fields per site), accessibility is better without
|
|
36198
|
+
* pulling in a DnD dependency, and screen-reader users get usable
|
|
36199
|
+
* keyboard semantics for free. The reorder API takes the full slug list
|
|
36200
|
+
* in the desired order; clicking ↑ swaps the row with its neighbour and
|
|
36201
|
+
* fires the mutation.
|
|
36202
|
+
*
|
|
36203
|
+
* Permission gate: `ROLE_ADMIN`. The route is also gated at the sidebar
|
|
36204
|
+
* level (`minRole: ROLE_ADMIN`), but a manually-typed URL still hits
|
|
36205
|
+
* this page — so the in-component check is the source of truth.
|
|
36206
|
+
*/
|
|
36207
|
+
const ROLE_ADMIN$1 = 50;
|
|
36208
|
+
const QUERY_KEY = ["byline-fields"];
|
|
36209
|
+
function BylineSchemaPage() {
|
|
36210
|
+
const { _: _t } = useLingui();
|
|
36211
|
+
const queryClient = useQueryClient();
|
|
36212
|
+
const toastManager = Toast.useToastManager();
|
|
36213
|
+
const { data: currentUser, isLoading: userLoading } = useCurrentUser();
|
|
36214
|
+
const [editingField, setEditingField] = React$1.useState(null);
|
|
36215
|
+
const [creating, setCreating] = React$1.useState(false);
|
|
36216
|
+
const [deletingField, setDeletingField] = React$1.useState(null);
|
|
36217
|
+
const fieldsQuery = useQuery({
|
|
36218
|
+
queryKey: QUERY_KEY,
|
|
36219
|
+
queryFn: listBylineFields,
|
|
36220
|
+
enabled: !!currentUser && currentUser.role >= ROLE_ADMIN$1
|
|
36221
|
+
});
|
|
36222
|
+
const usageQuery = useQuery({
|
|
36223
|
+
queryKey: [
|
|
36224
|
+
"byline-schema",
|
|
36225
|
+
"usage",
|
|
36226
|
+
editingField?.slug ?? null
|
|
36227
|
+
],
|
|
36228
|
+
queryFn: () => editingField ? getBylineFieldUsage(editingField.slug) : Promise.resolve(null),
|
|
36229
|
+
enabled: !!editingField
|
|
36230
|
+
});
|
|
36231
|
+
const deleteUsageQuery = useQuery({
|
|
36232
|
+
queryKey: [
|
|
36233
|
+
"byline-schema",
|
|
36234
|
+
"usage",
|
|
36235
|
+
deletingField?.slug ?? null
|
|
36236
|
+
],
|
|
36237
|
+
queryFn: () => deletingField ? getBylineFieldUsage(deletingField.slug) : Promise.resolve(null),
|
|
36238
|
+
enabled: !!deletingField
|
|
36239
|
+
});
|
|
36240
|
+
const createMutation = useMutation({
|
|
36241
|
+
mutationFn: (input) => createBylineField(input),
|
|
36242
|
+
onSuccess: (field) => {
|
|
36243
|
+
queryClient.invalidateQueries({ queryKey: QUERY_KEY });
|
|
36244
|
+
toastManager.add({
|
|
36245
|
+
title: _t({
|
|
36246
|
+
id: "5fyK5P",
|
|
36247
|
+
message: "Field created"
|
|
36248
|
+
}),
|
|
36249
|
+
description: _t({
|
|
36250
|
+
id: "CmHSIV",
|
|
36251
|
+
message: "Created \"{0}\".",
|
|
36252
|
+
values: { 0: field.label }
|
|
36253
|
+
})
|
|
36254
|
+
});
|
|
36255
|
+
setCreating(false);
|
|
36256
|
+
},
|
|
36257
|
+
onError: (mutationError) => {
|
|
36258
|
+
toastManager.add({
|
|
36259
|
+
title: _t({
|
|
36260
|
+
id: "WWQD//",
|
|
36261
|
+
message: "Failed to create field"
|
|
36262
|
+
}),
|
|
36263
|
+
description: mutationError instanceof Error ? mutationError.message : _t({
|
|
36264
|
+
id: "Vw8l6h",
|
|
36265
|
+
message: "An error occurred"
|
|
36266
|
+
}),
|
|
36267
|
+
type: "error"
|
|
36268
|
+
});
|
|
36269
|
+
}
|
|
36270
|
+
});
|
|
36271
|
+
const updateMutation = useMutation({
|
|
36272
|
+
mutationFn: (vars) => updateBylineField(vars.slug, vars.input),
|
|
36273
|
+
onSuccess: (field) => {
|
|
36274
|
+
queryClient.invalidateQueries({ queryKey: QUERY_KEY });
|
|
36275
|
+
toastManager.add({
|
|
36276
|
+
title: _t({
|
|
36277
|
+
id: "oaEgX0",
|
|
36278
|
+
message: "Field updated"
|
|
36279
|
+
}),
|
|
36280
|
+
description: _t({
|
|
36281
|
+
id: "m64wqx",
|
|
36282
|
+
message: "Saved \"{0}\".",
|
|
36283
|
+
values: { 0: field.label }
|
|
36284
|
+
})
|
|
36285
|
+
});
|
|
36286
|
+
setEditingField(null);
|
|
36287
|
+
},
|
|
36288
|
+
onError: (mutationError) => {
|
|
36289
|
+
toastManager.add({
|
|
36290
|
+
title: _t({
|
|
36291
|
+
id: "nfiPO9",
|
|
36292
|
+
message: "Failed to save field"
|
|
36293
|
+
}),
|
|
36294
|
+
description: mutationError instanceof Error ? mutationError.message : _t({
|
|
36295
|
+
id: "Vw8l6h",
|
|
36296
|
+
message: "An error occurred"
|
|
36297
|
+
}),
|
|
36298
|
+
type: "error"
|
|
36299
|
+
});
|
|
36300
|
+
}
|
|
36301
|
+
});
|
|
36302
|
+
const deleteMutation = useMutation({
|
|
36303
|
+
mutationFn: (slug) => deleteBylineField(slug),
|
|
36304
|
+
onSuccess: () => {
|
|
36305
|
+
queryClient.invalidateQueries({ queryKey: QUERY_KEY });
|
|
36306
|
+
toastManager.add({ title: _t({
|
|
36307
|
+
id: "LX8Pu1",
|
|
36308
|
+
message: "Field deleted"
|
|
36309
|
+
}) });
|
|
36310
|
+
setDeletingField(null);
|
|
36311
|
+
},
|
|
36312
|
+
onError: (mutationError) => {
|
|
36313
|
+
toastManager.add({
|
|
36314
|
+
title: _t({
|
|
36315
|
+
id: "e0xtVp",
|
|
36316
|
+
message: "Failed to delete field"
|
|
36317
|
+
}),
|
|
36318
|
+
description: mutationError instanceof Error ? mutationError.message : _t({
|
|
36319
|
+
id: "Vw8l6h",
|
|
36320
|
+
message: "An error occurred"
|
|
36321
|
+
}),
|
|
36322
|
+
type: "error"
|
|
36323
|
+
});
|
|
36324
|
+
}
|
|
36325
|
+
});
|
|
36326
|
+
const reorderMutation = useMutation({
|
|
36327
|
+
mutationFn: (slugs) => reorderBylineFields(slugs),
|
|
36328
|
+
onSuccess: () => {
|
|
36329
|
+
queryClient.invalidateQueries({ queryKey: QUERY_KEY });
|
|
36330
|
+
},
|
|
36331
|
+
onError: (mutationError) => {
|
|
36332
|
+
toastManager.add({
|
|
36333
|
+
title: _t({
|
|
36334
|
+
id: "eGNX2z",
|
|
36335
|
+
message: "Failed to reorder fields"
|
|
36336
|
+
}),
|
|
36337
|
+
description: mutationError instanceof Error ? mutationError.message : _t({
|
|
36338
|
+
id: "Vw8l6h",
|
|
36339
|
+
message: "An error occurred"
|
|
36340
|
+
}),
|
|
36341
|
+
type: "error"
|
|
36342
|
+
});
|
|
36343
|
+
}
|
|
36344
|
+
});
|
|
36345
|
+
if (userLoading) return /* @__PURE__ */ jsx("div", {
|
|
36346
|
+
className: "flex items-center justify-center min-h-[50vh]",
|
|
36347
|
+
children: /* @__PURE__ */ jsx(Loader, {})
|
|
36348
|
+
});
|
|
36349
|
+
if (!currentUser || currentUser.role < ROLE_ADMIN$1) return /* @__PURE__ */ jsx("div", {
|
|
36350
|
+
className: "flex items-center justify-center min-h-[50vh]",
|
|
36351
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
36352
|
+
className: "text-center",
|
|
36353
|
+
children: [/* @__PURE__ */ jsx("h1", {
|
|
36354
|
+
className: "text-2xl font-bold",
|
|
36355
|
+
children: _t({
|
|
36356
|
+
id: "S2Rr8h",
|
|
36357
|
+
message: "Access denied"
|
|
36358
|
+
})
|
|
36359
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
36360
|
+
className: "mt-2 text-kumo-subtle",
|
|
36361
|
+
children: _t({
|
|
36362
|
+
id: "MKw3nY",
|
|
36363
|
+
message: "You need admin permissions to manage byline schema."
|
|
36364
|
+
})
|
|
36365
|
+
})]
|
|
36366
|
+
})
|
|
36367
|
+
});
|
|
36368
|
+
const fields = fieldsQuery.data?.items ?? [];
|
|
36369
|
+
const handleMove = (index, delta) => {
|
|
36370
|
+
const targetIndex = index + delta;
|
|
36371
|
+
if (targetIndex < 0 || targetIndex >= fields.length) return;
|
|
36372
|
+
const next = [...fields];
|
|
36373
|
+
const a = next[index];
|
|
36374
|
+
const b = next[targetIndex];
|
|
36375
|
+
if (!a || !b) return;
|
|
36376
|
+
next[index] = b;
|
|
36377
|
+
next[targetIndex] = a;
|
|
36378
|
+
reorderMutation.mutate(next.map((f) => f.slug));
|
|
36379
|
+
};
|
|
36380
|
+
const handleCreate = (input) => {
|
|
36381
|
+
if ("slug" in input && "type" in input) createMutation.mutate(input);
|
|
36382
|
+
};
|
|
36383
|
+
const handleEdit = (input) => {
|
|
36384
|
+
if (!editingField) return;
|
|
36385
|
+
if (!("slug" in input) && !("type" in input)) updateMutation.mutate({
|
|
36386
|
+
slug: editingField.slug,
|
|
36387
|
+
input
|
|
36388
|
+
});
|
|
36389
|
+
};
|
|
36390
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
36391
|
+
className: "space-y-4",
|
|
36392
|
+
children: [
|
|
36393
|
+
/* @__PURE__ */ jsxs("div", {
|
|
36394
|
+
className: "flex items-center justify-between",
|
|
36395
|
+
children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h1", {
|
|
36396
|
+
className: "text-2xl font-bold",
|
|
36397
|
+
children: _t({
|
|
36398
|
+
id: "5YHQju",
|
|
36399
|
+
message: "Byline schema"
|
|
36400
|
+
})
|
|
36401
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
36402
|
+
className: "text-kumo-subtle text-sm",
|
|
36403
|
+
children: _t({
|
|
36404
|
+
id: "yYyG5z",
|
|
36405
|
+
message: "Define custom fields stored on every byline — job title, pronouns, social handles, and more."
|
|
36406
|
+
})
|
|
36407
|
+
})] }), /* @__PURE__ */ jsx(Button, {
|
|
36408
|
+
icon: /* @__PURE__ */ jsx(Plus, {}),
|
|
36409
|
+
onClick: () => setCreating(true),
|
|
36410
|
+
children: _t({
|
|
36411
|
+
id: "kV1LSq",
|
|
36412
|
+
message: "New field"
|
|
36413
|
+
})
|
|
36414
|
+
})]
|
|
36415
|
+
}),
|
|
36416
|
+
/* @__PURE__ */ jsx("div", {
|
|
36417
|
+
className: "rounded-md border bg-kumo-base overflow-x-auto",
|
|
36418
|
+
children: /* @__PURE__ */ jsxs("table", {
|
|
36419
|
+
className: "w-full",
|
|
36420
|
+
children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", {
|
|
36421
|
+
className: "border-b bg-kumo-tint/50",
|
|
36422
|
+
children: [
|
|
36423
|
+
/* @__PURE__ */ jsx("th", {
|
|
36424
|
+
scope: "col",
|
|
36425
|
+
className: "px-4 py-3 text-start text-sm font-medium",
|
|
36426
|
+
children: _t({
|
|
36427
|
+
id: "87a/t/",
|
|
36428
|
+
message: "Label"
|
|
36429
|
+
})
|
|
36430
|
+
}),
|
|
36431
|
+
/* @__PURE__ */ jsx("th", {
|
|
36432
|
+
scope: "col",
|
|
36433
|
+
className: "px-4 py-3 text-start text-sm font-medium",
|
|
36434
|
+
children: _t({
|
|
36435
|
+
id: "L85WcV",
|
|
36436
|
+
message: "Slug"
|
|
36437
|
+
})
|
|
36438
|
+
}),
|
|
36439
|
+
/* @__PURE__ */ jsx("th", {
|
|
36440
|
+
scope: "col",
|
|
36441
|
+
className: "px-4 py-3 text-start text-sm font-medium",
|
|
36442
|
+
children: _t({
|
|
36443
|
+
id: "+zy2Nq",
|
|
36444
|
+
message: "Type"
|
|
36445
|
+
})
|
|
36446
|
+
}),
|
|
36447
|
+
/* @__PURE__ */ jsx("th", {
|
|
36448
|
+
scope: "col",
|
|
36449
|
+
className: "px-4 py-3 text-start text-sm font-medium",
|
|
36450
|
+
children: _t({
|
|
36451
|
+
id: "O/SdND",
|
|
36452
|
+
message: "Translatable"
|
|
36453
|
+
})
|
|
36454
|
+
}),
|
|
36455
|
+
/* @__PURE__ */ jsx("th", {
|
|
36456
|
+
scope: "col",
|
|
36457
|
+
className: "px-4 py-3 text-start text-sm font-medium",
|
|
36458
|
+
children: _t({
|
|
36459
|
+
id: "TMLAx2",
|
|
36460
|
+
message: "Required"
|
|
36461
|
+
})
|
|
36462
|
+
}),
|
|
36463
|
+
/* @__PURE__ */ jsx("th", {
|
|
36464
|
+
scope: "col",
|
|
36465
|
+
className: "px-4 py-3 text-end text-sm font-medium",
|
|
36466
|
+
children: _t({
|
|
36467
|
+
id: "7L01XJ",
|
|
36468
|
+
message: "Actions"
|
|
36469
|
+
})
|
|
36470
|
+
})
|
|
36471
|
+
]
|
|
36472
|
+
}) }), /* @__PURE__ */ jsx("tbody", {
|
|
36473
|
+
className: "divide-y divide-kumo-line",
|
|
36474
|
+
children: fieldsQuery.isLoading ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", {
|
|
36475
|
+
colSpan: 6,
|
|
36476
|
+
className: "px-4 py-8 text-center text-kumo-subtle",
|
|
36477
|
+
children: _t({
|
|
36478
|
+
id: "IMm99K",
|
|
36479
|
+
message: "Loading byline fields…"
|
|
36480
|
+
})
|
|
36481
|
+
}) }) : fieldsQuery.isError ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsxs("td", {
|
|
36482
|
+
colSpan: 6,
|
|
36483
|
+
className: "px-4 py-8 text-center",
|
|
36484
|
+
children: [
|
|
36485
|
+
/* @__PURE__ */ jsx("p", {
|
|
36486
|
+
className: "text-kumo-danger font-medium",
|
|
36487
|
+
children: _t({
|
|
36488
|
+
id: "ORkiLh",
|
|
36489
|
+
message: "Couldn't load byline fields."
|
|
36490
|
+
})
|
|
36491
|
+
}),
|
|
36492
|
+
/* @__PURE__ */ jsx("p", {
|
|
36493
|
+
className: "text-sm text-kumo-subtle mt-1",
|
|
36494
|
+
children: fieldsQuery.error instanceof Error ? fieldsQuery.error.message : _t({
|
|
36495
|
+
id: "W5A0Ly",
|
|
36496
|
+
message: "An unexpected error occurred."
|
|
36497
|
+
})
|
|
36498
|
+
}),
|
|
36499
|
+
/* @__PURE__ */ jsx(Button, {
|
|
36500
|
+
variant: "outline",
|
|
36501
|
+
size: "sm",
|
|
36502
|
+
className: "mt-3",
|
|
36503
|
+
onClick: () => void fieldsQuery.refetch(),
|
|
36504
|
+
children: _t({
|
|
36505
|
+
id: "6gRgw8",
|
|
36506
|
+
message: "Retry"
|
|
36507
|
+
})
|
|
36508
|
+
})
|
|
36509
|
+
]
|
|
36510
|
+
}) }) : fields.length === 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsxs("td", {
|
|
36511
|
+
colSpan: 6,
|
|
36512
|
+
className: "px-4 py-12 text-center text-kumo-subtle",
|
|
36513
|
+
children: [
|
|
36514
|
+
/* @__PURE__ */ jsx(IdentificationCard, {
|
|
36515
|
+
className: "mx-auto h-8 w-8 mb-3 text-kumo-subtle",
|
|
36516
|
+
"aria-hidden": "true"
|
|
36517
|
+
}),
|
|
36518
|
+
/* @__PURE__ */ jsx("p", { children: _t({
|
|
36519
|
+
id: "7XUltI",
|
|
36520
|
+
message: "No byline fields yet."
|
|
36521
|
+
}) }),
|
|
36522
|
+
/* @__PURE__ */ jsx("p", {
|
|
36523
|
+
className: "text-xs mt-1",
|
|
36524
|
+
children: _t({
|
|
36525
|
+
id: "USA0K0",
|
|
36526
|
+
message: "Add fields like \"Job title\" or \"Pronouns\" to enrich every byline."
|
|
36527
|
+
})
|
|
36528
|
+
})
|
|
36529
|
+
]
|
|
36530
|
+
}) }) : fields.map((field, index) => /* @__PURE__ */ jsx(FieldRow, {
|
|
36531
|
+
field,
|
|
36532
|
+
isFirst: index === 0,
|
|
36533
|
+
isLast: index === fields.length - 1,
|
|
36534
|
+
onMoveUp: () => handleMove(index, -1),
|
|
36535
|
+
onMoveDown: () => handleMove(index, 1),
|
|
36536
|
+
onEdit: () => setEditingField(field),
|
|
36537
|
+
onDelete: () => setDeletingField(field),
|
|
36538
|
+
reorderPending: reorderMutation.isPending
|
|
36539
|
+
}, field.id))
|
|
36540
|
+
})]
|
|
36541
|
+
})
|
|
36542
|
+
}),
|
|
36543
|
+
/* @__PURE__ */ jsx(BylineFieldEditor, {
|
|
36544
|
+
open: creating,
|
|
36545
|
+
onOpenChange: (open) => {
|
|
36546
|
+
if (!open) {
|
|
36547
|
+
setCreating(false);
|
|
36548
|
+
createMutation.reset();
|
|
36549
|
+
}
|
|
36550
|
+
},
|
|
36551
|
+
onSave: handleCreate,
|
|
36552
|
+
isSaving: createMutation.isPending,
|
|
36553
|
+
error: createMutation.error
|
|
36554
|
+
}),
|
|
36555
|
+
editingField && /* @__PURE__ */ jsx(BylineFieldEditor, {
|
|
36556
|
+
open: !!editingField,
|
|
36557
|
+
onOpenChange: (open) => {
|
|
36558
|
+
if (!open) {
|
|
36559
|
+
setEditingField(null);
|
|
36560
|
+
updateMutation.reset();
|
|
36561
|
+
}
|
|
36562
|
+
},
|
|
36563
|
+
field: editingField,
|
|
36564
|
+
usageTotal: usageQuery.data?.totalAffectedRows ?? 0,
|
|
36565
|
+
onSave: handleEdit,
|
|
36566
|
+
isSaving: updateMutation.isPending,
|
|
36567
|
+
error: updateMutation.error
|
|
36568
|
+
}, editingField.id),
|
|
36569
|
+
/* @__PURE__ */ jsx(ConfirmDialog, {
|
|
36570
|
+
open: !!deletingField,
|
|
36571
|
+
onClose: () => {
|
|
36572
|
+
setDeletingField(null);
|
|
36573
|
+
deleteMutation.reset();
|
|
36574
|
+
},
|
|
36575
|
+
title: _t({
|
|
36576
|
+
id: "S353b7",
|
|
36577
|
+
message: "Delete byline field?"
|
|
36578
|
+
}),
|
|
36579
|
+
description: deletingField ? deleteUsageQuery.isLoading ? _t({
|
|
36580
|
+
id: "WeKBmZ",
|
|
36581
|
+
message: "Checking how many stored values reference \"{0}\"…",
|
|
36582
|
+
values: { 0: deletingField.label }
|
|
36583
|
+
}) : deleteUsageQuery.isError ? _t({
|
|
36584
|
+
id: "AsyqqW",
|
|
36585
|
+
message: "Couldn't verify how many values reference \"{0}\". Deleting will still remove every stored value for this field — but the count above could not be checked.",
|
|
36586
|
+
values: { 0: deletingField.label }
|
|
36587
|
+
}) : deleteUsageQuery.data && deleteUsageQuery.data.totalAffectedRows > 0 ? _t({
|
|
36588
|
+
id: "YjS/z6",
|
|
36589
|
+
message: "Deleting \"{0}\" will also remove {1, plural, one {# stored value} other {# stored values}} across all bylines. This cannot be undone.",
|
|
36590
|
+
values: {
|
|
36591
|
+
0: deletingField.label,
|
|
36592
|
+
1: deleteUsageQuery.data.totalAffectedRows
|
|
36593
|
+
}
|
|
36594
|
+
}) : _t({
|
|
36595
|
+
id: "27Gnm/",
|
|
36596
|
+
message: "Are you sure you want to delete \"{0}\"? No stored values reference this field.",
|
|
36597
|
+
values: { 0: deletingField.label }
|
|
36598
|
+
}) : "",
|
|
36599
|
+
confirmLabel: _t({
|
|
36600
|
+
id: "cnGeoo",
|
|
36601
|
+
message: "Delete"
|
|
36602
|
+
}),
|
|
36603
|
+
pendingLabel: _t({
|
|
36604
|
+
id: "gXQX4x",
|
|
36605
|
+
message: "Deleting…"
|
|
36606
|
+
}),
|
|
36607
|
+
isPending: deleteMutation.isPending,
|
|
36608
|
+
error: deleteMutation.error,
|
|
36609
|
+
onConfirm: () => {
|
|
36610
|
+
if (deletingField) deleteMutation.mutate(deletingField.slug);
|
|
36611
|
+
}
|
|
36612
|
+
})
|
|
36613
|
+
]
|
|
36614
|
+
});
|
|
36615
|
+
}
|
|
36616
|
+
function FieldRow({ field, isFirst, isLast, onMoveUp, onMoveDown, onEdit, onDelete, reorderPending }) {
|
|
36617
|
+
const { _: _t2 } = useLingui();
|
|
36618
|
+
return /* @__PURE__ */ jsxs("tr", {
|
|
36619
|
+
className: "hover:bg-kumo-tint/25",
|
|
36620
|
+
children: [
|
|
36621
|
+
/* @__PURE__ */ jsx("td", {
|
|
36622
|
+
className: "px-4 py-3 font-medium",
|
|
36623
|
+
children: field.label
|
|
36624
|
+
}),
|
|
36625
|
+
/* @__PURE__ */ jsx("td", {
|
|
36626
|
+
className: "px-4 py-3",
|
|
36627
|
+
children: /* @__PURE__ */ jsx("code", {
|
|
36628
|
+
className: "text-sm bg-kumo-tint px-1.5 py-0.5 rounded",
|
|
36629
|
+
children: field.slug
|
|
36630
|
+
})
|
|
36631
|
+
}),
|
|
36632
|
+
/* @__PURE__ */ jsx("td", {
|
|
36633
|
+
className: "px-4 py-3 text-sm",
|
|
36634
|
+
children: field.type
|
|
36635
|
+
}),
|
|
36636
|
+
/* @__PURE__ */ jsx("td", {
|
|
36637
|
+
className: "px-4 py-3 text-sm",
|
|
36638
|
+
children: field.translatable ? _t2({
|
|
36639
|
+
id: "l75CjT",
|
|
36640
|
+
message: "Yes"
|
|
36641
|
+
}) : _t2({
|
|
36642
|
+
id: "Vqmggp",
|
|
36643
|
+
message: "No (shared across translations)"
|
|
36644
|
+
})
|
|
36645
|
+
}),
|
|
36646
|
+
/* @__PURE__ */ jsx("td", {
|
|
36647
|
+
className: "px-4 py-3 text-sm",
|
|
36648
|
+
children: field.required ? _t2({
|
|
36649
|
+
id: "l75CjT",
|
|
36650
|
+
message: "Yes"
|
|
36651
|
+
}) : _t2({
|
|
36652
|
+
id: "1UzENP",
|
|
36653
|
+
message: "No"
|
|
36654
|
+
})
|
|
36655
|
+
}),
|
|
36656
|
+
/* @__PURE__ */ jsx("td", {
|
|
36657
|
+
className: "px-4 py-3 text-end",
|
|
36658
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
36659
|
+
className: "flex items-center justify-end gap-1",
|
|
36660
|
+
children: [
|
|
36661
|
+
/* @__PURE__ */ jsx(Button, {
|
|
36662
|
+
variant: "ghost",
|
|
36663
|
+
shape: "square",
|
|
36664
|
+
size: "sm",
|
|
36665
|
+
"aria-label": _t2({
|
|
36666
|
+
id: "ko+NNU",
|
|
36667
|
+
message: "Move \"{0}\" up",
|
|
36668
|
+
values: { 0: field.label }
|
|
36669
|
+
}),
|
|
36670
|
+
disabled: isFirst || reorderPending,
|
|
36671
|
+
onClick: onMoveUp,
|
|
36672
|
+
children: /* @__PURE__ */ jsx(ArrowUp, {
|
|
36673
|
+
className: "h-4 w-4",
|
|
36674
|
+
"aria-hidden": "true"
|
|
36675
|
+
})
|
|
36676
|
+
}),
|
|
36677
|
+
/* @__PURE__ */ jsx(Button, {
|
|
36678
|
+
variant: "ghost",
|
|
36679
|
+
shape: "square",
|
|
36680
|
+
size: "sm",
|
|
36681
|
+
"aria-label": _t2({
|
|
36682
|
+
id: "Q23Rqr",
|
|
36683
|
+
message: "Move \"{0}\" down",
|
|
36684
|
+
values: { 0: field.label }
|
|
36685
|
+
}),
|
|
36686
|
+
disabled: isLast || reorderPending,
|
|
36687
|
+
onClick: onMoveDown,
|
|
36688
|
+
children: /* @__PURE__ */ jsx(ArrowDown, {
|
|
36689
|
+
className: "h-4 w-4",
|
|
36690
|
+
"aria-hidden": "true"
|
|
36691
|
+
})
|
|
36692
|
+
}),
|
|
36693
|
+
/* @__PURE__ */ jsx(Button, {
|
|
36694
|
+
variant: "ghost",
|
|
36695
|
+
shape: "square",
|
|
36696
|
+
size: "sm",
|
|
36697
|
+
"aria-label": _t2({
|
|
36698
|
+
id: "N6j2JH",
|
|
36699
|
+
message: "Edit {0}",
|
|
36700
|
+
values: { 0: field.label }
|
|
36701
|
+
}),
|
|
36702
|
+
onClick: onEdit,
|
|
36703
|
+
children: /* @__PURE__ */ jsx(Pencil, {
|
|
36704
|
+
className: "h-4 w-4",
|
|
36705
|
+
"aria-hidden": "true"
|
|
36706
|
+
})
|
|
36707
|
+
}),
|
|
36708
|
+
/* @__PURE__ */ jsx(Button, {
|
|
36709
|
+
variant: "ghost",
|
|
36710
|
+
shape: "square",
|
|
36711
|
+
size: "sm",
|
|
36712
|
+
"aria-label": _t2({
|
|
36713
|
+
id: "Y2tU6I",
|
|
36714
|
+
message: "Delete {0}",
|
|
36715
|
+
values: { 0: field.label }
|
|
36716
|
+
}),
|
|
36717
|
+
onClick: onDelete,
|
|
36718
|
+
children: /* @__PURE__ */ jsx(Trash, {
|
|
36719
|
+
className: "h-4 w-4 text-kumo-danger",
|
|
36720
|
+
"aria-hidden": "true"
|
|
36721
|
+
})
|
|
36722
|
+
})
|
|
36723
|
+
]
|
|
36724
|
+
})
|
|
36725
|
+
})
|
|
36726
|
+
]
|
|
36727
|
+
});
|
|
36728
|
+
}
|
|
36729
|
+
|
|
35291
36730
|
//#endregion
|
|
35292
36731
|
//#region src/routes/bylines.tsx
|
|
35293
36732
|
/**
|
|
@@ -35304,7 +36743,8 @@ function toFormState(byline) {
|
|
|
35304
36743
|
bio: "",
|
|
35305
36744
|
websiteUrl: "",
|
|
35306
36745
|
userId: null,
|
|
35307
|
-
isGuest: false
|
|
36746
|
+
isGuest: false,
|
|
36747
|
+
customFields: {}
|
|
35308
36748
|
};
|
|
35309
36749
|
return {
|
|
35310
36750
|
slug: byline.slug,
|
|
@@ -35312,7 +36752,8 @@ function toFormState(byline) {
|
|
|
35312
36752
|
bio: byline.bio ?? "",
|
|
35313
36753
|
websiteUrl: byline.websiteUrl ?? "",
|
|
35314
36754
|
userId: byline.userId,
|
|
35315
|
-
isGuest: byline.isGuest
|
|
36755
|
+
isGuest: byline.isGuest,
|
|
36756
|
+
customFields: byline.customFields ?? {}
|
|
35316
36757
|
};
|
|
35317
36758
|
}
|
|
35318
36759
|
function getUserLabel(user) {
|
|
@@ -35325,6 +36766,7 @@ function BylinesPage() {
|
|
|
35325
36766
|
const navigate = useNavigate$1();
|
|
35326
36767
|
const { locale: routeLocale } = useSearch({ from: "/_admin/bylines" });
|
|
35327
36768
|
const [search, setSearch] = React$1.useState("");
|
|
36769
|
+
const debouncedSearch = useDebouncedValue$1(search, 300);
|
|
35328
36770
|
const [guestFilter, setGuestFilter] = React$1.useState("all");
|
|
35329
36771
|
const [selectedId, setSelectedId] = React$1.useState(null);
|
|
35330
36772
|
const [showDeleteConfirm, setShowDeleteConfirm] = React$1.useState(false);
|
|
@@ -35336,6 +36778,8 @@ function BylinesPage() {
|
|
|
35336
36778
|
});
|
|
35337
36779
|
const i18n = useI18nConfig(manifest);
|
|
35338
36780
|
const isMultiLocale = !!i18n && i18n.locales.length > 1;
|
|
36781
|
+
const { data: currentUser } = useCurrentUser();
|
|
36782
|
+
const canManageBylineSchema = (currentUser?.role ?? 0) >= BYLINE_SCHEMA_NAV_ITEM.minRole;
|
|
35339
36783
|
const activeLocale = routeLocale ?? i18n?.defaultLocale ?? void 0;
|
|
35340
36784
|
const handleLocaleChange = (locale) => {
|
|
35341
36785
|
navigate({
|
|
@@ -35347,12 +36791,12 @@ function BylinesPage() {
|
|
|
35347
36791
|
const { data, isLoading, error } = useQuery({
|
|
35348
36792
|
queryKey: [
|
|
35349
36793
|
"bylines",
|
|
35350
|
-
|
|
36794
|
+
debouncedSearch,
|
|
35351
36795
|
guestFilter,
|
|
35352
36796
|
activeLocale ?? null
|
|
35353
36797
|
],
|
|
35354
36798
|
queryFn: () => fetchBylines({
|
|
35355
|
-
search:
|
|
36799
|
+
search: debouncedSearch || void 0,
|
|
35356
36800
|
isGuest: guestFilter === "all" ? void 0 : guestFilter === "guest",
|
|
35357
36801
|
locale: activeLocale,
|
|
35358
36802
|
limit: 50
|
|
@@ -35369,6 +36813,12 @@ function BylinesPage() {
|
|
|
35369
36813
|
queryFn: () => fetchUsers({ limit: 100 })
|
|
35370
36814
|
});
|
|
35371
36815
|
const users = usersData?.items ?? [];
|
|
36816
|
+
const { data: customFieldsList, error: customFieldsError } = useQuery({
|
|
36817
|
+
queryKey: ["byline-fields"],
|
|
36818
|
+
queryFn: listBylineFields,
|
|
36819
|
+
staleTime: 60 * 1e3
|
|
36820
|
+
});
|
|
36821
|
+
const customFieldDefs = customFieldsList?.items ?? [];
|
|
35372
36822
|
const loadMoreMutation = useMutation({
|
|
35373
36823
|
mutationFn: async (snapshot) => {
|
|
35374
36824
|
return {
|
|
@@ -35384,7 +36834,7 @@ function BylinesPage() {
|
|
|
35384
36834
|
},
|
|
35385
36835
|
onSuccess: ({ result, snapshot }) => {
|
|
35386
36836
|
if (!loadMoreSnapshotMatches(snapshot, {
|
|
35387
|
-
search,
|
|
36837
|
+
search: debouncedSearch,
|
|
35388
36838
|
guestFilter,
|
|
35389
36839
|
locale: activeLocale
|
|
35390
36840
|
})) return;
|
|
@@ -35409,31 +36859,38 @@ function BylinesPage() {
|
|
|
35409
36859
|
enabled: !!selectedId && isMultiLocale
|
|
35410
36860
|
});
|
|
35411
36861
|
const createMutation = useMutation({
|
|
35412
|
-
mutationFn: () =>
|
|
35413
|
-
|
|
35414
|
-
|
|
35415
|
-
|
|
35416
|
-
|
|
35417
|
-
|
|
35418
|
-
|
|
35419
|
-
|
|
35420
|
-
|
|
36862
|
+
mutationFn: () => {
|
|
36863
|
+
const body = {
|
|
36864
|
+
slug: form.slug,
|
|
36865
|
+
displayName: form.displayName,
|
|
36866
|
+
bio: form.bio || null,
|
|
36867
|
+
websiteUrl: form.websiteUrl || null,
|
|
36868
|
+
userId: form.userId,
|
|
36869
|
+
isGuest: form.isGuest,
|
|
36870
|
+
locale: activeLocale
|
|
36871
|
+
};
|
|
36872
|
+
if (!customFieldsError && Object.keys(form.customFields).length > 0) body.customFields = form.customFields;
|
|
36873
|
+
return createByline(body);
|
|
36874
|
+
},
|
|
35421
36875
|
onSuccess: (created) => {
|
|
35422
36876
|
queryClient.invalidateQueries({ queryKey: ["bylines"] });
|
|
36877
|
+
queryClient.invalidateQueries({ queryKey: ["byline", created.id] });
|
|
35423
36878
|
setSelectedId(created.id);
|
|
35424
36879
|
}
|
|
35425
36880
|
});
|
|
35426
36881
|
const updateMutation = useMutation({
|
|
35427
36882
|
mutationFn: () => {
|
|
35428
36883
|
if (!selectedId) throw new Error("No byline selected");
|
|
35429
|
-
|
|
36884
|
+
const body = {
|
|
35430
36885
|
slug: form.slug,
|
|
35431
36886
|
displayName: form.displayName,
|
|
35432
36887
|
bio: form.bio || null,
|
|
35433
36888
|
websiteUrl: form.websiteUrl || null,
|
|
35434
36889
|
userId: form.userId,
|
|
35435
36890
|
isGuest: form.isGuest
|
|
35436
|
-
}
|
|
36891
|
+
};
|
|
36892
|
+
if (!customFieldsError) body.customFields = form.customFields;
|
|
36893
|
+
return updateByline(selectedId, body);
|
|
35437
36894
|
},
|
|
35438
36895
|
onSuccess: () => {
|
|
35439
36896
|
queryClient.invalidateQueries({ queryKey: ["bylines"] });
|
|
@@ -35471,7 +36928,7 @@ function BylinesPage() {
|
|
|
35471
36928
|
setSelectedId(created.id);
|
|
35472
36929
|
}
|
|
35473
36930
|
});
|
|
35474
|
-
if (isLoading) return /* @__PURE__ */ jsx("div", {
|
|
36931
|
+
if (isLoading && !data) return /* @__PURE__ */ jsx("div", {
|
|
35475
36932
|
className: "flex items-center justify-center min-h-[30vh]",
|
|
35476
36933
|
children: /* @__PURE__ */ jsx(Loader, {})
|
|
35477
36934
|
});
|
|
@@ -35488,21 +36945,32 @@ function BylinesPage() {
|
|
|
35488
36945
|
return /* @__PURE__ */ jsxs("div", {
|
|
35489
36946
|
className: "space-y-4",
|
|
35490
36947
|
children: [
|
|
35491
|
-
|
|
35492
|
-
className: "flex items-center justify-between",
|
|
35493
|
-
children: [/* @__PURE__ */ jsx("
|
|
36948
|
+
/* @__PURE__ */ jsxs("div", {
|
|
36949
|
+
className: "flex items-center justify-between gap-3",
|
|
36950
|
+
children: [/* @__PURE__ */ jsx("h1", {
|
|
35494
36951
|
className: "text-2xl font-semibold",
|
|
35495
36952
|
children: _t({
|
|
35496
36953
|
id: "xLGa7J",
|
|
35497
36954
|
message: "Bylines"
|
|
35498
36955
|
})
|
|
35499
|
-
})
|
|
35500
|
-
|
|
35501
|
-
|
|
35502
|
-
|
|
35503
|
-
|
|
36956
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
36957
|
+
className: "flex items-center gap-2",
|
|
36958
|
+
children: [canManageBylineSchema && /* @__PURE__ */ jsx(RouterLinkButton, {
|
|
36959
|
+
to: BYLINE_SCHEMA_NAV_ITEM.to,
|
|
36960
|
+
variant: "secondary",
|
|
36961
|
+
icon: /* @__PURE__ */ jsx(IdentificationCard, {}),
|
|
36962
|
+
children: _t({
|
|
36963
|
+
id: "5YHQju",
|
|
36964
|
+
message: "Byline schema"
|
|
36965
|
+
})
|
|
36966
|
+
}), isMultiLocale && i18n && activeLocale && /* @__PURE__ */ jsx(LocaleSwitcher, {
|
|
36967
|
+
locales: i18n.locales,
|
|
36968
|
+
defaultLocale: i18n.defaultLocale,
|
|
36969
|
+
value: activeLocale,
|
|
36970
|
+
onChange: handleLocaleChange
|
|
36971
|
+
})]
|
|
35504
36972
|
})]
|
|
35505
|
-
})
|
|
36973
|
+
}),
|
|
35506
36974
|
/* @__PURE__ */ jsxs("div", {
|
|
35507
36975
|
className: "grid grid-cols-1 gap-6 lg:grid-cols-[320px_1fr]",
|
|
35508
36976
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
@@ -35589,7 +37057,7 @@ function BylinesPage() {
|
|
|
35589
37057
|
variant: "secondary",
|
|
35590
37058
|
className: "w-full mt-2",
|
|
35591
37059
|
onClick: () => loadMoreMutation.mutate({
|
|
35592
|
-
search,
|
|
37060
|
+
search: debouncedSearch,
|
|
35593
37061
|
guestFilter,
|
|
35594
37062
|
locale: activeLocale,
|
|
35595
37063
|
cursor: nextCursor
|
|
@@ -35690,6 +37158,33 @@ function BylinesPage() {
|
|
|
35690
37158
|
},
|
|
35691
37159
|
className: "w-full"
|
|
35692
37160
|
}),
|
|
37161
|
+
customFieldDefs.length > 0 && customFieldDefs.map((field) => /* @__PURE__ */ jsx(CustomFieldInput, {
|
|
37162
|
+
field,
|
|
37163
|
+
value: form.customFields[field.slug],
|
|
37164
|
+
onChange: (next) => setForm((prev) => ({
|
|
37165
|
+
...prev,
|
|
37166
|
+
customFields: {
|
|
37167
|
+
...prev.customFields,
|
|
37168
|
+
[field.slug]: next
|
|
37169
|
+
}
|
|
37170
|
+
}))
|
|
37171
|
+
}, field.id)),
|
|
37172
|
+
customFieldsError && /* @__PURE__ */ jsxs("div", {
|
|
37173
|
+
className: "rounded-md border border-kumo-danger/40 bg-kumo-danger/5 p-3 text-sm",
|
|
37174
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
37175
|
+
className: "font-medium text-kumo-danger",
|
|
37176
|
+
children: _t({
|
|
37177
|
+
id: "Jci99j",
|
|
37178
|
+
message: "Couldn't load custom fields."
|
|
37179
|
+
})
|
|
37180
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
37181
|
+
className: "text-xs text-kumo-subtle mt-1",
|
|
37182
|
+
children: _t({
|
|
37183
|
+
id: "X+FaLl",
|
|
37184
|
+
message: "You can still edit the fixed fields above. Saving will not touch any stored custom-field values."
|
|
37185
|
+
})
|
|
37186
|
+
})]
|
|
37187
|
+
}),
|
|
35693
37188
|
/* @__PURE__ */ jsx(Switch, {
|
|
35694
37189
|
label: _t({
|
|
35695
37190
|
id: "6CRaxp",
|
|
@@ -35783,6 +37278,66 @@ function BylinesPage() {
|
|
|
35783
37278
|
]
|
|
35784
37279
|
});
|
|
35785
37280
|
}
|
|
37281
|
+
/**
|
|
37282
|
+
* Renders a single registered byline custom field as the appropriate
|
|
37283
|
+
* Kumo input for its type (Phase 6 of #1174).
|
|
37284
|
+
*
|
|
37285
|
+
* Five v1 type cases mirror `BylineFieldType` and the inputs that
|
|
37286
|
+
* `BylineFieldEditor` allows admins to register. Empty string inputs
|
|
37287
|
+
* coerce to `null` on save so the repo's "null clears the row"
|
|
37288
|
+
* storage semantic engages — server-side `BylineRepository.update`
|
|
37289
|
+
* deletes the value row rather than storing an empty-string JSON.
|
|
37290
|
+
*
|
|
37291
|
+
* `field.required` adds a `*` after the label as a visual hint; the
|
|
37292
|
+
* server is authoritative on validation (Phase 6 ACs don't include a
|
|
37293
|
+
* client-side required check — the registry's `required` flag is
|
|
37294
|
+
* descriptive rather than enforced in the write path today).
|
|
37295
|
+
*/
|
|
37296
|
+
function CustomFieldInput({ field, value, onChange }) {
|
|
37297
|
+
const { _: _t2 } = useLingui();
|
|
37298
|
+
const label = field.required ? `${field.label} *` : field.label;
|
|
37299
|
+
const stringValue = typeof value === "string" ? value : "";
|
|
37300
|
+
switch (field.type) {
|
|
37301
|
+
case "string": return /* @__PURE__ */ jsx(Input, {
|
|
37302
|
+
label,
|
|
37303
|
+
value: stringValue,
|
|
37304
|
+
onChange: (e) => onChange(e.target.value === "" ? null : e.target.value)
|
|
37305
|
+
});
|
|
37306
|
+
case "text": return /* @__PURE__ */ jsx(InputArea, {
|
|
37307
|
+
label,
|
|
37308
|
+
value: stringValue,
|
|
37309
|
+
onChange: (e) => onChange(e.target.value === "" ? null : e.target.value),
|
|
37310
|
+
rows: 3
|
|
37311
|
+
});
|
|
37312
|
+
case "url": return /* @__PURE__ */ jsx(Input, {
|
|
37313
|
+
type: "url",
|
|
37314
|
+
label,
|
|
37315
|
+
value: stringValue,
|
|
37316
|
+
onChange: (e) => onChange(e.target.value === "" ? null : e.target.value)
|
|
37317
|
+
});
|
|
37318
|
+
case "boolean": return /* @__PURE__ */ jsx(Switch, {
|
|
37319
|
+
label,
|
|
37320
|
+
checked: value === true,
|
|
37321
|
+
onCheckedChange: (checked) => onChange(checked)
|
|
37322
|
+
});
|
|
37323
|
+
case "select": {
|
|
37324
|
+
const options = field.validation?.options ?? [];
|
|
37325
|
+
const items = Object.create(null);
|
|
37326
|
+
items[""] = _t2({
|
|
37327
|
+
id: "9npDF6",
|
|
37328
|
+
message: "-- Select --"
|
|
37329
|
+
});
|
|
37330
|
+
for (const opt of options) items[opt] = opt;
|
|
37331
|
+
return /* @__PURE__ */ jsx(Select, {
|
|
37332
|
+
label,
|
|
37333
|
+
value: stringValue,
|
|
37334
|
+
onValueChange: (v) => onChange(!v ? null : v),
|
|
37335
|
+
items,
|
|
37336
|
+
className: "w-full"
|
|
37337
|
+
});
|
|
37338
|
+
}
|
|
37339
|
+
}
|
|
37340
|
+
}
|
|
35786
37341
|
|
|
35787
37342
|
//#endregion
|
|
35788
37343
|
//#region src/components/users/RoleBadge.tsx
|
|
@@ -37146,13 +38701,15 @@ function ContentListPage() {
|
|
|
37146
38701
|
field: "updatedAt",
|
|
37147
38702
|
direction: "desc"
|
|
37148
38703
|
});
|
|
38704
|
+
const [searchTerm, setSearchTerm] = React$1.useState("");
|
|
37149
38705
|
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, error } = useInfiniteQuery({
|
|
37150
38706
|
queryKey: [
|
|
37151
38707
|
"content",
|
|
37152
38708
|
collection,
|
|
37153
38709
|
{
|
|
37154
38710
|
locale: activeLocale,
|
|
37155
|
-
sort
|
|
38711
|
+
sort,
|
|
38712
|
+
search: searchTerm
|
|
37156
38713
|
}
|
|
37157
38714
|
],
|
|
37158
38715
|
queryFn: ({ pageParam }) => fetchContentList(collection, {
|
|
@@ -37160,7 +38717,8 @@ function ContentListPage() {
|
|
|
37160
38717
|
cursor: pageParam,
|
|
37161
38718
|
limit: 100,
|
|
37162
38719
|
orderBy: sort.field,
|
|
37163
|
-
order: sort.direction
|
|
38720
|
+
order: sort.direction,
|
|
38721
|
+
search: searchTerm || void 0
|
|
37164
38722
|
}),
|
|
37165
38723
|
initialPageParam: void 0,
|
|
37166
38724
|
getNextPageParam: (lastPage) => lastPage.nextCursor,
|
|
@@ -37299,7 +38857,8 @@ function ContentListPage() {
|
|
|
37299
38857
|
urlPattern: collectionConfig.urlPattern,
|
|
37300
38858
|
sort,
|
|
37301
38859
|
onSortChange: setSort,
|
|
37302
|
-
total
|
|
38860
|
+
total,
|
|
38861
|
+
onSearchChange: setSearchTerm
|
|
37303
38862
|
});
|
|
37304
38863
|
}
|
|
37305
38864
|
const contentNewRoute = createRoute({
|
|
@@ -37914,11 +39473,18 @@ const mediaRoute = createRoute({
|
|
|
37914
39473
|
});
|
|
37915
39474
|
function MediaPage() {
|
|
37916
39475
|
const queryClient = useQueryClient();
|
|
39476
|
+
const [search, setSearch] = React$1.useState("");
|
|
39477
|
+
const [mimeFilter, setMimeFilter] = React$1.useState(void 0);
|
|
37917
39478
|
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, error } = useInfiniteQuery({
|
|
37918
|
-
queryKey: ["media"
|
|
39479
|
+
queryKey: ["media", {
|
|
39480
|
+
search,
|
|
39481
|
+
mime: Array.isArray(mimeFilter) ? mimeFilter.join(",") : mimeFilter ?? ""
|
|
39482
|
+
}],
|
|
37919
39483
|
queryFn: ({ pageParam }) => fetchMediaList({
|
|
37920
39484
|
cursor: pageParam,
|
|
37921
|
-
limit: 100
|
|
39485
|
+
limit: 100,
|
|
39486
|
+
search: search || void 0,
|
|
39487
|
+
mimeType: mimeFilter
|
|
37922
39488
|
}),
|
|
37923
39489
|
initialPageParam: void 0,
|
|
37924
39490
|
getNextPageParam: (lastPage) => lastPage.nextCursor
|
|
@@ -37945,7 +39511,9 @@ function MediaPage() {
|
|
|
37945
39511
|
hasMore: !!hasNextPage,
|
|
37946
39512
|
onLoadMore: () => void fetchNextPage(),
|
|
37947
39513
|
onUpload: (file) => uploadMutation.mutate(file),
|
|
37948
|
-
onDelete: (id) => deleteMutation.mutate(id)
|
|
39514
|
+
onDelete: (id) => deleteMutation.mutate(id),
|
|
39515
|
+
onLocalSearchChange: setSearch,
|
|
39516
|
+
onLocalMimeFilterChange: setMimeFilter
|
|
37949
39517
|
});
|
|
37950
39518
|
}
|
|
37951
39519
|
const commentsRoute = createRoute({
|
|
@@ -38312,6 +39880,11 @@ const bylinesRoute = createRoute({
|
|
|
38312
39880
|
component: BylinesPage,
|
|
38313
39881
|
validateSearch: parseBylinesLocaleSearch
|
|
38314
39882
|
});
|
|
39883
|
+
const bylineSchemaRoute = createRoute({
|
|
39884
|
+
getParentRoute: () => adminLayoutRoute,
|
|
39885
|
+
path: "/byline-schema",
|
|
39886
|
+
component: BylineSchemaPage
|
|
39887
|
+
});
|
|
38315
39888
|
const contentTypesListRoute = createRoute({
|
|
38316
39889
|
getParentRoute: () => adminLayoutRoute,
|
|
38317
39890
|
path: "/content-types",
|
|
@@ -38538,6 +40111,7 @@ const adminRoutes = adminLayoutRoute.addChildren([
|
|
|
38538
40111
|
taxonomyRoute,
|
|
38539
40112
|
usersRoute,
|
|
38540
40113
|
bylinesRoute,
|
|
40114
|
+
bylineSchemaRoute,
|
|
38541
40115
|
widgetsRoute,
|
|
38542
40116
|
settingsRoute,
|
|
38543
40117
|
generalSettingsRoute,
|
|
@@ -38695,5 +40269,5 @@ function AdminApp({ pluginAdmins = EMPTY_PLUGINS, authProviders = EMPTY_AUTH_PRO
|
|
|
38695
40269
|
}
|
|
38696
40270
|
|
|
38697
40271
|
//#endregion
|
|
38698
|
-
export { API_BASE, API_TOKEN_SCOPES, AdminApp, AdminApp as App, AuthProviderProvider, CAPABILITY_LABELS, ContentEditor, ContentList, DEFAULT_LOCALE, Dashboard, EditorHeader, Header, Link, LoginPage, MediaLibrary, MediaPickerModal, PasskeyLogin, PasskeyRegistration, PluginAdminProvider, PortableTextEditor, SUPPORTED_LOCALES, SUPPORTED_LOCALE_CODES, SaveButton, Settings, SetupWizard, Shell, KumoSidebar as Sidebar, SidebarNav, analyzeWpPluginSite, analyzeWxr, apiFetch, bulkCommentAction, checkPluginUpdates, cn, compareRevisions, completeSignup, createAdminRouter, createAllowedDomain, createApiToken, createByline, createBylineTranslation, createCollection, createContent, createField, createMenu, createMenuItem, createMenuTranslation, createRedirect, createSection, createTaxonomy, createTerm, createTermTranslation, createWidget, createWidgetArea, deleteAllowedDomain, deleteByline, deleteCollection, deleteComment, deleteContent, deleteField, deleteFromProvider, deleteMedia, deleteMenu, deleteMenuItem, deletePasskey, deleteRedirect, deleteSection, deleteTerm, deleteWidget, deleteWidgetArea, describeCapability, disablePlugin, disableUser, discardDraft, duplicateContent, enablePlugin, enableUser, executeWpPluginImport, executeWxrImport, fetch404Summary, fetchAllowedDomains, fetchApiTokens, fetchAuthMode, fetchByline, fetchBylineTranslations, fetchBylines, fetchCollection, fetchCollections, fetchComment, fetchCommentCounts, fetchComments, fetchContent, fetchContentList, fetchDashboardStats, fetchEmailSettings, fetchFields, fetchManifest, fetchMarketplacePlugin, fetchMediaList, fetchMediaProviders, fetchMenu, fetchMenuTranslations, fetchMenus, fetchOrphanedTables, fetchPasskeys, fetchPlugin, fetchPlugins, fetchProviderMedia, fetchRedirects, fetchRevision, fetchRevisions, fetchSection, fetchSections, fetchSettings, fetchTaxonomyDef, fetchTaxonomyDefs, fetchTermTranslations, fetchTerms, fetchTheme, fetchTranslations, fetchTrashedContent, fetchUser, fetchUsers, fetchWidgetArea, fetchWidgetAreas, fetchWidgetComponents, generatePreviewUrl, getDraftStatus, getPreviewUrl, hasAllowedDomains, importWxrMedia, installMarketplacePlugin, inviteUser, parseApiResponse, permanentDeleteContent, prepareWxrImport, probeImportUrl, publishContent, registerOrphanedTable, renamePasskey, reorderFields, reorderMenuItems, reorderWidgets, requestSignup, resolveLocale, restoreContent, restoreRevision, revokeApiToken, rewriteContentUrls, scheduleContent, searchMarketplace, searchThemes, sendRecoveryLink, sendTestEmail, setSearchEnabled, throwResponseError, uninstallMarketplacePlugin, unpublishContent, unscheduleContent, updateAllowedDomain, updateByline, updateCollection, updateCommentStatus, updateContent, updateField, updateMarketplacePlugin, updateMedia, updateMenu, updateMenuItem, updateRedirect, updateSection, updateSettings, updateTerm, updateUser, updateWidget, uploadMedia, uploadToProvider, useAuthProviderList, useAuthProviders, useCurrentUser, useLocale, useNavigate, useParams, usePluginAdmins, usePluginField, usePluginHasPages, usePluginHasWidgets, usePluginPage, usePluginWidget, validateInviteToken, verifySignupToken };
|
|
40272
|
+
export { API_BASE, API_TOKEN_SCOPES, AdminApp, AdminApp as App, AuthProviderProvider, CAPABILITY_LABELS, ContentEditor, ContentList, DEFAULT_LOCALE, Dashboard, EditorHeader, Header, Link, LoginPage, MEDIA_SEARCH_MAX_LENGTH, MediaLibrary, MediaPickerModal, PasskeyLogin, PasskeyRegistration, PluginAdminProvider, PortableTextEditor, SUPPORTED_LOCALES, SUPPORTED_LOCALE_CODES, SaveButton, Settings, SetupWizard, Shell, KumoSidebar as Sidebar, SidebarNav, analyzeWpPluginSite, analyzeWxr, apiFetch, bulkCommentAction, checkPluginUpdates, cn, compareRevisions, completeSignup, createAdminRouter, createAllowedDomain, createApiToken, createByline, createBylineField, createBylineTranslation, createCollection, createContent, createField, createMenu, createMenuItem, createMenuTranslation, createRedirect, createSection, createTaxonomy, createTerm, createTermTranslation, createWidget, createWidgetArea, deleteAllowedDomain, deleteByline, deleteBylineField, deleteCollection, deleteComment, deleteContent, deleteField, deleteFromProvider, deleteMedia, deleteMenu, deleteMenuItem, deletePasskey, deleteRedirect, deleteSection, deleteTerm, deleteWidget, deleteWidgetArea, describeCapability, disablePlugin, disableUser, discardDraft, duplicateContent, enablePlugin, enableUser, executeWpPluginImport, executeWxrImport, fetch404Summary, fetchAllowedDomains, fetchApiTokens, fetchAuthMode, fetchByline, fetchBylineTranslations, fetchBylines, fetchCollection, fetchCollections, fetchComment, fetchCommentCounts, fetchComments, fetchContent, fetchContentList, fetchDashboardStats, fetchEmailSettings, fetchFields, fetchManifest, fetchMarketplacePlugin, fetchMediaList, fetchMediaProviders, fetchMenu, fetchMenuTranslations, fetchMenus, fetchOrphanedTables, fetchPasskeys, fetchPlugin, fetchPlugins, fetchProviderMedia, fetchRedirects, fetchRevision, fetchRevisions, fetchSection, fetchSections, fetchSettings, fetchTaxonomyDef, fetchTaxonomyDefs, fetchTermTranslations, fetchTerms, fetchTheme, fetchTranslations, fetchTrashedContent, fetchUser, fetchUsers, fetchWidgetArea, fetchWidgetAreas, fetchWidgetComponents, generatePreviewUrl, getBylineFieldUsage, getDraftStatus, getPreviewUrl, hasAllowedDomains, importWxrMedia, installMarketplacePlugin, inviteUser, listBylineFields, parseApiResponse, permanentDeleteContent, prepareWxrImport, probeImportUrl, publishContent, registerOrphanedTable, renamePasskey, reorderBylineFields, reorderFields, reorderMenuItems, reorderWidgets, requestSignup, resolveLocale, restoreContent, restoreRevision, revokeApiToken, rewriteContentUrls, scheduleContent, searchMarketplace, searchThemes, sendRecoveryLink, sendTestEmail, setSearchEnabled, throwResponseError, uninstallMarketplacePlugin, unpublishContent, unscheduleContent, updateAllowedDomain, updateByline, updateBylineField, updateCollection, updateCommentStatus, updateContent, updateField, updateMarketplacePlugin, updateMedia, updateMenu, updateMenuItem, updateRedirect, updateSection, updateSettings, updateTerm, updateUser, updateWidget, uploadMedia, uploadToProvider, useAuthProviderList, useAuthProviders, useCurrentUser, useLocale, useNavigate, useParams, usePluginAdmins, usePluginField, usePluginHasPages, usePluginHasWidgets, usePluginPage, usePluginWidget, validateInviteToken, verifySignupToken };
|
|
38699
40273
|
//# sourceMappingURL=index.js.map
|