@emdash-cms/admin 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{LocaleDirectionProvider-BNEVyvgW.js → LocaleDirectionProvider-Cj_AuFni.js} +23 -17
- package/dist/LocaleDirectionProvider-Cj_AuFni.js.map +1 -0
- package/dist/{config-ZP4qGbGk.d.ts → config-BRdaN73J.d.ts} +1 -1
- package/dist/{config-ZP4qGbGk.d.ts.map → config-BRdaN73J.d.ts.map} +1 -1
- package/dist/index.d.ts +142 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1680 -199
- 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/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-68Jb5F6d.js +6 -0
- package/dist/messages-68Jb5F6d.js.map +1 -0
- package/dist/messages-BG5ticgt.js +6 -0
- package/dist/messages-BG5ticgt.js.map +1 -0
- 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-C0p_fLTL.js +6 -0
- package/dist/messages-C0p_fLTL.js.map +1 -0
- package/dist/messages-C3wS1VMq.js +6 -0
- package/dist/messages-C3wS1VMq.js.map +1 -0
- package/dist/messages-CVN814Vy.js +6 -0
- package/dist/messages-CVN814Vy.js.map +1 -0
- package/dist/messages-Cxf80Xeu.js +6 -0
- package/dist/messages-Cxf80Xeu.js.map +1 -0
- package/dist/messages-D3HCqTUM.js +6 -0
- package/dist/messages-D3HCqTUM.js.map +1 -0
- package/dist/messages-DM8WFh_S.js +6 -0
- package/dist/messages-DM8WFh_S.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-BNEVyvgW.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-D2R4PbiQ.js +0 -6
- package/dist/messages-D2R4PbiQ.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-Cj_AuFni.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, BracketsAngle, 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, IdentificationCard, 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";
|
|
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,35 @@ 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
|
+
/**
|
|
28959
29451
|
* Navigation item rendered as a TanStack Router <Link> inside kumo's
|
|
28960
29452
|
* Sidebar.MenuItem. Styled to match kumo MenuButton appearance.
|
|
28961
29453
|
* This approach guarantees client-side navigation works correctly.
|
|
@@ -29060,7 +29552,7 @@ function SidebarNav({ manifest }) {
|
|
|
29060
29552
|
message: "Redirects"
|
|
29061
29553
|
}),
|
|
29062
29554
|
icon: ArrowsLeftRight,
|
|
29063
|
-
minRole: ROLE_ADMIN$
|
|
29555
|
+
minRole: ROLE_ADMIN$2
|
|
29064
29556
|
},
|
|
29065
29557
|
{
|
|
29066
29558
|
to: "/widgets",
|
|
@@ -29105,7 +29597,7 @@ function SidebarNav({ manifest }) {
|
|
|
29105
29597
|
message: "Content Types"
|
|
29106
29598
|
}),
|
|
29107
29599
|
icon: Database,
|
|
29108
|
-
minRole: ROLE_ADMIN$
|
|
29600
|
+
minRole: ROLE_ADMIN$2
|
|
29109
29601
|
},
|
|
29110
29602
|
{
|
|
29111
29603
|
to: "/users",
|
|
@@ -29114,7 +29606,7 @@ function SidebarNav({ manifest }) {
|
|
|
29114
29606
|
message: "Users"
|
|
29115
29607
|
}),
|
|
29116
29608
|
icon: Users,
|
|
29117
|
-
minRole: ROLE_ADMIN$
|
|
29609
|
+
minRole: ROLE_ADMIN$2
|
|
29118
29610
|
},
|
|
29119
29611
|
{
|
|
29120
29612
|
to: "/plugins-manager",
|
|
@@ -29123,7 +29615,7 @@ function SidebarNav({ manifest }) {
|
|
|
29123
29615
|
message: "Plugins"
|
|
29124
29616
|
}),
|
|
29125
29617
|
icon: PuzzlePiece,
|
|
29126
|
-
minRole: ROLE_ADMIN$
|
|
29618
|
+
minRole: ROLE_ADMIN$2
|
|
29127
29619
|
}
|
|
29128
29620
|
];
|
|
29129
29621
|
if (manifest.registry) adminItems.push({
|
|
@@ -29133,7 +29625,7 @@ function SidebarNav({ manifest }) {
|
|
|
29133
29625
|
message: "Registry"
|
|
29134
29626
|
}),
|
|
29135
29627
|
icon: Storefront,
|
|
29136
|
-
minRole: ROLE_ADMIN$
|
|
29628
|
+
minRole: ROLE_ADMIN$2
|
|
29137
29629
|
});
|
|
29138
29630
|
else if (manifest.marketplace) adminItems.push({
|
|
29139
29631
|
to: "/plugins/marketplace",
|
|
@@ -29142,7 +29634,7 @@ function SidebarNav({ manifest }) {
|
|
|
29142
29634
|
message: "Marketplace"
|
|
29143
29635
|
}),
|
|
29144
29636
|
icon: Storefront,
|
|
29145
|
-
minRole: ROLE_ADMIN$
|
|
29637
|
+
minRole: ROLE_ADMIN$2
|
|
29146
29638
|
});
|
|
29147
29639
|
if (manifest.marketplace) adminItems.push({
|
|
29148
29640
|
to: "/themes/marketplace",
|
|
@@ -29151,7 +29643,7 @@ function SidebarNav({ manifest }) {
|
|
|
29151
29643
|
message: "Themes"
|
|
29152
29644
|
}),
|
|
29153
29645
|
icon: Palette,
|
|
29154
|
-
minRole: ROLE_ADMIN$
|
|
29646
|
+
minRole: ROLE_ADMIN$2
|
|
29155
29647
|
});
|
|
29156
29648
|
adminItems.push({
|
|
29157
29649
|
to: "/import/wordpress",
|
|
@@ -29160,7 +29652,7 @@ function SidebarNav({ manifest }) {
|
|
|
29160
29652
|
message: "Import"
|
|
29161
29653
|
}),
|
|
29162
29654
|
icon: Upload,
|
|
29163
|
-
minRole: ROLE_ADMIN$
|
|
29655
|
+
minRole: ROLE_ADMIN$2
|
|
29164
29656
|
}, {
|
|
29165
29657
|
to: "/settings",
|
|
29166
29658
|
label: _t({
|
|
@@ -29168,7 +29660,7 @@ function SidebarNav({ manifest }) {
|
|
|
29168
29660
|
message: "Settings"
|
|
29169
29661
|
}),
|
|
29170
29662
|
icon: Gear,
|
|
29171
|
-
minRole: ROLE_ADMIN$
|
|
29663
|
+
minRole: ROLE_ADMIN$2
|
|
29172
29664
|
});
|
|
29173
29665
|
const pluginItems = [];
|
|
29174
29666
|
for (const [pluginId, config] of Object.entries(manifest.plugins)) {
|
|
@@ -29187,11 +29679,10 @@ function SidebarNav({ manifest }) {
|
|
|
29187
29679
|
}
|
|
29188
29680
|
}
|
|
29189
29681
|
}
|
|
29190
|
-
const
|
|
29191
|
-
const
|
|
29192
|
-
const
|
|
29193
|
-
const
|
|
29194
|
-
const visiblePlugins = filterByRole(pluginItems);
|
|
29682
|
+
const visibleContent = filterNavItemsByRole(contentItems, userRole);
|
|
29683
|
+
const visibleManage = filterNavItemsByRole(manageItems, userRole);
|
|
29684
|
+
const visibleAdmin = filterNavItemsByRole(adminItems, userRole);
|
|
29685
|
+
const visiblePlugins = filterNavItemsByRole(pluginItems, userRole);
|
|
29195
29686
|
function renderNavItems(items) {
|
|
29196
29687
|
return items.map((item, index) => {
|
|
29197
29688
|
return /* @__PURE__ */ jsx(NavMenuLink, {
|
|
@@ -29204,6 +29695,9 @@ function SidebarNav({ manifest }) {
|
|
|
29204
29695
|
/* Classic dark chrome — override kumo tokens within the sidebar */
|
|
29205
29696
|
.emdash-sidebar {
|
|
29206
29697
|
--color-kumo-base: #1d2327;
|
|
29698
|
+
/* Kumo 2.4 paints the surface via bg-(--sidebar-bg) on an inner
|
|
29699
|
+
container, resolved from the wrapper's light --color-kumo-base. */
|
|
29700
|
+
--sidebar-bg: #1d2327;
|
|
29207
29701
|
--color-kumo-tint: rgba(255,255,255,0.1);
|
|
29208
29702
|
--color-kumo-line: rgba(255,255,255,0.08);
|
|
29209
29703
|
--color-kumo-brand: #2271b1;
|
|
@@ -35285,6 +35779,858 @@ function AuthorMappingStep({ authorMappings, emdashUsers, onMappingChange, onCon
|
|
|
35285
35779
|
});
|
|
35286
35780
|
}
|
|
35287
35781
|
|
|
35782
|
+
//#endregion
|
|
35783
|
+
//#region src/components/BylineFieldEditor.tsx
|
|
35784
|
+
/**
|
|
35785
|
+
* BylineFieldEditor (Phase 5 of Discussion #1174).
|
|
35786
|
+
*
|
|
35787
|
+
* Purpose-built create/edit dialog for byline custom-field definitions.
|
|
35788
|
+
* Constrains the type select to the five v1 types declared by the
|
|
35789
|
+
* `BylineFieldType` union — narrower than the content-field universe —
|
|
35790
|
+
* and exposes the `translatable` toggle (the storage-split flag) that
|
|
35791
|
+
* the content `FieldEditor` doesn't have.
|
|
35792
|
+
*
|
|
35793
|
+
* Single-step form (not the type-picker → config flow used by
|
|
35794
|
+
* `FieldEditor`): with only five types a `<Select>` is faster than a
|
|
35795
|
+
* grid of cards and the dialog stays under ~200 LOC.
|
|
35796
|
+
*
|
|
35797
|
+
* The `translatable` toggle is locked when editing a field that already
|
|
35798
|
+
* has stored values — the server returns `TRANSLATABLE_LOCKED` (409) for
|
|
35799
|
+
* the flip, but disabling the input + showing a help message saves a
|
|
35800
|
+
* round-trip and is friendlier than a toast. Usage is passed in by the
|
|
35801
|
+
* parent (which fetches `getBylineFieldUsage` on edit-open) so this
|
|
35802
|
+
* component stays a pure controlled form.
|
|
35803
|
+
*/
|
|
35804
|
+
const SLUG_INVALID_CHARS_REGEX = /[^a-z0-9]+/g;
|
|
35805
|
+
const SLUG_LEADING_TRAILING_REGEX = /^_|_$/g;
|
|
35806
|
+
/**
|
|
35807
|
+
* The five v1 byline field types. Kept in lock-step with the server-side
|
|
35808
|
+
* `BYLINE_FIELD_TYPES` constant (`packages/core/src/schema/types.ts`).
|
|
35809
|
+
*
|
|
35810
|
+
* `MessageDescriptor`s are resolved with `t(descriptor)` inside the
|
|
35811
|
+
* component so the strings extract correctly to the Lingui catalog.
|
|
35812
|
+
*/
|
|
35813
|
+
const TYPE_OPTIONS = [
|
|
35814
|
+
{
|
|
35815
|
+
value: "string",
|
|
35816
|
+
label: {
|
|
35817
|
+
id: "q13dKg",
|
|
35818
|
+
message: "Short text"
|
|
35819
|
+
}
|
|
35820
|
+
},
|
|
35821
|
+
{
|
|
35822
|
+
value: "text",
|
|
35823
|
+
label: {
|
|
35824
|
+
id: "g+zzQs",
|
|
35825
|
+
message: "Long text"
|
|
35826
|
+
}
|
|
35827
|
+
},
|
|
35828
|
+
{
|
|
35829
|
+
value: "url",
|
|
35830
|
+
label: {
|
|
35831
|
+
id: "IagCbF",
|
|
35832
|
+
message: "URL"
|
|
35833
|
+
}
|
|
35834
|
+
},
|
|
35835
|
+
{
|
|
35836
|
+
value: "boolean",
|
|
35837
|
+
label: {
|
|
35838
|
+
id: "bOwPjT",
|
|
35839
|
+
message: "Boolean"
|
|
35840
|
+
}
|
|
35841
|
+
},
|
|
35842
|
+
{
|
|
35843
|
+
value: "select",
|
|
35844
|
+
label: {
|
|
35845
|
+
id: "rG3WVm",
|
|
35846
|
+
message: "Select"
|
|
35847
|
+
}
|
|
35848
|
+
}
|
|
35849
|
+
];
|
|
35850
|
+
function initialFormState(field) {
|
|
35851
|
+
if (!field) return {
|
|
35852
|
+
slug: "",
|
|
35853
|
+
label: "",
|
|
35854
|
+
type: "string",
|
|
35855
|
+
required: false,
|
|
35856
|
+
translatable: true,
|
|
35857
|
+
options: ""
|
|
35858
|
+
};
|
|
35859
|
+
return {
|
|
35860
|
+
slug: field.slug,
|
|
35861
|
+
label: field.label,
|
|
35862
|
+
type: field.type,
|
|
35863
|
+
required: field.required,
|
|
35864
|
+
translatable: field.translatable,
|
|
35865
|
+
options: field.validation?.options?.join("\n") ?? ""
|
|
35866
|
+
};
|
|
35867
|
+
}
|
|
35868
|
+
function BylineFieldEditor({ open, onOpenChange, field, usageTotal = 0, onSave, isSaving, error }) {
|
|
35869
|
+
const { _: _t } = useLingui();
|
|
35870
|
+
const isEdit = !!field;
|
|
35871
|
+
const [state, setState] = React$1.useState(() => initialFormState(field));
|
|
35872
|
+
React$1.useEffect(() => {
|
|
35873
|
+
if (open) setState(initialFormState(field));
|
|
35874
|
+
}, [open, field]);
|
|
35875
|
+
const setField = (key, value) => setState((prev) => ({
|
|
35876
|
+
...prev,
|
|
35877
|
+
[key]: value
|
|
35878
|
+
}));
|
|
35879
|
+
const handleLabelChange = (value) => {
|
|
35880
|
+
setField("label", value);
|
|
35881
|
+
if (!isEdit) setField("slug", value.toLowerCase().replace(SLUG_INVALID_CHARS_REGEX, "_").replace(SLUG_LEADING_TRAILING_REGEX, ""));
|
|
35882
|
+
};
|
|
35883
|
+
const parsedSelectOptions = state.type === "select" ? state.options.split("\n").map((o) => o.trim()).filter(Boolean) : [];
|
|
35884
|
+
const handleSave = () => {
|
|
35885
|
+
if (!state.label.trim()) return;
|
|
35886
|
+
if (!isEdit && !state.slug.trim()) return;
|
|
35887
|
+
if (state.type === "select" && parsedSelectOptions.length === 0) return;
|
|
35888
|
+
const validation = state.type === "select" ? { options: parsedSelectOptions } : null;
|
|
35889
|
+
if (isEdit) onSave({
|
|
35890
|
+
label: state.label,
|
|
35891
|
+
required: state.required,
|
|
35892
|
+
translatable: field && state.translatable !== field.translatable ? state.translatable : void 0,
|
|
35893
|
+
validation
|
|
35894
|
+
});
|
|
35895
|
+
else onSave({
|
|
35896
|
+
slug: state.slug,
|
|
35897
|
+
label: state.label,
|
|
35898
|
+
type: state.type,
|
|
35899
|
+
required: state.required,
|
|
35900
|
+
translatable: state.translatable,
|
|
35901
|
+
validation
|
|
35902
|
+
});
|
|
35903
|
+
};
|
|
35904
|
+
const translatableLocked = isEdit && usageTotal > 0;
|
|
35905
|
+
const canSave = state.label.trim().length > 0 && (isEdit || state.slug.trim().length > 0) && (state.type !== "select" || parsedSelectOptions.length > 0);
|
|
35906
|
+
return /* @__PURE__ */ jsx(Dialog.Root, {
|
|
35907
|
+
open,
|
|
35908
|
+
onOpenChange,
|
|
35909
|
+
children: /* @__PURE__ */ jsxs(Dialog, {
|
|
35910
|
+
className: "p-6 max-w-xl",
|
|
35911
|
+
size: "lg",
|
|
35912
|
+
children: [
|
|
35913
|
+
/* @__PURE__ */ jsxs("div", {
|
|
35914
|
+
className: "flex items-start justify-between gap-4 mb-4",
|
|
35915
|
+
children: [/* @__PURE__ */ jsx(Dialog.Title, {
|
|
35916
|
+
className: "text-lg font-semibold leading-none tracking-tight",
|
|
35917
|
+
children: isEdit ? _t({
|
|
35918
|
+
id: "hcfOYc",
|
|
35919
|
+
message: "Edit byline field"
|
|
35920
|
+
}) : _t({
|
|
35921
|
+
id: "hT2KP5",
|
|
35922
|
+
message: "New byline field"
|
|
35923
|
+
})
|
|
35924
|
+
}), /* @__PURE__ */ jsx(Dialog.Close, {
|
|
35925
|
+
"aria-label": _t({
|
|
35926
|
+
id: "yz7wBu",
|
|
35927
|
+
message: "Close"
|
|
35928
|
+
}),
|
|
35929
|
+
render: (props) => /* @__PURE__ */ jsxs(Button, {
|
|
35930
|
+
...props,
|
|
35931
|
+
variant: "ghost",
|
|
35932
|
+
shape: "square",
|
|
35933
|
+
"aria-label": _t({
|
|
35934
|
+
id: "yz7wBu",
|
|
35935
|
+
message: "Close"
|
|
35936
|
+
}),
|
|
35937
|
+
className: "absolute end-4 top-4",
|
|
35938
|
+
children: [/* @__PURE__ */ jsx(X, { className: "h-4 w-4" }), /* @__PURE__ */ jsx("span", {
|
|
35939
|
+
className: "sr-only",
|
|
35940
|
+
children: _t({
|
|
35941
|
+
id: "yz7wBu",
|
|
35942
|
+
message: "Close"
|
|
35943
|
+
})
|
|
35944
|
+
})]
|
|
35945
|
+
})
|
|
35946
|
+
})]
|
|
35947
|
+
}),
|
|
35948
|
+
/* @__PURE__ */ jsxs("div", {
|
|
35949
|
+
className: "space-y-5",
|
|
35950
|
+
children: [
|
|
35951
|
+
/* @__PURE__ */ jsxs("div", {
|
|
35952
|
+
className: "grid grid-cols-2 gap-4",
|
|
35953
|
+
children: [/* @__PURE__ */ jsx(Input, {
|
|
35954
|
+
label: _t({
|
|
35955
|
+
id: "87a/t/",
|
|
35956
|
+
message: "Label"
|
|
35957
|
+
}),
|
|
35958
|
+
value: state.label,
|
|
35959
|
+
onChange: (e) => handleLabelChange(e.target.value),
|
|
35960
|
+
placeholder: _t({
|
|
35961
|
+
id: "qMgKul",
|
|
35962
|
+
message: "Job title"
|
|
35963
|
+
})
|
|
35964
|
+
}), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Input, {
|
|
35965
|
+
label: _t({
|
|
35966
|
+
id: "L85WcV",
|
|
35967
|
+
message: "Slug"
|
|
35968
|
+
}),
|
|
35969
|
+
value: state.slug,
|
|
35970
|
+
onChange: (e) => setField("slug", e.target.value),
|
|
35971
|
+
placeholder: _t({
|
|
35972
|
+
id: "gddbae",
|
|
35973
|
+
message: "job_title"
|
|
35974
|
+
}),
|
|
35975
|
+
disabled: isEdit
|
|
35976
|
+
}), isEdit && /* @__PURE__ */ jsx("p", {
|
|
35977
|
+
className: "text-xs text-kumo-subtle mt-2",
|
|
35978
|
+
children: _t({
|
|
35979
|
+
id: "5OXS9U",
|
|
35980
|
+
message: "Slugs cannot be changed after the field is created."
|
|
35981
|
+
})
|
|
35982
|
+
})] })]
|
|
35983
|
+
}),
|
|
35984
|
+
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Select, {
|
|
35985
|
+
label: _t({
|
|
35986
|
+
id: "+zy2Nq",
|
|
35987
|
+
message: "Type"
|
|
35988
|
+
}),
|
|
35989
|
+
value: state.type,
|
|
35990
|
+
onValueChange: (v) => setField("type", v ?? "string"),
|
|
35991
|
+
items: Object.fromEntries(TYPE_OPTIONS.map(({ value, label }) => [value, _t(label)])),
|
|
35992
|
+
disabled: isEdit
|
|
35993
|
+
}), isEdit && /* @__PURE__ */ jsx("p", {
|
|
35994
|
+
className: "text-xs text-kumo-subtle mt-2",
|
|
35995
|
+
children: _t({
|
|
35996
|
+
id: "NF9uuv",
|
|
35997
|
+
message: "Field type cannot be changed after creation."
|
|
35998
|
+
})
|
|
35999
|
+
})] }),
|
|
36000
|
+
/* @__PURE__ */ jsxs("div", {
|
|
36001
|
+
className: "flex flex-wrap items-center gap-6",
|
|
36002
|
+
children: [/* @__PURE__ */ jsx(Switch, {
|
|
36003
|
+
checked: state.required,
|
|
36004
|
+
onCheckedChange: (checked) => setField("required", checked),
|
|
36005
|
+
label: /* @__PURE__ */ jsx("span", {
|
|
36006
|
+
className: "text-sm",
|
|
36007
|
+
children: _t({
|
|
36008
|
+
id: "TMLAx2",
|
|
36009
|
+
message: "Required"
|
|
36010
|
+
})
|
|
36011
|
+
})
|
|
36012
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
36013
|
+
className: "flex flex-col gap-1",
|
|
36014
|
+
children: [/* @__PURE__ */ jsx(Switch, {
|
|
36015
|
+
checked: state.translatable,
|
|
36016
|
+
onCheckedChange: (checked) => setField("translatable", checked),
|
|
36017
|
+
disabled: translatableLocked,
|
|
36018
|
+
label: /* @__PURE__ */ jsx("span", {
|
|
36019
|
+
className: "text-sm",
|
|
36020
|
+
children: _t({
|
|
36021
|
+
id: "O/SdND",
|
|
36022
|
+
message: "Translatable"
|
|
36023
|
+
})
|
|
36024
|
+
})
|
|
36025
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
36026
|
+
className: "text-xs text-kumo-subtle max-w-xs",
|
|
36027
|
+
children: translatableLocked ? _t({
|
|
36028
|
+
id: "3GPtwD",
|
|
36029
|
+
message: "Locked because this field has stored values. Delete the values (or the field) to change this."
|
|
36030
|
+
}) : state.translatable ? _t({
|
|
36031
|
+
id: "ONAVuM",
|
|
36032
|
+
message: "Stored per locale — each translation of a byline gets its own value."
|
|
36033
|
+
}) : _t({
|
|
36034
|
+
id: "msPOkF",
|
|
36035
|
+
message: "Shared across all translations of the same byline."
|
|
36036
|
+
})
|
|
36037
|
+
})]
|
|
36038
|
+
})]
|
|
36039
|
+
}),
|
|
36040
|
+
state.type === "select" && /* @__PURE__ */ jsx(InputArea, {
|
|
36041
|
+
label: _t({
|
|
36042
|
+
id: "okW6+j",
|
|
36043
|
+
message: "Options (one per line)"
|
|
36044
|
+
}),
|
|
36045
|
+
value: state.options,
|
|
36046
|
+
onChange: (e) => setField("options", e.target.value),
|
|
36047
|
+
placeholder: _t({
|
|
36048
|
+
id: "Y9cJqL",
|
|
36049
|
+
message: "Editor\nReporter\nPhotographer"
|
|
36050
|
+
}),
|
|
36051
|
+
rows: 5
|
|
36052
|
+
}),
|
|
36053
|
+
/* @__PURE__ */ jsx(DialogError, {
|
|
36054
|
+
message: getMutationError(error),
|
|
36055
|
+
className: "mt-1"
|
|
36056
|
+
})
|
|
36057
|
+
]
|
|
36058
|
+
}),
|
|
36059
|
+
/* @__PURE__ */ jsxs("div", {
|
|
36060
|
+
className: "mt-6 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
|
36061
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
36062
|
+
variant: "outline",
|
|
36063
|
+
onClick: () => onOpenChange(false),
|
|
36064
|
+
disabled: isSaving,
|
|
36065
|
+
children: _t({
|
|
36066
|
+
id: "dEgA5A",
|
|
36067
|
+
message: "Cancel"
|
|
36068
|
+
})
|
|
36069
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
36070
|
+
onClick: handleSave,
|
|
36071
|
+
disabled: !canSave || isSaving,
|
|
36072
|
+
children: isSaving ? _t({
|
|
36073
|
+
id: "K/F6pa",
|
|
36074
|
+
message: "Saving…"
|
|
36075
|
+
}) : isEdit ? _t({
|
|
36076
|
+
id: "y3aU20",
|
|
36077
|
+
message: "Save changes"
|
|
36078
|
+
}) : _t({
|
|
36079
|
+
id: "A3eIjV",
|
|
36080
|
+
message: "Create field"
|
|
36081
|
+
})
|
|
36082
|
+
})]
|
|
36083
|
+
})
|
|
36084
|
+
]
|
|
36085
|
+
})
|
|
36086
|
+
});
|
|
36087
|
+
}
|
|
36088
|
+
|
|
36089
|
+
//#endregion
|
|
36090
|
+
//#region src/routes/byline-schema.tsx
|
|
36091
|
+
/**
|
|
36092
|
+
* Byline custom-field schema management (Phase 5 of Discussion #1174).
|
|
36093
|
+
*
|
|
36094
|
+
* Admin can create / edit / delete / reorder byline custom-field
|
|
36095
|
+
* definitions. Modelled on the content-type management UX but uses the
|
|
36096
|
+
* purpose-built `BylineFieldEditor` (5-type subset + `translatable`
|
|
36097
|
+
* toggle).
|
|
36098
|
+
*
|
|
36099
|
+
* Reorder uses ↑/↓ buttons per row rather than drag-and-drop — the v1
|
|
36100
|
+
* registry caps the field set at a small number (admins typically
|
|
36101
|
+
* register 2–5 custom fields per site), accessibility is better without
|
|
36102
|
+
* pulling in a DnD dependency, and screen-reader users get usable
|
|
36103
|
+
* keyboard semantics for free. The reorder API takes the full slug list
|
|
36104
|
+
* in the desired order; clicking ↑ swaps the row with its neighbour and
|
|
36105
|
+
* fires the mutation.
|
|
36106
|
+
*
|
|
36107
|
+
* Permission gate: `ROLE_ADMIN`. The route is also gated at the sidebar
|
|
36108
|
+
* level (`minRole: ROLE_ADMIN`), but a manually-typed URL still hits
|
|
36109
|
+
* this page — so the in-component check is the source of truth.
|
|
36110
|
+
*/
|
|
36111
|
+
const ROLE_ADMIN$1 = 50;
|
|
36112
|
+
const QUERY_KEY = ["byline-fields"];
|
|
36113
|
+
function BylineSchemaPage() {
|
|
36114
|
+
const { _: _t } = useLingui();
|
|
36115
|
+
const queryClient = useQueryClient();
|
|
36116
|
+
const toastManager = Toast.useToastManager();
|
|
36117
|
+
const { data: currentUser, isLoading: userLoading } = useCurrentUser();
|
|
36118
|
+
const [editingField, setEditingField] = React$1.useState(null);
|
|
36119
|
+
const [creating, setCreating] = React$1.useState(false);
|
|
36120
|
+
const [deletingField, setDeletingField] = React$1.useState(null);
|
|
36121
|
+
const fieldsQuery = useQuery({
|
|
36122
|
+
queryKey: QUERY_KEY,
|
|
36123
|
+
queryFn: listBylineFields,
|
|
36124
|
+
enabled: !!currentUser && currentUser.role >= ROLE_ADMIN$1
|
|
36125
|
+
});
|
|
36126
|
+
const usageQuery = useQuery({
|
|
36127
|
+
queryKey: [
|
|
36128
|
+
"byline-schema",
|
|
36129
|
+
"usage",
|
|
36130
|
+
editingField?.slug ?? null
|
|
36131
|
+
],
|
|
36132
|
+
queryFn: () => editingField ? getBylineFieldUsage(editingField.slug) : Promise.resolve(null),
|
|
36133
|
+
enabled: !!editingField
|
|
36134
|
+
});
|
|
36135
|
+
const deleteUsageQuery = useQuery({
|
|
36136
|
+
queryKey: [
|
|
36137
|
+
"byline-schema",
|
|
36138
|
+
"usage",
|
|
36139
|
+
deletingField?.slug ?? null
|
|
36140
|
+
],
|
|
36141
|
+
queryFn: () => deletingField ? getBylineFieldUsage(deletingField.slug) : Promise.resolve(null),
|
|
36142
|
+
enabled: !!deletingField
|
|
36143
|
+
});
|
|
36144
|
+
const createMutation = useMutation({
|
|
36145
|
+
mutationFn: (input) => createBylineField(input),
|
|
36146
|
+
onSuccess: (field) => {
|
|
36147
|
+
queryClient.invalidateQueries({ queryKey: QUERY_KEY });
|
|
36148
|
+
toastManager.add({
|
|
36149
|
+
title: _t({
|
|
36150
|
+
id: "5fyK5P",
|
|
36151
|
+
message: "Field created"
|
|
36152
|
+
}),
|
|
36153
|
+
description: _t({
|
|
36154
|
+
id: "CmHSIV",
|
|
36155
|
+
message: "Created \"{0}\".",
|
|
36156
|
+
values: { 0: field.label }
|
|
36157
|
+
})
|
|
36158
|
+
});
|
|
36159
|
+
setCreating(false);
|
|
36160
|
+
},
|
|
36161
|
+
onError: (mutationError) => {
|
|
36162
|
+
toastManager.add({
|
|
36163
|
+
title: _t({
|
|
36164
|
+
id: "WWQD//",
|
|
36165
|
+
message: "Failed to create field"
|
|
36166
|
+
}),
|
|
36167
|
+
description: mutationError instanceof Error ? mutationError.message : _t({
|
|
36168
|
+
id: "Vw8l6h",
|
|
36169
|
+
message: "An error occurred"
|
|
36170
|
+
}),
|
|
36171
|
+
type: "error"
|
|
36172
|
+
});
|
|
36173
|
+
}
|
|
36174
|
+
});
|
|
36175
|
+
const updateMutation = useMutation({
|
|
36176
|
+
mutationFn: (vars) => updateBylineField(vars.slug, vars.input),
|
|
36177
|
+
onSuccess: (field) => {
|
|
36178
|
+
queryClient.invalidateQueries({ queryKey: QUERY_KEY });
|
|
36179
|
+
toastManager.add({
|
|
36180
|
+
title: _t({
|
|
36181
|
+
id: "oaEgX0",
|
|
36182
|
+
message: "Field updated"
|
|
36183
|
+
}),
|
|
36184
|
+
description: _t({
|
|
36185
|
+
id: "m64wqx",
|
|
36186
|
+
message: "Saved \"{0}\".",
|
|
36187
|
+
values: { 0: field.label }
|
|
36188
|
+
})
|
|
36189
|
+
});
|
|
36190
|
+
setEditingField(null);
|
|
36191
|
+
},
|
|
36192
|
+
onError: (mutationError) => {
|
|
36193
|
+
toastManager.add({
|
|
36194
|
+
title: _t({
|
|
36195
|
+
id: "nfiPO9",
|
|
36196
|
+
message: "Failed to save field"
|
|
36197
|
+
}),
|
|
36198
|
+
description: mutationError instanceof Error ? mutationError.message : _t({
|
|
36199
|
+
id: "Vw8l6h",
|
|
36200
|
+
message: "An error occurred"
|
|
36201
|
+
}),
|
|
36202
|
+
type: "error"
|
|
36203
|
+
});
|
|
36204
|
+
}
|
|
36205
|
+
});
|
|
36206
|
+
const deleteMutation = useMutation({
|
|
36207
|
+
mutationFn: (slug) => deleteBylineField(slug),
|
|
36208
|
+
onSuccess: () => {
|
|
36209
|
+
queryClient.invalidateQueries({ queryKey: QUERY_KEY });
|
|
36210
|
+
toastManager.add({ title: _t({
|
|
36211
|
+
id: "LX8Pu1",
|
|
36212
|
+
message: "Field deleted"
|
|
36213
|
+
}) });
|
|
36214
|
+
setDeletingField(null);
|
|
36215
|
+
},
|
|
36216
|
+
onError: (mutationError) => {
|
|
36217
|
+
toastManager.add({
|
|
36218
|
+
title: _t({
|
|
36219
|
+
id: "e0xtVp",
|
|
36220
|
+
message: "Failed to delete field"
|
|
36221
|
+
}),
|
|
36222
|
+
description: mutationError instanceof Error ? mutationError.message : _t({
|
|
36223
|
+
id: "Vw8l6h",
|
|
36224
|
+
message: "An error occurred"
|
|
36225
|
+
}),
|
|
36226
|
+
type: "error"
|
|
36227
|
+
});
|
|
36228
|
+
}
|
|
36229
|
+
});
|
|
36230
|
+
const reorderMutation = useMutation({
|
|
36231
|
+
mutationFn: (slugs) => reorderBylineFields(slugs),
|
|
36232
|
+
onSuccess: () => {
|
|
36233
|
+
queryClient.invalidateQueries({ queryKey: QUERY_KEY });
|
|
36234
|
+
},
|
|
36235
|
+
onError: (mutationError) => {
|
|
36236
|
+
toastManager.add({
|
|
36237
|
+
title: _t({
|
|
36238
|
+
id: "eGNX2z",
|
|
36239
|
+
message: "Failed to reorder fields"
|
|
36240
|
+
}),
|
|
36241
|
+
description: mutationError instanceof Error ? mutationError.message : _t({
|
|
36242
|
+
id: "Vw8l6h",
|
|
36243
|
+
message: "An error occurred"
|
|
36244
|
+
}),
|
|
36245
|
+
type: "error"
|
|
36246
|
+
});
|
|
36247
|
+
}
|
|
36248
|
+
});
|
|
36249
|
+
if (userLoading) return /* @__PURE__ */ jsx("div", {
|
|
36250
|
+
className: "flex items-center justify-center min-h-[50vh]",
|
|
36251
|
+
children: /* @__PURE__ */ jsx(Loader, {})
|
|
36252
|
+
});
|
|
36253
|
+
if (!currentUser || currentUser.role < ROLE_ADMIN$1) return /* @__PURE__ */ jsx("div", {
|
|
36254
|
+
className: "flex items-center justify-center min-h-[50vh]",
|
|
36255
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
36256
|
+
className: "text-center",
|
|
36257
|
+
children: [/* @__PURE__ */ jsx("h1", {
|
|
36258
|
+
className: "text-2xl font-bold",
|
|
36259
|
+
children: _t({
|
|
36260
|
+
id: "S2Rr8h",
|
|
36261
|
+
message: "Access denied"
|
|
36262
|
+
})
|
|
36263
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
36264
|
+
className: "mt-2 text-kumo-subtle",
|
|
36265
|
+
children: _t({
|
|
36266
|
+
id: "MKw3nY",
|
|
36267
|
+
message: "You need admin permissions to manage byline schema."
|
|
36268
|
+
})
|
|
36269
|
+
})]
|
|
36270
|
+
})
|
|
36271
|
+
});
|
|
36272
|
+
const fields = fieldsQuery.data?.items ?? [];
|
|
36273
|
+
const handleMove = (index, delta) => {
|
|
36274
|
+
const targetIndex = index + delta;
|
|
36275
|
+
if (targetIndex < 0 || targetIndex >= fields.length) return;
|
|
36276
|
+
const next = [...fields];
|
|
36277
|
+
const a = next[index];
|
|
36278
|
+
const b = next[targetIndex];
|
|
36279
|
+
if (!a || !b) return;
|
|
36280
|
+
next[index] = b;
|
|
36281
|
+
next[targetIndex] = a;
|
|
36282
|
+
reorderMutation.mutate(next.map((f) => f.slug));
|
|
36283
|
+
};
|
|
36284
|
+
const handleCreate = (input) => {
|
|
36285
|
+
if ("slug" in input && "type" in input) createMutation.mutate(input);
|
|
36286
|
+
};
|
|
36287
|
+
const handleEdit = (input) => {
|
|
36288
|
+
if (!editingField) return;
|
|
36289
|
+
if (!("slug" in input) && !("type" in input)) updateMutation.mutate({
|
|
36290
|
+
slug: editingField.slug,
|
|
36291
|
+
input
|
|
36292
|
+
});
|
|
36293
|
+
};
|
|
36294
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
36295
|
+
className: "space-y-4",
|
|
36296
|
+
children: [
|
|
36297
|
+
/* @__PURE__ */ jsxs("div", {
|
|
36298
|
+
className: "flex items-center justify-between",
|
|
36299
|
+
children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h1", {
|
|
36300
|
+
className: "text-2xl font-bold",
|
|
36301
|
+
children: _t({
|
|
36302
|
+
id: "5YHQju",
|
|
36303
|
+
message: "Byline schema"
|
|
36304
|
+
})
|
|
36305
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
36306
|
+
className: "text-kumo-subtle text-sm",
|
|
36307
|
+
children: _t({
|
|
36308
|
+
id: "yYyG5z",
|
|
36309
|
+
message: "Define custom fields stored on every byline — job title, pronouns, social handles, and more."
|
|
36310
|
+
})
|
|
36311
|
+
})] }), /* @__PURE__ */ jsx(Button, {
|
|
36312
|
+
icon: /* @__PURE__ */ jsx(Plus, {}),
|
|
36313
|
+
onClick: () => setCreating(true),
|
|
36314
|
+
children: _t({
|
|
36315
|
+
id: "kV1LSq",
|
|
36316
|
+
message: "New field"
|
|
36317
|
+
})
|
|
36318
|
+
})]
|
|
36319
|
+
}),
|
|
36320
|
+
/* @__PURE__ */ jsx("div", {
|
|
36321
|
+
className: "rounded-md border bg-kumo-base overflow-x-auto",
|
|
36322
|
+
children: /* @__PURE__ */ jsxs("table", {
|
|
36323
|
+
className: "w-full",
|
|
36324
|
+
children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", {
|
|
36325
|
+
className: "border-b bg-kumo-tint/50",
|
|
36326
|
+
children: [
|
|
36327
|
+
/* @__PURE__ */ jsx("th", {
|
|
36328
|
+
scope: "col",
|
|
36329
|
+
className: "px-4 py-3 text-start text-sm font-medium",
|
|
36330
|
+
children: _t({
|
|
36331
|
+
id: "87a/t/",
|
|
36332
|
+
message: "Label"
|
|
36333
|
+
})
|
|
36334
|
+
}),
|
|
36335
|
+
/* @__PURE__ */ jsx("th", {
|
|
36336
|
+
scope: "col",
|
|
36337
|
+
className: "px-4 py-3 text-start text-sm font-medium",
|
|
36338
|
+
children: _t({
|
|
36339
|
+
id: "L85WcV",
|
|
36340
|
+
message: "Slug"
|
|
36341
|
+
})
|
|
36342
|
+
}),
|
|
36343
|
+
/* @__PURE__ */ jsx("th", {
|
|
36344
|
+
scope: "col",
|
|
36345
|
+
className: "px-4 py-3 text-start text-sm font-medium",
|
|
36346
|
+
children: _t({
|
|
36347
|
+
id: "+zy2Nq",
|
|
36348
|
+
message: "Type"
|
|
36349
|
+
})
|
|
36350
|
+
}),
|
|
36351
|
+
/* @__PURE__ */ jsx("th", {
|
|
36352
|
+
scope: "col",
|
|
36353
|
+
className: "px-4 py-3 text-start text-sm font-medium",
|
|
36354
|
+
children: _t({
|
|
36355
|
+
id: "O/SdND",
|
|
36356
|
+
message: "Translatable"
|
|
36357
|
+
})
|
|
36358
|
+
}),
|
|
36359
|
+
/* @__PURE__ */ jsx("th", {
|
|
36360
|
+
scope: "col",
|
|
36361
|
+
className: "px-4 py-3 text-start text-sm font-medium",
|
|
36362
|
+
children: _t({
|
|
36363
|
+
id: "TMLAx2",
|
|
36364
|
+
message: "Required"
|
|
36365
|
+
})
|
|
36366
|
+
}),
|
|
36367
|
+
/* @__PURE__ */ jsx("th", {
|
|
36368
|
+
scope: "col",
|
|
36369
|
+
className: "px-4 py-3 text-end text-sm font-medium",
|
|
36370
|
+
children: _t({
|
|
36371
|
+
id: "7L01XJ",
|
|
36372
|
+
message: "Actions"
|
|
36373
|
+
})
|
|
36374
|
+
})
|
|
36375
|
+
]
|
|
36376
|
+
}) }), /* @__PURE__ */ jsx("tbody", {
|
|
36377
|
+
className: "divide-y divide-kumo-line",
|
|
36378
|
+
children: fieldsQuery.isLoading ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", {
|
|
36379
|
+
colSpan: 6,
|
|
36380
|
+
className: "px-4 py-8 text-center text-kumo-subtle",
|
|
36381
|
+
children: _t({
|
|
36382
|
+
id: "IMm99K",
|
|
36383
|
+
message: "Loading byline fields…"
|
|
36384
|
+
})
|
|
36385
|
+
}) }) : fieldsQuery.isError ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsxs("td", {
|
|
36386
|
+
colSpan: 6,
|
|
36387
|
+
className: "px-4 py-8 text-center",
|
|
36388
|
+
children: [
|
|
36389
|
+
/* @__PURE__ */ jsx("p", {
|
|
36390
|
+
className: "text-kumo-danger font-medium",
|
|
36391
|
+
children: _t({
|
|
36392
|
+
id: "ORkiLh",
|
|
36393
|
+
message: "Couldn't load byline fields."
|
|
36394
|
+
})
|
|
36395
|
+
}),
|
|
36396
|
+
/* @__PURE__ */ jsx("p", {
|
|
36397
|
+
className: "text-sm text-kumo-subtle mt-1",
|
|
36398
|
+
children: fieldsQuery.error instanceof Error ? fieldsQuery.error.message : _t({
|
|
36399
|
+
id: "W5A0Ly",
|
|
36400
|
+
message: "An unexpected error occurred."
|
|
36401
|
+
})
|
|
36402
|
+
}),
|
|
36403
|
+
/* @__PURE__ */ jsx(Button, {
|
|
36404
|
+
variant: "outline",
|
|
36405
|
+
size: "sm",
|
|
36406
|
+
className: "mt-3",
|
|
36407
|
+
onClick: () => void fieldsQuery.refetch(),
|
|
36408
|
+
children: _t({
|
|
36409
|
+
id: "6gRgw8",
|
|
36410
|
+
message: "Retry"
|
|
36411
|
+
})
|
|
36412
|
+
})
|
|
36413
|
+
]
|
|
36414
|
+
}) }) : fields.length === 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsxs("td", {
|
|
36415
|
+
colSpan: 6,
|
|
36416
|
+
className: "px-4 py-12 text-center text-kumo-subtle",
|
|
36417
|
+
children: [
|
|
36418
|
+
/* @__PURE__ */ jsx(IdentificationCard, {
|
|
36419
|
+
className: "mx-auto h-8 w-8 mb-3 text-kumo-subtle",
|
|
36420
|
+
"aria-hidden": "true"
|
|
36421
|
+
}),
|
|
36422
|
+
/* @__PURE__ */ jsx("p", { children: _t({
|
|
36423
|
+
id: "7XUltI",
|
|
36424
|
+
message: "No byline fields yet."
|
|
36425
|
+
}) }),
|
|
36426
|
+
/* @__PURE__ */ jsx("p", {
|
|
36427
|
+
className: "text-xs mt-1",
|
|
36428
|
+
children: _t({
|
|
36429
|
+
id: "USA0K0",
|
|
36430
|
+
message: "Add fields like \"Job title\" or \"Pronouns\" to enrich every byline."
|
|
36431
|
+
})
|
|
36432
|
+
})
|
|
36433
|
+
]
|
|
36434
|
+
}) }) : fields.map((field, index) => /* @__PURE__ */ jsx(FieldRow, {
|
|
36435
|
+
field,
|
|
36436
|
+
isFirst: index === 0,
|
|
36437
|
+
isLast: index === fields.length - 1,
|
|
36438
|
+
onMoveUp: () => handleMove(index, -1),
|
|
36439
|
+
onMoveDown: () => handleMove(index, 1),
|
|
36440
|
+
onEdit: () => setEditingField(field),
|
|
36441
|
+
onDelete: () => setDeletingField(field),
|
|
36442
|
+
reorderPending: reorderMutation.isPending
|
|
36443
|
+
}, field.id))
|
|
36444
|
+
})]
|
|
36445
|
+
})
|
|
36446
|
+
}),
|
|
36447
|
+
/* @__PURE__ */ jsx(BylineFieldEditor, {
|
|
36448
|
+
open: creating,
|
|
36449
|
+
onOpenChange: (open) => {
|
|
36450
|
+
if (!open) {
|
|
36451
|
+
setCreating(false);
|
|
36452
|
+
createMutation.reset();
|
|
36453
|
+
}
|
|
36454
|
+
},
|
|
36455
|
+
onSave: handleCreate,
|
|
36456
|
+
isSaving: createMutation.isPending,
|
|
36457
|
+
error: createMutation.error
|
|
36458
|
+
}),
|
|
36459
|
+
editingField && /* @__PURE__ */ jsx(BylineFieldEditor, {
|
|
36460
|
+
open: !!editingField,
|
|
36461
|
+
onOpenChange: (open) => {
|
|
36462
|
+
if (!open) {
|
|
36463
|
+
setEditingField(null);
|
|
36464
|
+
updateMutation.reset();
|
|
36465
|
+
}
|
|
36466
|
+
},
|
|
36467
|
+
field: editingField,
|
|
36468
|
+
usageTotal: usageQuery.data?.totalAffectedRows ?? 0,
|
|
36469
|
+
onSave: handleEdit,
|
|
36470
|
+
isSaving: updateMutation.isPending,
|
|
36471
|
+
error: updateMutation.error
|
|
36472
|
+
}, editingField.id),
|
|
36473
|
+
/* @__PURE__ */ jsx(ConfirmDialog, {
|
|
36474
|
+
open: !!deletingField,
|
|
36475
|
+
onClose: () => {
|
|
36476
|
+
setDeletingField(null);
|
|
36477
|
+
deleteMutation.reset();
|
|
36478
|
+
},
|
|
36479
|
+
title: _t({
|
|
36480
|
+
id: "S353b7",
|
|
36481
|
+
message: "Delete byline field?"
|
|
36482
|
+
}),
|
|
36483
|
+
description: deletingField ? deleteUsageQuery.isLoading ? _t({
|
|
36484
|
+
id: "WeKBmZ",
|
|
36485
|
+
message: "Checking how many stored values reference \"{0}\"…",
|
|
36486
|
+
values: { 0: deletingField.label }
|
|
36487
|
+
}) : deleteUsageQuery.isError ? _t({
|
|
36488
|
+
id: "AsyqqW",
|
|
36489
|
+
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.",
|
|
36490
|
+
values: { 0: deletingField.label }
|
|
36491
|
+
}) : deleteUsageQuery.data && deleteUsageQuery.data.totalAffectedRows > 0 ? _t({
|
|
36492
|
+
id: "YjS/z6",
|
|
36493
|
+
message: "Deleting \"{0}\" will also remove {1, plural, one {# stored value} other {# stored values}} across all bylines. This cannot be undone.",
|
|
36494
|
+
values: {
|
|
36495
|
+
0: deletingField.label,
|
|
36496
|
+
1: deleteUsageQuery.data.totalAffectedRows
|
|
36497
|
+
}
|
|
36498
|
+
}) : _t({
|
|
36499
|
+
id: "27Gnm/",
|
|
36500
|
+
message: "Are you sure you want to delete \"{0}\"? No stored values reference this field.",
|
|
36501
|
+
values: { 0: deletingField.label }
|
|
36502
|
+
}) : "",
|
|
36503
|
+
confirmLabel: _t({
|
|
36504
|
+
id: "cnGeoo",
|
|
36505
|
+
message: "Delete"
|
|
36506
|
+
}),
|
|
36507
|
+
pendingLabel: _t({
|
|
36508
|
+
id: "gXQX4x",
|
|
36509
|
+
message: "Deleting…"
|
|
36510
|
+
}),
|
|
36511
|
+
isPending: deleteMutation.isPending,
|
|
36512
|
+
error: deleteMutation.error,
|
|
36513
|
+
onConfirm: () => {
|
|
36514
|
+
if (deletingField) deleteMutation.mutate(deletingField.slug);
|
|
36515
|
+
}
|
|
36516
|
+
})
|
|
36517
|
+
]
|
|
36518
|
+
});
|
|
36519
|
+
}
|
|
36520
|
+
function FieldRow({ field, isFirst, isLast, onMoveUp, onMoveDown, onEdit, onDelete, reorderPending }) {
|
|
36521
|
+
const { _: _t2 } = useLingui();
|
|
36522
|
+
return /* @__PURE__ */ jsxs("tr", {
|
|
36523
|
+
className: "hover:bg-kumo-tint/25",
|
|
36524
|
+
children: [
|
|
36525
|
+
/* @__PURE__ */ jsx("td", {
|
|
36526
|
+
className: "px-4 py-3 font-medium",
|
|
36527
|
+
children: field.label
|
|
36528
|
+
}),
|
|
36529
|
+
/* @__PURE__ */ jsx("td", {
|
|
36530
|
+
className: "px-4 py-3",
|
|
36531
|
+
children: /* @__PURE__ */ jsx("code", {
|
|
36532
|
+
className: "text-sm bg-kumo-tint px-1.5 py-0.5 rounded",
|
|
36533
|
+
children: field.slug
|
|
36534
|
+
})
|
|
36535
|
+
}),
|
|
36536
|
+
/* @__PURE__ */ jsx("td", {
|
|
36537
|
+
className: "px-4 py-3 text-sm",
|
|
36538
|
+
children: field.type
|
|
36539
|
+
}),
|
|
36540
|
+
/* @__PURE__ */ jsx("td", {
|
|
36541
|
+
className: "px-4 py-3 text-sm",
|
|
36542
|
+
children: field.translatable ? _t2({
|
|
36543
|
+
id: "l75CjT",
|
|
36544
|
+
message: "Yes"
|
|
36545
|
+
}) : _t2({
|
|
36546
|
+
id: "Vqmggp",
|
|
36547
|
+
message: "No (shared across translations)"
|
|
36548
|
+
})
|
|
36549
|
+
}),
|
|
36550
|
+
/* @__PURE__ */ jsx("td", {
|
|
36551
|
+
className: "px-4 py-3 text-sm",
|
|
36552
|
+
children: field.required ? _t2({
|
|
36553
|
+
id: "l75CjT",
|
|
36554
|
+
message: "Yes"
|
|
36555
|
+
}) : _t2({
|
|
36556
|
+
id: "1UzENP",
|
|
36557
|
+
message: "No"
|
|
36558
|
+
})
|
|
36559
|
+
}),
|
|
36560
|
+
/* @__PURE__ */ jsx("td", {
|
|
36561
|
+
className: "px-4 py-3 text-end",
|
|
36562
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
36563
|
+
className: "flex items-center justify-end gap-1",
|
|
36564
|
+
children: [
|
|
36565
|
+
/* @__PURE__ */ jsx(Button, {
|
|
36566
|
+
variant: "ghost",
|
|
36567
|
+
shape: "square",
|
|
36568
|
+
size: "sm",
|
|
36569
|
+
"aria-label": _t2({
|
|
36570
|
+
id: "ko+NNU",
|
|
36571
|
+
message: "Move \"{0}\" up",
|
|
36572
|
+
values: { 0: field.label }
|
|
36573
|
+
}),
|
|
36574
|
+
disabled: isFirst || reorderPending,
|
|
36575
|
+
onClick: onMoveUp,
|
|
36576
|
+
children: /* @__PURE__ */ jsx(ArrowUp, {
|
|
36577
|
+
className: "h-4 w-4",
|
|
36578
|
+
"aria-hidden": "true"
|
|
36579
|
+
})
|
|
36580
|
+
}),
|
|
36581
|
+
/* @__PURE__ */ jsx(Button, {
|
|
36582
|
+
variant: "ghost",
|
|
36583
|
+
shape: "square",
|
|
36584
|
+
size: "sm",
|
|
36585
|
+
"aria-label": _t2({
|
|
36586
|
+
id: "Q23Rqr",
|
|
36587
|
+
message: "Move \"{0}\" down",
|
|
36588
|
+
values: { 0: field.label }
|
|
36589
|
+
}),
|
|
36590
|
+
disabled: isLast || reorderPending,
|
|
36591
|
+
onClick: onMoveDown,
|
|
36592
|
+
children: /* @__PURE__ */ jsx(ArrowDown, {
|
|
36593
|
+
className: "h-4 w-4",
|
|
36594
|
+
"aria-hidden": "true"
|
|
36595
|
+
})
|
|
36596
|
+
}),
|
|
36597
|
+
/* @__PURE__ */ jsx(Button, {
|
|
36598
|
+
variant: "ghost",
|
|
36599
|
+
shape: "square",
|
|
36600
|
+
size: "sm",
|
|
36601
|
+
"aria-label": _t2({
|
|
36602
|
+
id: "N6j2JH",
|
|
36603
|
+
message: "Edit {0}",
|
|
36604
|
+
values: { 0: field.label }
|
|
36605
|
+
}),
|
|
36606
|
+
onClick: onEdit,
|
|
36607
|
+
children: /* @__PURE__ */ jsx(Pencil, {
|
|
36608
|
+
className: "h-4 w-4",
|
|
36609
|
+
"aria-hidden": "true"
|
|
36610
|
+
})
|
|
36611
|
+
}),
|
|
36612
|
+
/* @__PURE__ */ jsx(Button, {
|
|
36613
|
+
variant: "ghost",
|
|
36614
|
+
shape: "square",
|
|
36615
|
+
size: "sm",
|
|
36616
|
+
"aria-label": _t2({
|
|
36617
|
+
id: "Y2tU6I",
|
|
36618
|
+
message: "Delete {0}",
|
|
36619
|
+
values: { 0: field.label }
|
|
36620
|
+
}),
|
|
36621
|
+
onClick: onDelete,
|
|
36622
|
+
children: /* @__PURE__ */ jsx(Trash, {
|
|
36623
|
+
className: "h-4 w-4 text-kumo-danger",
|
|
36624
|
+
"aria-hidden": "true"
|
|
36625
|
+
})
|
|
36626
|
+
})
|
|
36627
|
+
]
|
|
36628
|
+
})
|
|
36629
|
+
})
|
|
36630
|
+
]
|
|
36631
|
+
});
|
|
36632
|
+
}
|
|
36633
|
+
|
|
35288
36634
|
//#endregion
|
|
35289
36635
|
//#region src/routes/bylines.tsx
|
|
35290
36636
|
/**
|
|
@@ -35301,7 +36647,8 @@ function toFormState(byline) {
|
|
|
35301
36647
|
bio: "",
|
|
35302
36648
|
websiteUrl: "",
|
|
35303
36649
|
userId: null,
|
|
35304
|
-
isGuest: false
|
|
36650
|
+
isGuest: false,
|
|
36651
|
+
customFields: {}
|
|
35305
36652
|
};
|
|
35306
36653
|
return {
|
|
35307
36654
|
slug: byline.slug,
|
|
@@ -35309,7 +36656,8 @@ function toFormState(byline) {
|
|
|
35309
36656
|
bio: byline.bio ?? "",
|
|
35310
36657
|
websiteUrl: byline.websiteUrl ?? "",
|
|
35311
36658
|
userId: byline.userId,
|
|
35312
|
-
isGuest: byline.isGuest
|
|
36659
|
+
isGuest: byline.isGuest,
|
|
36660
|
+
customFields: byline.customFields ?? {}
|
|
35313
36661
|
};
|
|
35314
36662
|
}
|
|
35315
36663
|
function getUserLabel(user) {
|
|
@@ -35322,6 +36670,7 @@ function BylinesPage() {
|
|
|
35322
36670
|
const navigate = useNavigate$1();
|
|
35323
36671
|
const { locale: routeLocale } = useSearch({ from: "/_admin/bylines" });
|
|
35324
36672
|
const [search, setSearch] = React$1.useState("");
|
|
36673
|
+
const debouncedSearch = useDebouncedValue$1(search, 300);
|
|
35325
36674
|
const [guestFilter, setGuestFilter] = React$1.useState("all");
|
|
35326
36675
|
const [selectedId, setSelectedId] = React$1.useState(null);
|
|
35327
36676
|
const [showDeleteConfirm, setShowDeleteConfirm] = React$1.useState(false);
|
|
@@ -35333,6 +36682,8 @@ function BylinesPage() {
|
|
|
35333
36682
|
});
|
|
35334
36683
|
const i18n = useI18nConfig(manifest);
|
|
35335
36684
|
const isMultiLocale = !!i18n && i18n.locales.length > 1;
|
|
36685
|
+
const { data: currentUser } = useCurrentUser();
|
|
36686
|
+
const canManageBylineSchema = (currentUser?.role ?? 0) >= BYLINE_SCHEMA_NAV_ITEM.minRole;
|
|
35336
36687
|
const activeLocale = routeLocale ?? i18n?.defaultLocale ?? void 0;
|
|
35337
36688
|
const handleLocaleChange = (locale) => {
|
|
35338
36689
|
navigate({
|
|
@@ -35344,12 +36695,12 @@ function BylinesPage() {
|
|
|
35344
36695
|
const { data, isLoading, error } = useQuery({
|
|
35345
36696
|
queryKey: [
|
|
35346
36697
|
"bylines",
|
|
35347
|
-
|
|
36698
|
+
debouncedSearch,
|
|
35348
36699
|
guestFilter,
|
|
35349
36700
|
activeLocale ?? null
|
|
35350
36701
|
],
|
|
35351
36702
|
queryFn: () => fetchBylines({
|
|
35352
|
-
search:
|
|
36703
|
+
search: debouncedSearch || void 0,
|
|
35353
36704
|
isGuest: guestFilter === "all" ? void 0 : guestFilter === "guest",
|
|
35354
36705
|
locale: activeLocale,
|
|
35355
36706
|
limit: 50
|
|
@@ -35366,6 +36717,12 @@ function BylinesPage() {
|
|
|
35366
36717
|
queryFn: () => fetchUsers({ limit: 100 })
|
|
35367
36718
|
});
|
|
35368
36719
|
const users = usersData?.items ?? [];
|
|
36720
|
+
const { data: customFieldsList, error: customFieldsError } = useQuery({
|
|
36721
|
+
queryKey: ["byline-fields"],
|
|
36722
|
+
queryFn: listBylineFields,
|
|
36723
|
+
staleTime: 60 * 1e3
|
|
36724
|
+
});
|
|
36725
|
+
const customFieldDefs = customFieldsList?.items ?? [];
|
|
35369
36726
|
const loadMoreMutation = useMutation({
|
|
35370
36727
|
mutationFn: async (snapshot) => {
|
|
35371
36728
|
return {
|
|
@@ -35381,7 +36738,7 @@ function BylinesPage() {
|
|
|
35381
36738
|
},
|
|
35382
36739
|
onSuccess: ({ result, snapshot }) => {
|
|
35383
36740
|
if (!loadMoreSnapshotMatches(snapshot, {
|
|
35384
|
-
search,
|
|
36741
|
+
search: debouncedSearch,
|
|
35385
36742
|
guestFilter,
|
|
35386
36743
|
locale: activeLocale
|
|
35387
36744
|
})) return;
|
|
@@ -35406,31 +36763,38 @@ function BylinesPage() {
|
|
|
35406
36763
|
enabled: !!selectedId && isMultiLocale
|
|
35407
36764
|
});
|
|
35408
36765
|
const createMutation = useMutation({
|
|
35409
|
-
mutationFn: () =>
|
|
35410
|
-
|
|
35411
|
-
|
|
35412
|
-
|
|
35413
|
-
|
|
35414
|
-
|
|
35415
|
-
|
|
35416
|
-
|
|
35417
|
-
|
|
36766
|
+
mutationFn: () => {
|
|
36767
|
+
const body = {
|
|
36768
|
+
slug: form.slug,
|
|
36769
|
+
displayName: form.displayName,
|
|
36770
|
+
bio: form.bio || null,
|
|
36771
|
+
websiteUrl: form.websiteUrl || null,
|
|
36772
|
+
userId: form.userId,
|
|
36773
|
+
isGuest: form.isGuest,
|
|
36774
|
+
locale: activeLocale
|
|
36775
|
+
};
|
|
36776
|
+
if (!customFieldsError && Object.keys(form.customFields).length > 0) body.customFields = form.customFields;
|
|
36777
|
+
return createByline(body);
|
|
36778
|
+
},
|
|
35418
36779
|
onSuccess: (created) => {
|
|
35419
36780
|
queryClient.invalidateQueries({ queryKey: ["bylines"] });
|
|
36781
|
+
queryClient.invalidateQueries({ queryKey: ["byline", created.id] });
|
|
35420
36782
|
setSelectedId(created.id);
|
|
35421
36783
|
}
|
|
35422
36784
|
});
|
|
35423
36785
|
const updateMutation = useMutation({
|
|
35424
36786
|
mutationFn: () => {
|
|
35425
36787
|
if (!selectedId) throw new Error("No byline selected");
|
|
35426
|
-
|
|
36788
|
+
const body = {
|
|
35427
36789
|
slug: form.slug,
|
|
35428
36790
|
displayName: form.displayName,
|
|
35429
36791
|
bio: form.bio || null,
|
|
35430
36792
|
websiteUrl: form.websiteUrl || null,
|
|
35431
36793
|
userId: form.userId,
|
|
35432
36794
|
isGuest: form.isGuest
|
|
35433
|
-
}
|
|
36795
|
+
};
|
|
36796
|
+
if (!customFieldsError) body.customFields = form.customFields;
|
|
36797
|
+
return updateByline(selectedId, body);
|
|
35434
36798
|
},
|
|
35435
36799
|
onSuccess: () => {
|
|
35436
36800
|
queryClient.invalidateQueries({ queryKey: ["bylines"] });
|
|
@@ -35468,7 +36832,7 @@ function BylinesPage() {
|
|
|
35468
36832
|
setSelectedId(created.id);
|
|
35469
36833
|
}
|
|
35470
36834
|
});
|
|
35471
|
-
if (isLoading) return /* @__PURE__ */ jsx("div", {
|
|
36835
|
+
if (isLoading && !data) return /* @__PURE__ */ jsx("div", {
|
|
35472
36836
|
className: "flex items-center justify-center min-h-[30vh]",
|
|
35473
36837
|
children: /* @__PURE__ */ jsx(Loader, {})
|
|
35474
36838
|
});
|
|
@@ -35485,21 +36849,32 @@ function BylinesPage() {
|
|
|
35485
36849
|
return /* @__PURE__ */ jsxs("div", {
|
|
35486
36850
|
className: "space-y-4",
|
|
35487
36851
|
children: [
|
|
35488
|
-
|
|
35489
|
-
className: "flex items-center justify-between",
|
|
35490
|
-
children: [/* @__PURE__ */ jsx("
|
|
36852
|
+
/* @__PURE__ */ jsxs("div", {
|
|
36853
|
+
className: "flex items-center justify-between gap-3",
|
|
36854
|
+
children: [/* @__PURE__ */ jsx("h1", {
|
|
35491
36855
|
className: "text-2xl font-semibold",
|
|
35492
36856
|
children: _t({
|
|
35493
36857
|
id: "xLGa7J",
|
|
35494
36858
|
message: "Bylines"
|
|
35495
36859
|
})
|
|
35496
|
-
})
|
|
35497
|
-
|
|
35498
|
-
|
|
35499
|
-
|
|
35500
|
-
|
|
36860
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
36861
|
+
className: "flex items-center gap-2",
|
|
36862
|
+
children: [canManageBylineSchema && /* @__PURE__ */ jsx(RouterLinkButton, {
|
|
36863
|
+
to: BYLINE_SCHEMA_NAV_ITEM.to,
|
|
36864
|
+
variant: "secondary",
|
|
36865
|
+
icon: /* @__PURE__ */ jsx(IdentificationCard, {}),
|
|
36866
|
+
children: _t({
|
|
36867
|
+
id: "5YHQju",
|
|
36868
|
+
message: "Byline schema"
|
|
36869
|
+
})
|
|
36870
|
+
}), isMultiLocale && i18n && activeLocale && /* @__PURE__ */ jsx(LocaleSwitcher, {
|
|
36871
|
+
locales: i18n.locales,
|
|
36872
|
+
defaultLocale: i18n.defaultLocale,
|
|
36873
|
+
value: activeLocale,
|
|
36874
|
+
onChange: handleLocaleChange
|
|
36875
|
+
})]
|
|
35501
36876
|
})]
|
|
35502
|
-
})
|
|
36877
|
+
}),
|
|
35503
36878
|
/* @__PURE__ */ jsxs("div", {
|
|
35504
36879
|
className: "grid grid-cols-1 gap-6 lg:grid-cols-[320px_1fr]",
|
|
35505
36880
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
@@ -35586,7 +36961,7 @@ function BylinesPage() {
|
|
|
35586
36961
|
variant: "secondary",
|
|
35587
36962
|
className: "w-full mt-2",
|
|
35588
36963
|
onClick: () => loadMoreMutation.mutate({
|
|
35589
|
-
search,
|
|
36964
|
+
search: debouncedSearch,
|
|
35590
36965
|
guestFilter,
|
|
35591
36966
|
locale: activeLocale,
|
|
35592
36967
|
cursor: nextCursor
|
|
@@ -35687,6 +37062,33 @@ function BylinesPage() {
|
|
|
35687
37062
|
},
|
|
35688
37063
|
className: "w-full"
|
|
35689
37064
|
}),
|
|
37065
|
+
customFieldDefs.length > 0 && customFieldDefs.map((field) => /* @__PURE__ */ jsx(CustomFieldInput, {
|
|
37066
|
+
field,
|
|
37067
|
+
value: form.customFields[field.slug],
|
|
37068
|
+
onChange: (next) => setForm((prev) => ({
|
|
37069
|
+
...prev,
|
|
37070
|
+
customFields: {
|
|
37071
|
+
...prev.customFields,
|
|
37072
|
+
[field.slug]: next
|
|
37073
|
+
}
|
|
37074
|
+
}))
|
|
37075
|
+
}, field.id)),
|
|
37076
|
+
customFieldsError && /* @__PURE__ */ jsxs("div", {
|
|
37077
|
+
className: "rounded-md border border-kumo-danger/40 bg-kumo-danger/5 p-3 text-sm",
|
|
37078
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
37079
|
+
className: "font-medium text-kumo-danger",
|
|
37080
|
+
children: _t({
|
|
37081
|
+
id: "Jci99j",
|
|
37082
|
+
message: "Couldn't load custom fields."
|
|
37083
|
+
})
|
|
37084
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
37085
|
+
className: "text-xs text-kumo-subtle mt-1",
|
|
37086
|
+
children: _t({
|
|
37087
|
+
id: "X+FaLl",
|
|
37088
|
+
message: "You can still edit the fixed fields above. Saving will not touch any stored custom-field values."
|
|
37089
|
+
})
|
|
37090
|
+
})]
|
|
37091
|
+
}),
|
|
35690
37092
|
/* @__PURE__ */ jsx(Switch, {
|
|
35691
37093
|
label: _t({
|
|
35692
37094
|
id: "6CRaxp",
|
|
@@ -35780,6 +37182,66 @@ function BylinesPage() {
|
|
|
35780
37182
|
]
|
|
35781
37183
|
});
|
|
35782
37184
|
}
|
|
37185
|
+
/**
|
|
37186
|
+
* Renders a single registered byline custom field as the appropriate
|
|
37187
|
+
* Kumo input for its type (Phase 6 of #1174).
|
|
37188
|
+
*
|
|
37189
|
+
* Five v1 type cases mirror `BylineFieldType` and the inputs that
|
|
37190
|
+
* `BylineFieldEditor` allows admins to register. Empty string inputs
|
|
37191
|
+
* coerce to `null` on save so the repo's "null clears the row"
|
|
37192
|
+
* storage semantic engages — server-side `BylineRepository.update`
|
|
37193
|
+
* deletes the value row rather than storing an empty-string JSON.
|
|
37194
|
+
*
|
|
37195
|
+
* `field.required` adds a `*` after the label as a visual hint; the
|
|
37196
|
+
* server is authoritative on validation (Phase 6 ACs don't include a
|
|
37197
|
+
* client-side required check — the registry's `required` flag is
|
|
37198
|
+
* descriptive rather than enforced in the write path today).
|
|
37199
|
+
*/
|
|
37200
|
+
function CustomFieldInput({ field, value, onChange }) {
|
|
37201
|
+
const { _: _t2 } = useLingui();
|
|
37202
|
+
const label = field.required ? `${field.label} *` : field.label;
|
|
37203
|
+
const stringValue = typeof value === "string" ? value : "";
|
|
37204
|
+
switch (field.type) {
|
|
37205
|
+
case "string": return /* @__PURE__ */ jsx(Input, {
|
|
37206
|
+
label,
|
|
37207
|
+
value: stringValue,
|
|
37208
|
+
onChange: (e) => onChange(e.target.value === "" ? null : e.target.value)
|
|
37209
|
+
});
|
|
37210
|
+
case "text": return /* @__PURE__ */ jsx(InputArea, {
|
|
37211
|
+
label,
|
|
37212
|
+
value: stringValue,
|
|
37213
|
+
onChange: (e) => onChange(e.target.value === "" ? null : e.target.value),
|
|
37214
|
+
rows: 3
|
|
37215
|
+
});
|
|
37216
|
+
case "url": return /* @__PURE__ */ jsx(Input, {
|
|
37217
|
+
type: "url",
|
|
37218
|
+
label,
|
|
37219
|
+
value: stringValue,
|
|
37220
|
+
onChange: (e) => onChange(e.target.value === "" ? null : e.target.value)
|
|
37221
|
+
});
|
|
37222
|
+
case "boolean": return /* @__PURE__ */ jsx(Switch, {
|
|
37223
|
+
label,
|
|
37224
|
+
checked: value === true,
|
|
37225
|
+
onCheckedChange: (checked) => onChange(checked)
|
|
37226
|
+
});
|
|
37227
|
+
case "select": {
|
|
37228
|
+
const options = field.validation?.options ?? [];
|
|
37229
|
+
const items = Object.create(null);
|
|
37230
|
+
items[""] = _t2({
|
|
37231
|
+
id: "9npDF6",
|
|
37232
|
+
message: "-- Select --"
|
|
37233
|
+
});
|
|
37234
|
+
for (const opt of options) items[opt] = opt;
|
|
37235
|
+
return /* @__PURE__ */ jsx(Select, {
|
|
37236
|
+
label,
|
|
37237
|
+
value: stringValue,
|
|
37238
|
+
onValueChange: (v) => onChange(!v ? null : v),
|
|
37239
|
+
items,
|
|
37240
|
+
className: "w-full"
|
|
37241
|
+
});
|
|
37242
|
+
}
|
|
37243
|
+
}
|
|
37244
|
+
}
|
|
35783
37245
|
|
|
35784
37246
|
//#endregion
|
|
35785
37247
|
//#region src/components/users/RoleBadge.tsx
|
|
@@ -37143,13 +38605,15 @@ function ContentListPage() {
|
|
|
37143
38605
|
field: "updatedAt",
|
|
37144
38606
|
direction: "desc"
|
|
37145
38607
|
});
|
|
38608
|
+
const [searchTerm, setSearchTerm] = React$1.useState("");
|
|
37146
38609
|
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, error } = useInfiniteQuery({
|
|
37147
38610
|
queryKey: [
|
|
37148
38611
|
"content",
|
|
37149
38612
|
collection,
|
|
37150
38613
|
{
|
|
37151
38614
|
locale: activeLocale,
|
|
37152
|
-
sort
|
|
38615
|
+
sort,
|
|
38616
|
+
search: searchTerm
|
|
37153
38617
|
}
|
|
37154
38618
|
],
|
|
37155
38619
|
queryFn: ({ pageParam }) => fetchContentList(collection, {
|
|
@@ -37157,7 +38621,8 @@ function ContentListPage() {
|
|
|
37157
38621
|
cursor: pageParam,
|
|
37158
38622
|
limit: 100,
|
|
37159
38623
|
orderBy: sort.field,
|
|
37160
|
-
order: sort.direction
|
|
38624
|
+
order: sort.direction,
|
|
38625
|
+
search: searchTerm || void 0
|
|
37161
38626
|
}),
|
|
37162
38627
|
initialPageParam: void 0,
|
|
37163
38628
|
getNextPageParam: (lastPage) => lastPage.nextCursor,
|
|
@@ -37296,7 +38761,8 @@ function ContentListPage() {
|
|
|
37296
38761
|
urlPattern: collectionConfig.urlPattern,
|
|
37297
38762
|
sort,
|
|
37298
38763
|
onSortChange: setSort,
|
|
37299
|
-
total
|
|
38764
|
+
total,
|
|
38765
|
+
onSearchChange: setSearchTerm
|
|
37300
38766
|
});
|
|
37301
38767
|
}
|
|
37302
38768
|
const contentNewRoute = createRoute({
|
|
@@ -37911,11 +39377,18 @@ const mediaRoute = createRoute({
|
|
|
37911
39377
|
});
|
|
37912
39378
|
function MediaPage() {
|
|
37913
39379
|
const queryClient = useQueryClient();
|
|
39380
|
+
const [search, setSearch] = React$1.useState("");
|
|
39381
|
+
const [mimeFilter, setMimeFilter] = React$1.useState(void 0);
|
|
37914
39382
|
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, error } = useInfiniteQuery({
|
|
37915
|
-
queryKey: ["media"
|
|
39383
|
+
queryKey: ["media", {
|
|
39384
|
+
search,
|
|
39385
|
+
mime: Array.isArray(mimeFilter) ? mimeFilter.join(",") : mimeFilter ?? ""
|
|
39386
|
+
}],
|
|
37916
39387
|
queryFn: ({ pageParam }) => fetchMediaList({
|
|
37917
39388
|
cursor: pageParam,
|
|
37918
|
-
limit: 100
|
|
39389
|
+
limit: 100,
|
|
39390
|
+
search: search || void 0,
|
|
39391
|
+
mimeType: mimeFilter
|
|
37919
39392
|
}),
|
|
37920
39393
|
initialPageParam: void 0,
|
|
37921
39394
|
getNextPageParam: (lastPage) => lastPage.nextCursor
|
|
@@ -37942,7 +39415,9 @@ function MediaPage() {
|
|
|
37942
39415
|
hasMore: !!hasNextPage,
|
|
37943
39416
|
onLoadMore: () => void fetchNextPage(),
|
|
37944
39417
|
onUpload: (file) => uploadMutation.mutate(file),
|
|
37945
|
-
onDelete: (id) => deleteMutation.mutate(id)
|
|
39418
|
+
onDelete: (id) => deleteMutation.mutate(id),
|
|
39419
|
+
onLocalSearchChange: setSearch,
|
|
39420
|
+
onLocalMimeFilterChange: setMimeFilter
|
|
37946
39421
|
});
|
|
37947
39422
|
}
|
|
37948
39423
|
const commentsRoute = createRoute({
|
|
@@ -38309,6 +39784,11 @@ const bylinesRoute = createRoute({
|
|
|
38309
39784
|
component: BylinesPage,
|
|
38310
39785
|
validateSearch: parseBylinesLocaleSearch
|
|
38311
39786
|
});
|
|
39787
|
+
const bylineSchemaRoute = createRoute({
|
|
39788
|
+
getParentRoute: () => adminLayoutRoute,
|
|
39789
|
+
path: "/byline-schema",
|
|
39790
|
+
component: BylineSchemaPage
|
|
39791
|
+
});
|
|
38312
39792
|
const contentTypesListRoute = createRoute({
|
|
38313
39793
|
getParentRoute: () => adminLayoutRoute,
|
|
38314
39794
|
path: "/content-types",
|
|
@@ -38535,6 +40015,7 @@ const adminRoutes = adminLayoutRoute.addChildren([
|
|
|
38535
40015
|
taxonomyRoute,
|
|
38536
40016
|
usersRoute,
|
|
38537
40017
|
bylinesRoute,
|
|
40018
|
+
bylineSchemaRoute,
|
|
38538
40019
|
widgetsRoute,
|
|
38539
40020
|
settingsRoute,
|
|
38540
40021
|
generalSettingsRoute,
|
|
@@ -38692,5 +40173,5 @@ function AdminApp({ pluginAdmins = EMPTY_PLUGINS, authProviders = EMPTY_AUTH_PRO
|
|
|
38692
40173
|
}
|
|
38693
40174
|
|
|
38694
40175
|
//#endregion
|
|
38695
|
-
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 };
|
|
40176
|
+
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 };
|
|
38696
40177
|
//# sourceMappingURL=index.js.map
|