@emdash-cms/admin 0.14.0 → 0.15.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-D32XwRVp.js → LocaleDirectionProvider-C-w4lOU2.js} +17 -17
- package/dist/{LocaleDirectionProvider-D32XwRVp.js.map → LocaleDirectionProvider-C-w4lOU2.js.map} +1 -1
- package/dist/index.d.ts +56 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1398 -563
- 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.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/zh-CN/messages.mjs +1 -1
- package/dist/locales/zh-TW/messages.mjs +1 -1
- package/dist/messages-B1vNWgt7.js +6 -0
- package/dist/messages-B1vNWgt7.js.map +1 -0
- package/dist/messages-B4SjKBpv.js +6 -0
- package/dist/messages-B4SjKBpv.js.map +1 -0
- package/dist/messages-BMFLZDHo.js +6 -0
- package/dist/messages-BMFLZDHo.js.map +1 -0
- package/dist/messages-BWDz4uQu.js +6 -0
- package/dist/messages-BWDz4uQu.js.map +1 -0
- package/dist/messages-BXt6OWYC.js +6 -0
- package/dist/messages-BXt6OWYC.js.map +1 -0
- package/dist/messages-CA-O-TjM.js +6 -0
- package/dist/messages-CA-O-TjM.js.map +1 -0
- package/dist/messages-CX3dih9c.js +6 -0
- package/dist/messages-CX3dih9c.js.map +1 -0
- package/dist/messages-CXZHQHKt.js +6 -0
- package/dist/messages-CXZHQHKt.js.map +1 -0
- package/dist/messages-Cz2f6Su-.js +6 -0
- package/dist/messages-Cz2f6Su-.js.map +1 -0
- package/dist/messages-DW1BWiJ5.js +6 -0
- package/dist/messages-DW1BWiJ5.js.map +1 -0
- package/dist/messages-DpMKfmmV.js +6 -0
- package/dist/messages-DpMKfmmV.js.map +1 -0
- package/dist/messages-hYPn9ilH.js +6 -0
- package/dist/messages-hYPn9ilH.js.map +1 -0
- package/dist/messages-qYhBOhtE.js +6 -0
- package/dist/messages-qYhBOhtE.js.map +1 -0
- package/dist/messages-rxCTcw_6.js +6 -0
- package/dist/messages-rxCTcw_6.js.map +1 -0
- package/dist/messages-wpqHOBfn.js +6 -0
- package/dist/messages-wpqHOBfn.js.map +1 -0
- package/dist/messages-zuRDwn8n.js +6 -0
- package/dist/messages-zuRDwn8n.js.map +1 -0
- package/dist/{plugins-qrjvPK4g.js → plugins-BZzztFdK.js} +3 -3
- package/dist/plugins-BZzztFdK.js.map +1 -0
- package/dist/styles.css +1 -1
- package/package.json +5 -4
- package/dist/messages-35lkat0c.js +0 -6
- package/dist/messages-35lkat0c.js.map +0 -1
- package/dist/messages-BSSaBU23.js +0 -6
- package/dist/messages-BSSaBU23.js.map +0 -1
- package/dist/messages-Boy6iMLn.js +0 -6
- package/dist/messages-Boy6iMLn.js.map +0 -1
- package/dist/messages-BwIV9n3i.js +0 -6
- package/dist/messages-BwIV9n3i.js.map +0 -1
- package/dist/messages-CGrAqIHP.js +0 -6
- package/dist/messages-CGrAqIHP.js.map +0 -1
- package/dist/messages-CgLmZ-Xe.js +0 -6
- package/dist/messages-CgLmZ-Xe.js.map +0 -1
- package/dist/messages-Ckz9OJjP.js +0 -6
- package/dist/messages-Ckz9OJjP.js.map +0 -1
- package/dist/messages-CrY9gw3p.js +0 -6
- package/dist/messages-CrY9gw3p.js.map +0 -1
- package/dist/messages-CtrOZaXa.js +0 -6
- package/dist/messages-CtrOZaXa.js.map +0 -1
- package/dist/messages-CxW3nwfb.js +0 -6
- package/dist/messages-CxW3nwfb.js.map +0 -1
- package/dist/messages-D7CyjcVC.js +0 -6
- package/dist/messages-D7CyjcVC.js.map +0 -1
- package/dist/messages-Dhy7Stmm.js +0 -6
- package/dist/messages-Dhy7Stmm.js.map +0 -1
- package/dist/messages-FyJi9vss.js +0 -6
- package/dist/messages-FyJi9vss.js.map +0 -1
- package/dist/messages-JMBMiRyI.js +0 -6
- package/dist/messages-JMBMiRyI.js.map +0 -1
- package/dist/messages-pPTbyAIO.js +0 -6
- package/dist/messages-pPTbyAIO.js.map +0 -1
- package/dist/messages-zl21KaEb.js +0 -6
- package/dist/messages-zl21KaEb.js.map +0 -1
- package/dist/plugins-qrjvPK4g.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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-
|
|
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-
|
|
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-C-w4lOU2.js";
|
|
3
3
|
import "./locales/index.js";
|
|
4
|
-
import { 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";
|
|
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
7
|
import { QueryClient, QueryClientProvider, useInfiniteQuery, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
@@ -27,12 +27,13 @@ import { TableHeader } from "@tiptap/extension-table-header";
|
|
|
27
27
|
import { TableRow } from "@tiptap/extension-table-row";
|
|
28
28
|
import TextAlign from "@tiptap/extension-text-align";
|
|
29
29
|
import Typography from "@tiptap/extension-typography";
|
|
30
|
-
import { EditorContent, Node as Node$2, NodeViewWrapper, ReactNodeViewRenderer, mergeAttributes as mergeAttributes$1, useEditor, useEditorState } from "@tiptap/react";
|
|
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
33
|
import { Plugin, PluginKey } from "@tiptap/pm/state";
|
|
34
34
|
import { Decoration, DecorationSet } from "@tiptap/pm/view";
|
|
35
35
|
import { createPortal } from "react-dom";
|
|
36
|
+
import CodeBlock$1 from "@tiptap/extension-code-block";
|
|
36
37
|
import { DragHandle } from "@tiptap/extension-drag-handle-react";
|
|
37
38
|
import { isAllowedUri } from "@tiptap/extension-link";
|
|
38
39
|
import { BlockRenderer } from "@emdash-cms/blocks";
|
|
@@ -1956,6 +1957,7 @@ async function fetchBylines(options) {
|
|
|
1956
1957
|
if (options?.search) params.set("search", options.search);
|
|
1957
1958
|
if (options?.isGuest !== void 0) params.set("isGuest", String(options.isGuest));
|
|
1958
1959
|
if (options?.userId) params.set("userId", options.userId);
|
|
1960
|
+
if (options?.locale) params.set("locale", options.locale);
|
|
1959
1961
|
if (options?.cursor) params.set("cursor", options.cursor);
|
|
1960
1962
|
if (options?.limit) params.set("limit", String(options.limit));
|
|
1961
1963
|
return parseApiResponse(await apiFetch(`${API_BASE}/admin/bylines${params.toString() ? `?${params}` : ""}`), "Failed to fetch bylines");
|
|
@@ -1984,6 +1986,26 @@ async function deleteByline(id) {
|
|
|
1984
1986
|
message: "Failed to delete byline"
|
|
1985
1987
|
}));
|
|
1986
1988
|
}
|
|
1989
|
+
/**
|
|
1990
|
+
* Fetch every translation of a byline (siblings sharing the same
|
|
1991
|
+
* translation_group).
|
|
1992
|
+
*/
|
|
1993
|
+
async function fetchBylineTranslations(id) {
|
|
1994
|
+
return parseApiResponse(await apiFetch(`${API_BASE}/admin/bylines/${id}/translations`), "Failed to fetch byline translations");
|
|
1995
|
+
}
|
|
1996
|
+
/**
|
|
1997
|
+
* Create a new locale variant of a byline. The new row joins the source's
|
|
1998
|
+
* `translation_group`. Body defaults — slug, display name, avatar, website —
|
|
1999
|
+
* inherit from the source when omitted, so editors only have to fill in the
|
|
2000
|
+
* localized bio (and optionally a localized display name).
|
|
2001
|
+
*/
|
|
2002
|
+
async function createBylineTranslation(id, input) {
|
|
2003
|
+
return parseApiResponse(await apiFetch(`${API_BASE}/admin/bylines/${id}/translations`, {
|
|
2004
|
+
method: "POST",
|
|
2005
|
+
headers: { "Content-Type": "application/json" },
|
|
2006
|
+
body: JSON.stringify(input)
|
|
2007
|
+
}), "Failed to create byline translation");
|
|
2008
|
+
}
|
|
1987
2009
|
|
|
1988
2010
|
//#endregion
|
|
1989
2011
|
//#region src/lib/api/menus.ts
|
|
@@ -5525,6 +5547,392 @@ function Suggestion({ pluginKey = SuggestionPluginKey, editor, char = "@", allow
|
|
|
5525
5547
|
}
|
|
5526
5548
|
var index_default = Suggestion;
|
|
5527
5549
|
|
|
5550
|
+
//#endregion
|
|
5551
|
+
//#region src/components/editor/codeBlockLanguages.ts
|
|
5552
|
+
const CODE_BLOCK_LANGUAGES = [
|
|
5553
|
+
{
|
|
5554
|
+
id: "plaintext",
|
|
5555
|
+
label: "Plain text",
|
|
5556
|
+
aliases: [
|
|
5557
|
+
"text",
|
|
5558
|
+
"plain",
|
|
5559
|
+
"txt"
|
|
5560
|
+
]
|
|
5561
|
+
},
|
|
5562
|
+
{
|
|
5563
|
+
id: "astro",
|
|
5564
|
+
label: "Astro"
|
|
5565
|
+
},
|
|
5566
|
+
{
|
|
5567
|
+
id: "bash",
|
|
5568
|
+
label: "Bash",
|
|
5569
|
+
aliases: [
|
|
5570
|
+
"sh",
|
|
5571
|
+
"shell",
|
|
5572
|
+
"zsh"
|
|
5573
|
+
]
|
|
5574
|
+
},
|
|
5575
|
+
{
|
|
5576
|
+
id: "c",
|
|
5577
|
+
label: "C"
|
|
5578
|
+
},
|
|
5579
|
+
{
|
|
5580
|
+
id: "cpp",
|
|
5581
|
+
label: "C++",
|
|
5582
|
+
aliases: ["c++"]
|
|
5583
|
+
},
|
|
5584
|
+
{
|
|
5585
|
+
id: "csharp",
|
|
5586
|
+
label: "C#",
|
|
5587
|
+
aliases: ["cs", "c#"]
|
|
5588
|
+
},
|
|
5589
|
+
{
|
|
5590
|
+
id: "css",
|
|
5591
|
+
label: "CSS"
|
|
5592
|
+
},
|
|
5593
|
+
{
|
|
5594
|
+
id: "diff",
|
|
5595
|
+
label: "Diff",
|
|
5596
|
+
aliases: ["patch"]
|
|
5597
|
+
},
|
|
5598
|
+
{
|
|
5599
|
+
id: "dockerfile",
|
|
5600
|
+
label: "Dockerfile",
|
|
5601
|
+
aliases: ["docker"]
|
|
5602
|
+
},
|
|
5603
|
+
{
|
|
5604
|
+
id: "go",
|
|
5605
|
+
label: "Go",
|
|
5606
|
+
aliases: ["golang"]
|
|
5607
|
+
},
|
|
5608
|
+
{
|
|
5609
|
+
id: "graphql",
|
|
5610
|
+
label: "GraphQL",
|
|
5611
|
+
aliases: ["gql"]
|
|
5612
|
+
},
|
|
5613
|
+
{
|
|
5614
|
+
id: "html",
|
|
5615
|
+
label: "HTML"
|
|
5616
|
+
},
|
|
5617
|
+
{
|
|
5618
|
+
id: "java",
|
|
5619
|
+
label: "Java"
|
|
5620
|
+
},
|
|
5621
|
+
{
|
|
5622
|
+
id: "javascript",
|
|
5623
|
+
label: "JavaScript",
|
|
5624
|
+
aliases: ["js"]
|
|
5625
|
+
},
|
|
5626
|
+
{
|
|
5627
|
+
id: "json",
|
|
5628
|
+
label: "JSON"
|
|
5629
|
+
},
|
|
5630
|
+
{
|
|
5631
|
+
id: "jsx",
|
|
5632
|
+
label: "JSX"
|
|
5633
|
+
},
|
|
5634
|
+
{
|
|
5635
|
+
id: "kotlin",
|
|
5636
|
+
label: "Kotlin",
|
|
5637
|
+
aliases: ["kt"]
|
|
5638
|
+
},
|
|
5639
|
+
{
|
|
5640
|
+
id: "markdown",
|
|
5641
|
+
label: "Markdown",
|
|
5642
|
+
aliases: ["md"]
|
|
5643
|
+
},
|
|
5644
|
+
{
|
|
5645
|
+
id: "mdx",
|
|
5646
|
+
label: "MDX"
|
|
5647
|
+
},
|
|
5648
|
+
{
|
|
5649
|
+
id: "php",
|
|
5650
|
+
label: "PHP"
|
|
5651
|
+
},
|
|
5652
|
+
{
|
|
5653
|
+
id: "python",
|
|
5654
|
+
label: "Python",
|
|
5655
|
+
aliases: ["py"]
|
|
5656
|
+
},
|
|
5657
|
+
{
|
|
5658
|
+
id: "ruby",
|
|
5659
|
+
label: "Ruby",
|
|
5660
|
+
aliases: ["rb"]
|
|
5661
|
+
},
|
|
5662
|
+
{
|
|
5663
|
+
id: "rust",
|
|
5664
|
+
label: "Rust",
|
|
5665
|
+
aliases: ["rs"]
|
|
5666
|
+
},
|
|
5667
|
+
{
|
|
5668
|
+
id: "scss",
|
|
5669
|
+
label: "SCSS",
|
|
5670
|
+
aliases: ["sass"]
|
|
5671
|
+
},
|
|
5672
|
+
{
|
|
5673
|
+
id: "sql",
|
|
5674
|
+
label: "SQL"
|
|
5675
|
+
},
|
|
5676
|
+
{
|
|
5677
|
+
id: "svelte",
|
|
5678
|
+
label: "Svelte"
|
|
5679
|
+
},
|
|
5680
|
+
{
|
|
5681
|
+
id: "swift",
|
|
5682
|
+
label: "Swift"
|
|
5683
|
+
},
|
|
5684
|
+
{
|
|
5685
|
+
id: "toml",
|
|
5686
|
+
label: "TOML"
|
|
5687
|
+
},
|
|
5688
|
+
{
|
|
5689
|
+
id: "tsx",
|
|
5690
|
+
label: "TSX"
|
|
5691
|
+
},
|
|
5692
|
+
{
|
|
5693
|
+
id: "typescript",
|
|
5694
|
+
label: "TypeScript",
|
|
5695
|
+
aliases: ["ts"]
|
|
5696
|
+
},
|
|
5697
|
+
{
|
|
5698
|
+
id: "vue",
|
|
5699
|
+
label: "Vue"
|
|
5700
|
+
},
|
|
5701
|
+
{
|
|
5702
|
+
id: "xml",
|
|
5703
|
+
label: "XML"
|
|
5704
|
+
},
|
|
5705
|
+
{
|
|
5706
|
+
id: "yaml",
|
|
5707
|
+
label: "YAML",
|
|
5708
|
+
aliases: ["yml"]
|
|
5709
|
+
}
|
|
5710
|
+
];
|
|
5711
|
+
/**
|
|
5712
|
+
* Look up a language by id or alias. Case-insensitive.
|
|
5713
|
+
* Returns the canonical entry, or `null` if not found in the curated list.
|
|
5714
|
+
*/
|
|
5715
|
+
function findLanguage(value) {
|
|
5716
|
+
if (!value) return null;
|
|
5717
|
+
const needle = value.trim().toLowerCase();
|
|
5718
|
+
if (!needle) return null;
|
|
5719
|
+
for (const lang of CODE_BLOCK_LANGUAGES) {
|
|
5720
|
+
if (lang.id === needle) return lang;
|
|
5721
|
+
if (lang.aliases?.includes(needle)) return lang;
|
|
5722
|
+
}
|
|
5723
|
+
return null;
|
|
5724
|
+
}
|
|
5725
|
+
/**
|
|
5726
|
+
* Normalize a user-entered language string to a canonical id where possible.
|
|
5727
|
+
* Unknown inputs are sanitized to a single safe class token: lowercased,
|
|
5728
|
+
* trimmed, with any character outside `[a-z0-9_-]` (including whitespace,
|
|
5729
|
+
* dots, slashes, etc.) collapsed to `-`. This keeps the stored value safe
|
|
5730
|
+
* to interpolate into a `language-{id}` CSS class without splitting on
|
|
5731
|
+
* whitespace.
|
|
5732
|
+
*
|
|
5733
|
+
* Examples:
|
|
5734
|
+
* normalizeLanguage("TypeScript") -> "typescript" (canonical id)
|
|
5735
|
+
* normalizeLanguage("ts") -> "typescript" (alias)
|
|
5736
|
+
* normalizeLanguage("Objective C") -> "objective-c" (sanitized)
|
|
5737
|
+
* normalizeLanguage("F#") -> "f-" (sanitized)
|
|
5738
|
+
* normalizeLanguage("") -> undefined
|
|
5739
|
+
*/
|
|
5740
|
+
const DISALLOWED_CHARS_RE = /[^a-z0-9_-]+/g;
|
|
5741
|
+
const LEADING_TRAILING_HYPHENS_RE = /^-+|-+$/g;
|
|
5742
|
+
function normalizeLanguage(value) {
|
|
5743
|
+
if (!value) return void 0;
|
|
5744
|
+
const trimmed = value.trim();
|
|
5745
|
+
if (!trimmed) return void 0;
|
|
5746
|
+
const match = findLanguage(trimmed);
|
|
5747
|
+
if (match) return match.id;
|
|
5748
|
+
return trimmed.toLowerCase().replace(DISALLOWED_CHARS_RE, "-").replace(LEADING_TRAILING_HYPHENS_RE, "") || void 0;
|
|
5749
|
+
}
|
|
5750
|
+
/**
|
|
5751
|
+
* Human-readable label for a stored language id. Falls back to the id itself
|
|
5752
|
+
* for unknown values so the editor never shows "undefined".
|
|
5753
|
+
*/
|
|
5754
|
+
function languageLabel(value) {
|
|
5755
|
+
if (!value) return "Plain text";
|
|
5756
|
+
const match = findLanguage(value);
|
|
5757
|
+
if (match) return match.label;
|
|
5758
|
+
return value;
|
|
5759
|
+
}
|
|
5760
|
+
|
|
5761
|
+
//#endregion
|
|
5762
|
+
//#region src/components/editor/CodeBlockNode.tsx
|
|
5763
|
+
/**
|
|
5764
|
+
* Code block node with language picker.
|
|
5765
|
+
*
|
|
5766
|
+
* Wraps the base `@tiptap/extension-code-block` with a React node view that
|
|
5767
|
+
* overlays a small language chip in the top-right corner. Clicking the chip
|
|
5768
|
+
* opens a popover with a Kumo Autocomplete: a free-form text input plus a
|
|
5769
|
+
* filtered list of curated language suggestions. The value is persisted on
|
|
5770
|
+
* the node's `language` attribute and round-trips through Portable Text as
|
|
5771
|
+
* `block.language`.
|
|
5772
|
+
*
|
|
5773
|
+
* The picker accepts arbitrary strings (not restricted to the curated list)
|
|
5774
|
+
* so that less common languages can still be used. Free-form input is
|
|
5775
|
+
* sanitized to a single safe CSS class token via `normalizeLanguage` so the
|
|
5776
|
+
* frontend's `language-{id}` class stays well-formed.
|
|
5777
|
+
*/
|
|
5778
|
+
const LANGUAGE_ITEMS = CODE_BLOCK_LANGUAGES.map((l) => l.label);
|
|
5779
|
+
function filterLanguages(item, query) {
|
|
5780
|
+
if (!query) return true;
|
|
5781
|
+
const needle = query.toLowerCase();
|
|
5782
|
+
const lang = CODE_BLOCK_LANGUAGES.find((l) => l.label === item);
|
|
5783
|
+
if (!lang) return false;
|
|
5784
|
+
if (lang.label.toLowerCase().includes(needle)) return true;
|
|
5785
|
+
if (lang.id.toLowerCase().includes(needle)) return true;
|
|
5786
|
+
return lang.aliases?.some((alias) => alias.toLowerCase().includes(needle)) ?? false;
|
|
5787
|
+
}
|
|
5788
|
+
function CodeBlockNodeView({ node, updateAttributes, selected }) {
|
|
5789
|
+
const { _: _t } = useLingui();
|
|
5790
|
+
const [isEditing, setIsEditing] = React$1.useState(false);
|
|
5791
|
+
const storedLanguage = typeof node.attrs.language === "string" ? node.attrs.language : "";
|
|
5792
|
+
const [draft, setDraft] = React$1.useState(() => languageLabel(storedLanguage));
|
|
5793
|
+
const popoverRef = React$1.useRef(null);
|
|
5794
|
+
React$1.useEffect(() => {
|
|
5795
|
+
if (!isEditing) setDraft(languageLabel(storedLanguage));
|
|
5796
|
+
}, [storedLanguage, isEditing]);
|
|
5797
|
+
const openPicker = React$1.useCallback(() => {
|
|
5798
|
+
setDraft(storedLanguage ? languageLabel(storedLanguage) : "");
|
|
5799
|
+
setIsEditing(true);
|
|
5800
|
+
}, [storedLanguage]);
|
|
5801
|
+
const closePicker = React$1.useCallback(() => {
|
|
5802
|
+
setIsEditing(false);
|
|
5803
|
+
setDraft(languageLabel(storedLanguage));
|
|
5804
|
+
}, [storedLanguage]);
|
|
5805
|
+
const commit = React$1.useCallback((value) => {
|
|
5806
|
+
updateAttributes({ language: normalizeLanguage(value ?? draft) ?? null });
|
|
5807
|
+
setIsEditing(false);
|
|
5808
|
+
}, [draft, updateAttributes]);
|
|
5809
|
+
const handleKeyDown = (e) => {
|
|
5810
|
+
if (e.key === "Enter") {
|
|
5811
|
+
e.preventDefault();
|
|
5812
|
+
commit();
|
|
5813
|
+
} else if (e.key === "Escape") {
|
|
5814
|
+
e.preventDefault();
|
|
5815
|
+
closePicker();
|
|
5816
|
+
}
|
|
5817
|
+
};
|
|
5818
|
+
React$1.useEffect(() => {
|
|
5819
|
+
if (!isEditing) return void 0;
|
|
5820
|
+
const onMouseDown = (event) => {
|
|
5821
|
+
const target = event.target instanceof Node ? event.target : null;
|
|
5822
|
+
if (popoverRef.current && target && !popoverRef.current.contains(target)) closePicker();
|
|
5823
|
+
};
|
|
5824
|
+
document.addEventListener("mousedown", onMouseDown);
|
|
5825
|
+
return () => document.removeEventListener("mousedown", onMouseDown);
|
|
5826
|
+
}, [isEditing, closePicker]);
|
|
5827
|
+
const label = languageLabel(storedLanguage);
|
|
5828
|
+
const chipPersistent = isEditing || Boolean(storedLanguage) || selected;
|
|
5829
|
+
return /* @__PURE__ */ jsxs(NodeViewWrapper, {
|
|
5830
|
+
className: "group relative my-4",
|
|
5831
|
+
"data-language": storedLanguage || void 0,
|
|
5832
|
+
children: [/* @__PURE__ */ jsx("pre", {
|
|
5833
|
+
className: "emdash-code-block",
|
|
5834
|
+
children: /* @__PURE__ */ jsx(NodeViewContent, { as: "code" })
|
|
5835
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
5836
|
+
className: "absolute end-2 top-2 select-none",
|
|
5837
|
+
contentEditable: false,
|
|
5838
|
+
children: isEditing ? /* @__PURE__ */ jsxs("div", {
|
|
5839
|
+
ref: popoverRef,
|
|
5840
|
+
className: "flex items-center gap-1 rounded-md border bg-kumo-overlay p-1 shadow-lg",
|
|
5841
|
+
onKeyDown: handleKeyDown,
|
|
5842
|
+
children: [
|
|
5843
|
+
/* @__PURE__ */ jsxs(Autocomplete, {
|
|
5844
|
+
items: LANGUAGE_ITEMS,
|
|
5845
|
+
value: draft,
|
|
5846
|
+
onValueChange: (next) => setDraft(next),
|
|
5847
|
+
filter: filterLanguages,
|
|
5848
|
+
children: [/* @__PURE__ */ jsx(Autocomplete.InputGroup, {
|
|
5849
|
+
size: "sm",
|
|
5850
|
+
placeholder: _t({
|
|
5851
|
+
id: "vXIe7J",
|
|
5852
|
+
message: "Language"
|
|
5853
|
+
})
|
|
5854
|
+
}), /* @__PURE__ */ jsxs(Autocomplete.Content, {
|
|
5855
|
+
sideOffset: 4,
|
|
5856
|
+
children: [/* @__PURE__ */ jsx(Autocomplete.List, { children: (item) => /* @__PURE__ */ jsx(Autocomplete.Item, {
|
|
5857
|
+
value: item,
|
|
5858
|
+
children: item
|
|
5859
|
+
}, item) }), /* @__PURE__ */ jsx(Autocomplete.Empty, { children: _t({
|
|
5860
|
+
id: "4TVVsS",
|
|
5861
|
+
message: "No matches"
|
|
5862
|
+
}) })]
|
|
5863
|
+
})]
|
|
5864
|
+
}),
|
|
5865
|
+
/* @__PURE__ */ jsx(Button, {
|
|
5866
|
+
type: "button",
|
|
5867
|
+
variant: "ghost",
|
|
5868
|
+
shape: "square",
|
|
5869
|
+
className: "h-7 w-7",
|
|
5870
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
5871
|
+
onClick: () => commit(),
|
|
5872
|
+
title: _t({
|
|
5873
|
+
id: "Ek42QX",
|
|
5874
|
+
message: "Apply language"
|
|
5875
|
+
}),
|
|
5876
|
+
"aria-label": _t({
|
|
5877
|
+
id: "Ek42QX",
|
|
5878
|
+
message: "Apply language"
|
|
5879
|
+
}),
|
|
5880
|
+
children: /* @__PURE__ */ jsx(Check, { className: "h-4 w-4" })
|
|
5881
|
+
}),
|
|
5882
|
+
/* @__PURE__ */ jsx(Button, {
|
|
5883
|
+
type: "button",
|
|
5884
|
+
variant: "ghost",
|
|
5885
|
+
shape: "square",
|
|
5886
|
+
className: "h-7 w-7",
|
|
5887
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
5888
|
+
onClick: closePicker,
|
|
5889
|
+
title: _t({
|
|
5890
|
+
id: "dEgA5A",
|
|
5891
|
+
message: "Cancel"
|
|
5892
|
+
}),
|
|
5893
|
+
"aria-label": _t({
|
|
5894
|
+
id: "dEgA5A",
|
|
5895
|
+
message: "Cancel"
|
|
5896
|
+
}),
|
|
5897
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
|
|
5898
|
+
})
|
|
5899
|
+
]
|
|
5900
|
+
}) : /* @__PURE__ */ jsx("button", {
|
|
5901
|
+
type: "button",
|
|
5902
|
+
tabIndex: chipPersistent ? 0 : -1,
|
|
5903
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
5904
|
+
onClick: openPicker,
|
|
5905
|
+
className: "rounded-md border bg-kumo-overlay/90 px-2 py-1 text-xs text-kumo-subtle opacity-0 transition-opacity hover:text-kumo-strong focus:opacity-100 focus:outline-none focus:ring-2 focus:ring-kumo-brand group-hover:opacity-100 data-[persistent=true]:opacity-100",
|
|
5906
|
+
"data-persistent": chipPersistent ? "true" : "false",
|
|
5907
|
+
title: _t({
|
|
5908
|
+
id: "9cbOH7",
|
|
5909
|
+
message: "Set language"
|
|
5910
|
+
}),
|
|
5911
|
+
"aria-label": _t({
|
|
5912
|
+
id: "q/A3pF",
|
|
5913
|
+
message: "Set language (current: {label})",
|
|
5914
|
+
values: { label }
|
|
5915
|
+
}),
|
|
5916
|
+
"aria-hidden": chipPersistent ? void 0 : true,
|
|
5917
|
+
children: storedLanguage ? label : _t({
|
|
5918
|
+
id: "9cbOH7",
|
|
5919
|
+
message: "Set language"
|
|
5920
|
+
})
|
|
5921
|
+
})
|
|
5922
|
+
})]
|
|
5923
|
+
});
|
|
5924
|
+
}
|
|
5925
|
+
/**
|
|
5926
|
+
* TipTap extension: code block with an inline language picker node view.
|
|
5927
|
+
*
|
|
5928
|
+
* Drop-in replacement for StarterKit's default `codeBlock`. Configure
|
|
5929
|
+
* `StarterKit.configure({ codeBlock: false })` and add this extension to
|
|
5930
|
+
* the editor's extensions array.
|
|
5931
|
+
*/
|
|
5932
|
+
const CodeBlockExtension = CodeBlock$1.extend({ addNodeView() {
|
|
5933
|
+
return ReactNodeViewRenderer(CodeBlockNodeView);
|
|
5934
|
+
} });
|
|
5935
|
+
|
|
5528
5936
|
//#endregion
|
|
5529
5937
|
//#region src/components/editor/BlockMenu.tsx
|
|
5530
5938
|
const blockTransforms = [
|
|
@@ -8153,6 +8561,7 @@ function PortableTextEditor({ value, onChange, placeholder = "Start writing...",
|
|
|
8153
8561
|
color: "#3b82f6",
|
|
8154
8562
|
width: 2
|
|
8155
8563
|
},
|
|
8564
|
+
codeBlock: false,
|
|
8156
8565
|
link: {
|
|
8157
8566
|
openOnClick: false,
|
|
8158
8567
|
enableClickSelection: true,
|
|
@@ -8160,6 +8569,7 @@ function PortableTextEditor({ value, onChange, placeholder = "Start writing...",
|
|
|
8160
8569
|
},
|
|
8161
8570
|
underline: {}
|
|
8162
8571
|
}),
|
|
8572
|
+
CodeBlockExtension,
|
|
8163
8573
|
ImageExtension,
|
|
8164
8574
|
MarkdownLinkExtension,
|
|
8165
8575
|
PluginBlockExtension,
|
|
@@ -8262,8 +8672,7 @@ function PortableTextEditor({ value, onChange, placeholder = "Start writing...",
|
|
|
8262
8672
|
if (!editor || !pluginBlockModal) return;
|
|
8263
8673
|
const { id, ...data } = values;
|
|
8264
8674
|
const editPos = editingBlockPosRef.current;
|
|
8265
|
-
if (editPos !== null) {
|
|
8266
|
-
const { tr } = editor.state;
|
|
8675
|
+
if (editPos !== null) editor.chain().command(({ tr }) => {
|
|
8267
8676
|
const node = tr.doc.nodeAt(editPos);
|
|
8268
8677
|
if (node?.type.name === "pluginBlock") {
|
|
8269
8678
|
tr.setNodeMarkup(editPos, void 0, {
|
|
@@ -8271,9 +8680,11 @@ function PortableTextEditor({ value, onChange, placeholder = "Start writing...",
|
|
|
8271
8680
|
id: typeof id === "string" ? id : node.attrs.id,
|
|
8272
8681
|
data
|
|
8273
8682
|
});
|
|
8274
|
-
|
|
8683
|
+
return true;
|
|
8275
8684
|
}
|
|
8276
|
-
|
|
8685
|
+
return false;
|
|
8686
|
+
}).run();
|
|
8687
|
+
else editor.chain().focus().insertContent({
|
|
8277
8688
|
type: "pluginBlock",
|
|
8278
8689
|
attrs: {
|
|
8279
8690
|
blockType: pluginBlockModal.type,
|
|
@@ -10373,7 +10784,7 @@ function formatScheduledDate(dateStr) {
|
|
|
10373
10784
|
/**
|
|
10374
10785
|
* Content editor with dynamic field rendering
|
|
10375
10786
|
*/
|
|
10376
|
-
function ContentEditor({ collection, collectionLabel, item, fields, isNew, isSaving, onSave, onAutosave, isAutosaving, lastAutosaveAt, onPublish, onUnpublish, onDiscardDraft, onSchedule, onUnschedule, isScheduling, supportsDrafts = false, supportsRevisions = false, supportsPreview = false, currentUser, users, onAuthorChange, availableBylines, selectedBylines, onBylinesChange, onQuickCreateByline, onQuickEditByline, onDelete, isDeleting, i18n, translations, onTranslate, pluginBlocks, hasSeo = false, onSeoChange, manifest }) {
|
|
10787
|
+
function ContentEditor({ collection, collectionLabel, item, fields, isNew, entryLocale, isSaving, onSave, onAutosave, isAutosaving, lastAutosaveAt, onPublish, onUnpublish, onDiscardDraft, onSchedule, onUnschedule, isScheduling, supportsDrafts = false, supportsRevisions = false, supportsPreview = false, currentUser, users, onAuthorChange, availableBylines, availableBylinesLoaded, selectedBylines, onBylinesChange, onQuickCreateByline, onQuickEditByline, onDelete, isDeleting, i18n, translations, onTranslate, pluginBlocks, hasSeo = false, onSeoChange, manifest }) {
|
|
10377
10788
|
const { _: _t } = useLingui();
|
|
10378
10789
|
const navigate = useNavigate$1();
|
|
10379
10790
|
const [formData, setFormData] = React$1.useState(item?.data || {});
|
|
@@ -10384,6 +10795,7 @@ function ContentEditor({ collection, collectionLabel, item, fields, isNew, isSav
|
|
|
10384
10795
|
bylineId: entry.byline.id,
|
|
10385
10796
|
roleLabel: entry.roleLabel
|
|
10386
10797
|
})) ?? []);
|
|
10798
|
+
const [bylinesTouched, setBylinesTouched] = React$1.useState(false);
|
|
10387
10799
|
const [portableTextEditor, setPortableTextEditor] = React$1.useState(null);
|
|
10388
10800
|
const [blockSidebarPanel, setBlockSidebarPanel] = React$1.useState(null);
|
|
10389
10801
|
const handleBlockSidebarOpen = React$1.useCallback((panel) => {
|
|
@@ -10425,6 +10837,7 @@ function ContentEditor({ collection, collectionLabel, item, fields, isNew, isSav
|
|
|
10425
10837
|
bylines: nextBylines
|
|
10426
10838
|
}));
|
|
10427
10839
|
pendingAutosaveStateRef.current = null;
|
|
10840
|
+
setBylinesTouched(false);
|
|
10428
10841
|
}
|
|
10429
10842
|
const itemDataString = React$1.useMemo(() => item ? JSON.stringify(item.data) : "", [item?.data]);
|
|
10430
10843
|
React$1.useEffect(() => {
|
|
@@ -10446,6 +10859,7 @@ function ContentEditor({ collection, collectionLabel, item, fields, isNew, isSav
|
|
|
10446
10859
|
})) ?? []
|
|
10447
10860
|
}));
|
|
10448
10861
|
pendingAutosaveStateRef.current = null;
|
|
10862
|
+
setBylinesTouched(false);
|
|
10449
10863
|
}
|
|
10450
10864
|
}, [
|
|
10451
10865
|
item?.updatedAt,
|
|
@@ -10455,6 +10869,7 @@ function ContentEditor({ collection, collectionLabel, item, fields, isNew, isSav
|
|
|
10455
10869
|
]);
|
|
10456
10870
|
const activeBylines = isNew ? selectedBylines ?? [] : internalBylines;
|
|
10457
10871
|
const handleBylinesChange = React$1.useCallback((next) => {
|
|
10872
|
+
setBylinesTouched(true);
|
|
10458
10873
|
if (isNew) {
|
|
10459
10874
|
onBylinesChange?.(next);
|
|
10460
10875
|
return;
|
|
@@ -10497,13 +10912,13 @@ function ContentEditor({ collection, collectionLabel, item, fields, isNew, isSav
|
|
|
10497
10912
|
if (hasInvalidUrls(formDataRef.current)) return;
|
|
10498
10913
|
const payload = {
|
|
10499
10914
|
data: formDataRef.current,
|
|
10500
|
-
slug: slugRef.current || void 0
|
|
10501
|
-
bylines: activeBylines
|
|
10915
|
+
slug: slugRef.current || void 0
|
|
10502
10916
|
};
|
|
10917
|
+
if (bylinesTouched) payload.bylines = activeBylines;
|
|
10503
10918
|
pendingAutosaveStateRef.current = serializeEditorState({
|
|
10504
10919
|
data: payload.data,
|
|
10505
10920
|
slug: payload.slug || "",
|
|
10506
|
-
bylines:
|
|
10921
|
+
bylines: activeBylines
|
|
10507
10922
|
});
|
|
10508
10923
|
onAutosave(payload);
|
|
10509
10924
|
}, AUTOSAVE_DELAY);
|
|
@@ -10519,6 +10934,7 @@ function ContentEditor({ collection, collectionLabel, item, fields, isNew, isSav
|
|
|
10519
10934
|
isSaving,
|
|
10520
10935
|
isAutosaving,
|
|
10521
10936
|
activeBylines,
|
|
10937
|
+
bylinesTouched,
|
|
10522
10938
|
hasInvalidUrls
|
|
10523
10939
|
]);
|
|
10524
10940
|
const handleSubmit = (e) => {
|
|
@@ -10528,11 +10944,12 @@ function ContentEditor({ collection, collectionLabel, item, fields, isNew, isSav
|
|
|
10528
10944
|
clearTimeout(autosaveTimeoutRef.current);
|
|
10529
10945
|
autosaveTimeoutRef.current = null;
|
|
10530
10946
|
}
|
|
10531
|
-
|
|
10947
|
+
const payload = {
|
|
10532
10948
|
data: formData,
|
|
10533
|
-
slug: slug || void 0
|
|
10534
|
-
|
|
10535
|
-
|
|
10949
|
+
slug: slug || void 0
|
|
10950
|
+
};
|
|
10951
|
+
if (isNew || bylinesTouched) payload.bylines = activeBylines;
|
|
10952
|
+
onSave?.(payload);
|
|
10536
10953
|
};
|
|
10537
10954
|
const [isLoadingPreview, setIsLoadingPreview] = React$1.useState(false);
|
|
10538
10955
|
const urlPattern = manifest?.collections[collection]?.urlPattern;
|
|
@@ -11066,9 +11483,12 @@ function ContentEditor({ collection, collectionLabel, item, fields, isNew, isSav
|
|
|
11066
11483
|
}), /* @__PURE__ */ jsx(BylineCreditsEditor, {
|
|
11067
11484
|
credits: activeBylines,
|
|
11068
11485
|
bylines: availableBylines ?? [],
|
|
11486
|
+
bylinesLoaded: availableBylinesLoaded,
|
|
11069
11487
|
onChange: handleBylinesChange,
|
|
11070
11488
|
onQuickCreate: onQuickCreateByline,
|
|
11071
|
-
onQuickEdit: onQuickEditByline
|
|
11489
|
+
onQuickEdit: onQuickEditByline,
|
|
11490
|
+
entryLocale: item?.locale ?? entryLocale,
|
|
11491
|
+
i18n
|
|
11072
11492
|
})]
|
|
11073
11493
|
}),
|
|
11074
11494
|
i18n && item && !isNew && /* @__PURE__ */ jsx("div", {
|
|
@@ -11725,7 +12145,7 @@ function FileFieldRenderer({ id, label, value, onChange, required, allowedMimeTy
|
|
|
11725
12145
|
]
|
|
11726
12146
|
});
|
|
11727
12147
|
}
|
|
11728
|
-
function BylineCreditsEditor({ credits, bylines, onChange, onQuickCreate, onQuickEdit }) {
|
|
12148
|
+
function BylineCreditsEditor({ credits, bylines, onChange, onQuickCreate, onQuickEdit, entryLocale, i18n, bylinesLoaded = true }) {
|
|
11729
12149
|
const { _: _t7 } = useLingui();
|
|
11730
12150
|
const [selectedBylineId, setSelectedBylineId] = React$1.useState("");
|
|
11731
12151
|
const [quickName, setQuickName] = React$1.useState("");
|
|
@@ -11768,6 +12188,27 @@ function BylineCreditsEditor({ credits, bylines, onChange, onQuickCreate, onQuic
|
|
|
11768
12188
|
return /* @__PURE__ */ jsxs("div", {
|
|
11769
12189
|
className: "space-y-3",
|
|
11770
12190
|
children: [
|
|
12191
|
+
!!i18n && i18n.locales.length > 1 && bylinesLoaded && bylines.length === 0 && !!entryLocale && /* @__PURE__ */ jsxs("div", {
|
|
12192
|
+
className: "rounded border border-dashed p-3 text-sm space-y-2",
|
|
12193
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
12194
|
+
className: "text-kumo-subtle",
|
|
12195
|
+
children: _t7({
|
|
12196
|
+
id: "Tmb8Fw",
|
|
12197
|
+
message: "No bylines available in {entryLocale}. Create a variant from the Bylines page before crediting one on this entry.",
|
|
12198
|
+
values: { entryLocale }
|
|
12199
|
+
})
|
|
12200
|
+
}), /* @__PURE__ */ jsx(RouterLinkButton, {
|
|
12201
|
+
to: "/bylines",
|
|
12202
|
+
search: { locale: entryLocale ?? void 0 },
|
|
12203
|
+
variant: "secondary",
|
|
12204
|
+
size: "sm",
|
|
12205
|
+
children: _t7({
|
|
12206
|
+
id: "yXD9HA",
|
|
12207
|
+
message: "Manage bylines in {entryLocale}",
|
|
12208
|
+
values: { entryLocale }
|
|
12209
|
+
})
|
|
12210
|
+
})]
|
|
12211
|
+
}),
|
|
11771
12212
|
/* @__PURE__ */ jsxs("div", {
|
|
11772
12213
|
className: "flex gap-2",
|
|
11773
12214
|
children: [/* @__PURE__ */ jsx(Select, {
|
|
@@ -17600,10 +18041,10 @@ function formatInstallCount(count) {
|
|
|
17600
18041
|
* Lists each requested capability with a human-readable explanation.
|
|
17601
18042
|
* User must explicitly confirm before the action proceeds.
|
|
17602
18043
|
*/
|
|
17603
|
-
function CapabilityConsentDialog({ mode, pluginName, capabilities, allowedHosts, newCapabilities = [], auditVerdict, isPending = false, error, onConfirm, onCancel }) {
|
|
18044
|
+
function CapabilityConsentDialog({ mode, pluginName, capabilities, allowedHosts, newCapabilities = [], newlyPublicRoutes = [], auditVerdict, isPending = false, error, onConfirm, onCancel }) {
|
|
17604
18045
|
const { _: _t } = useLingui();
|
|
17605
18046
|
const newSet = new Set(newCapabilities);
|
|
17606
|
-
const isUpdate = mode === "update" || newCapabilities.length > 0;
|
|
18047
|
+
const isUpdate = mode === "update" || newCapabilities.length > 0 || newlyPublicRoutes.length > 0;
|
|
17607
18048
|
return /* @__PURE__ */ jsxs("div", {
|
|
17608
18049
|
className: "fixed inset-0 z-50 flex items-center justify-center",
|
|
17609
18050
|
role: "dialog",
|
|
@@ -17644,31 +18085,60 @@ function CapabilityConsentDialog({ mode, pluginName, capabilities, allowedHosts,
|
|
|
17644
18085
|
}),
|
|
17645
18086
|
/* @__PURE__ */ jsxs("div", {
|
|
17646
18087
|
className: "px-6 py-4 space-y-3",
|
|
17647
|
-
children: [
|
|
17648
|
-
|
|
17649
|
-
|
|
17650
|
-
|
|
17651
|
-
|
|
17652
|
-
className: cn(isNew
|
|
17653
|
-
|
|
17654
|
-
|
|
17655
|
-
|
|
17656
|
-
|
|
17657
|
-
|
|
17658
|
-
|
|
18088
|
+
children: [
|
|
18089
|
+
capabilities.map((cap) => {
|
|
18090
|
+
const isNew = newSet.has(cap);
|
|
18091
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
18092
|
+
className: cn("flex items-start gap-3 rounded-md p-2 text-sm", isNew ? "bg-warning/10 border border-warning/30" : "bg-kumo-tint/50"),
|
|
18093
|
+
children: [/* @__PURE__ */ jsx(ShieldCheck, { className: cn("mt-0.5 h-4 w-4 shrink-0", isNew ? "text-warning" : "text-kumo-subtle") }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("span", {
|
|
18094
|
+
className: cn(isNew && "font-medium"),
|
|
18095
|
+
children: describeCapability(cap, allowedHosts)
|
|
18096
|
+
}), isNew && /* @__PURE__ */ jsx("span", {
|
|
18097
|
+
className: "ms-2 text-xs text-warning font-medium",
|
|
18098
|
+
children: _t({
|
|
18099
|
+
id: "p6NueD",
|
|
18100
|
+
message: "NEW"
|
|
18101
|
+
})
|
|
18102
|
+
})] })]
|
|
18103
|
+
}, cap);
|
|
18104
|
+
}),
|
|
18105
|
+
newlyPublicRoutes.length > 0 && /* @__PURE__ */ jsxs("div", {
|
|
18106
|
+
className: "rounded-md border border-warning/30 bg-warning/10 p-3 text-sm",
|
|
18107
|
+
children: [
|
|
18108
|
+
/* @__PURE__ */ jsxs("div", {
|
|
18109
|
+
className: "flex items-center gap-2 font-medium text-warning",
|
|
18110
|
+
children: [/* @__PURE__ */ jsx(Warning, { className: "h-4 w-4 shrink-0" }), _t({
|
|
18111
|
+
id: "NXruDe",
|
|
18112
|
+
message: "New public routes"
|
|
18113
|
+
})]
|
|
18114
|
+
}),
|
|
18115
|
+
/* @__PURE__ */ jsx("p", {
|
|
18116
|
+
className: "mt-1 text-xs text-kumo-subtle",
|
|
18117
|
+
children: _t({
|
|
18118
|
+
id: "qCUL4n",
|
|
18119
|
+
message: "This update exposes the following routes without authentication:"
|
|
18120
|
+
})
|
|
18121
|
+
}),
|
|
18122
|
+
/* @__PURE__ */ jsx("ul", {
|
|
18123
|
+
className: "mt-2 space-y-1 ps-5 text-xs",
|
|
18124
|
+
children: newlyPublicRoutes.map((route) => /* @__PURE__ */ jsx("li", {
|
|
18125
|
+
className: "list-disc font-mono",
|
|
18126
|
+
children: route
|
|
18127
|
+
}, route))
|
|
17659
18128
|
})
|
|
17660
|
-
|
|
17661
|
-
},
|
|
17662
|
-
|
|
17663
|
-
|
|
17664
|
-
|
|
17665
|
-
|
|
17666
|
-
|
|
17667
|
-
|
|
17668
|
-
|
|
17669
|
-
|
|
17670
|
-
|
|
17671
|
-
|
|
18129
|
+
]
|
|
18130
|
+
}),
|
|
18131
|
+
auditVerdict && auditVerdict !== "pass" && /* @__PURE__ */ jsxs("div", {
|
|
18132
|
+
className: cn("flex items-center gap-2 rounded-md p-3 text-sm mt-2", auditVerdict === "warn" ? "bg-warning/10 text-warning" : "bg-kumo-danger/10 text-kumo-danger"),
|
|
18133
|
+
children: [auditVerdict === "warn" ? /* @__PURE__ */ jsx(Warning, { className: "h-4 w-4 shrink-0" }) : /* @__PURE__ */ jsx(ShieldWarning, { className: "h-4 w-4 shrink-0" }), /* @__PURE__ */ jsx("span", { children: auditVerdict === "warn" ? _t({
|
|
18134
|
+
id: "zPUuMZ",
|
|
18135
|
+
message: "Security audit flagged potential concerns with this plugin."
|
|
18136
|
+
}) : _t({
|
|
18137
|
+
id: "nvVzbM",
|
|
18138
|
+
message: "Security audit flagged this plugin as potentially unsafe."
|
|
18139
|
+
}) })]
|
|
18140
|
+
})
|
|
18141
|
+
]
|
|
17672
18142
|
}),
|
|
17673
18143
|
/* @__PURE__ */ jsx(DialogError, {
|
|
17674
18144
|
message: error,
|
|
@@ -17707,6 +18177,332 @@ function CapabilityConsentDialog({ mode, pluginName, capabilities, allowedHosts,
|
|
|
17707
18177
|
});
|
|
17708
18178
|
}
|
|
17709
18179
|
|
|
18180
|
+
//#endregion
|
|
18181
|
+
//#region src/lib/api/registry.ts
|
|
18182
|
+
let cachedDiscovery = null;
|
|
18183
|
+
async function getDiscoveryClient(config) {
|
|
18184
|
+
if (cachedDiscovery && cachedDiscovery.config.aggregatorUrl === config.aggregatorUrl && cachedDiscovery.config.acceptLabelers === config.acceptLabelers) return cachedDiscovery.client;
|
|
18185
|
+
const DiscoveryClient = (await import("@emdash-cms/registry-client/discovery")).DiscoveryClient;
|
|
18186
|
+
const discovery = new DiscoveryClient({
|
|
18187
|
+
aggregatorUrl: config.aggregatorUrl,
|
|
18188
|
+
acceptLabelers: config.acceptLabelers
|
|
18189
|
+
});
|
|
18190
|
+
const wrapped = {
|
|
18191
|
+
async searchPackages(opts) {
|
|
18192
|
+
return discovery.searchPackages({
|
|
18193
|
+
q: opts.q,
|
|
18194
|
+
cursor: opts.cursor,
|
|
18195
|
+
limit: opts.limit
|
|
18196
|
+
});
|
|
18197
|
+
},
|
|
18198
|
+
async resolvePackage(handle, slug) {
|
|
18199
|
+
return discovery.resolvePackage({
|
|
18200
|
+
handle,
|
|
18201
|
+
slug
|
|
18202
|
+
});
|
|
18203
|
+
},
|
|
18204
|
+
async getPackage(did, slug) {
|
|
18205
|
+
return discovery.getPackage({
|
|
18206
|
+
did,
|
|
18207
|
+
slug
|
|
18208
|
+
});
|
|
18209
|
+
},
|
|
18210
|
+
async getLatestRelease(did, slug) {
|
|
18211
|
+
return discovery.getLatestRelease({
|
|
18212
|
+
did,
|
|
18213
|
+
package: slug
|
|
18214
|
+
});
|
|
18215
|
+
},
|
|
18216
|
+
async listReleases(did, slug, opts) {
|
|
18217
|
+
return discovery.listReleases({
|
|
18218
|
+
did,
|
|
18219
|
+
package: slug,
|
|
18220
|
+
cursor: opts?.cursor,
|
|
18221
|
+
limit: opts?.limit
|
|
18222
|
+
});
|
|
18223
|
+
}
|
|
18224
|
+
};
|
|
18225
|
+
cachedDiscovery = {
|
|
18226
|
+
config,
|
|
18227
|
+
client: wrapped
|
|
18228
|
+
};
|
|
18229
|
+
return wrapped;
|
|
18230
|
+
}
|
|
18231
|
+
/**
|
|
18232
|
+
* Returns whether a release should be considered installable given the
|
|
18233
|
+
* configured policy. Currently implements the minimum-release-age check
|
|
18234
|
+
* described in RFC 0001's "Pre-label gap and launch tempo" section,
|
|
18235
|
+
* plus the `minimumReleaseAgeExclude` allowlist.
|
|
18236
|
+
*
|
|
18237
|
+
* Returns `false` (release blocked) when the policy is configured but
|
|
18238
|
+
* the release is missing a valid `indexedAt` -- we fail closed rather
|
|
18239
|
+
* than silently letting unbounded-age releases through.
|
|
18240
|
+
*/
|
|
18241
|
+
function releasePassesPolicy(release, pkg, policy, now = Date.now()) {
|
|
18242
|
+
if (!policy?.minimumReleaseAgeSeconds) return true;
|
|
18243
|
+
if (releaseExemptFromMinimumAge(policy.minimumReleaseAgeExclude, pkg.did, pkg.slug)) return true;
|
|
18244
|
+
const indexedAt = Date.parse(release.indexedAt);
|
|
18245
|
+
if (!Number.isFinite(indexedAt)) return false;
|
|
18246
|
+
return (now - indexedAt) / 1e3 >= policy.minimumReleaseAgeSeconds;
|
|
18247
|
+
}
|
|
18248
|
+
/**
|
|
18249
|
+
* Canonicalize a capabilities list for set-style comparison. Mirrors
|
|
18250
|
+
* the server-side helper `canonicalCapabilitiesForDriftCheck` in
|
|
18251
|
+
* `packages/core/src/registry/config.ts` -- both sides must produce
|
|
18252
|
+
* the same canonical shape so the install handler's drift check is
|
|
18253
|
+
* stable across reorderings, duplicates, and junk entries.
|
|
18254
|
+
*
|
|
18255
|
+
* Filters non-strings, deduplicates, and sorts lexically.
|
|
18256
|
+
*/
|
|
18257
|
+
function canonicalCapabilitiesForDriftCheck(value) {
|
|
18258
|
+
if (!Array.isArray(value)) return [];
|
|
18259
|
+
const seen = /* @__PURE__ */ new Set();
|
|
18260
|
+
for (const entry of value) if (typeof entry === "string" && entry.length > 0) seen.add(entry);
|
|
18261
|
+
return [...seen].toSorted();
|
|
18262
|
+
}
|
|
18263
|
+
/**
|
|
18264
|
+
* Matches a `(publisher_did, slug)` against the
|
|
18265
|
+
* `minimumReleaseAgeExclude` allowlist. Mirrors the server-side helper
|
|
18266
|
+
* of the same name in `packages/core/src/registry/config.ts`.
|
|
18267
|
+
*
|
|
18268
|
+
* DID-only on purpose: handles are aggregator-supplied envelope data
|
|
18269
|
+
* and accepting them as a trust input would let a compromised
|
|
18270
|
+
* aggregator bypass the holdback by claiming any handle for any
|
|
18271
|
+
* package. DIDs are tied to the AT URI of the record itself.
|
|
18272
|
+
*
|
|
18273
|
+
* Entries from the config list have already been lowercased at
|
|
18274
|
+
* manifest build time, so this only needs to lowercase the runtime
|
|
18275
|
+
* values for comparison.
|
|
18276
|
+
*/
|
|
18277
|
+
function releaseExemptFromMinimumAge(exclude, publisherDid, slug) {
|
|
18278
|
+
if (!exclude || exclude.length === 0) return false;
|
|
18279
|
+
const didLower = publisherDid.toLowerCase();
|
|
18280
|
+
const fullDid = `${didLower}/${slug.toLowerCase()}`;
|
|
18281
|
+
for (const entry of exclude) {
|
|
18282
|
+
if (entry === didLower) return true;
|
|
18283
|
+
if (entry === fullDid) return true;
|
|
18284
|
+
}
|
|
18285
|
+
return false;
|
|
18286
|
+
}
|
|
18287
|
+
async function searchRegistryPackages(config, opts) {
|
|
18288
|
+
return (await getDiscoveryClient(config)).searchPackages(opts);
|
|
18289
|
+
}
|
|
18290
|
+
async function resolveRegistryPackage(config, handle, slug) {
|
|
18291
|
+
return (await getDiscoveryClient(config)).resolvePackage(handle, slug);
|
|
18292
|
+
}
|
|
18293
|
+
async function getRegistryPackage(config, did, slug) {
|
|
18294
|
+
return (await getDiscoveryClient(config)).getPackage(did, slug);
|
|
18295
|
+
}
|
|
18296
|
+
async function listRegistryReleases(config, did, slug, opts) {
|
|
18297
|
+
return (await getDiscoveryClient(config)).listReleases(did, slug, opts);
|
|
18298
|
+
}
|
|
18299
|
+
/**
|
|
18300
|
+
* Resolve a publisher DID to its claimed handle using the same
|
|
18301
|
+
* `LocalActorResolver` pattern as `@emdash-cms/plugin-cli` and
|
|
18302
|
+
* `@emdash-cms/auth-atproto`. Bidirectional verification (handle's
|
|
18303
|
+
* domain points back to the same DID) is part of the resolver --
|
|
18304
|
+
* `LocalActorResolver` returns the sentinel `"handle.invalid"` when
|
|
18305
|
+
* the `alsoKnownAs` handle is present but doesn't round-trip.
|
|
18306
|
+
*
|
|
18307
|
+
* Three distinct outcomes the UI can render:
|
|
18308
|
+
*
|
|
18309
|
+
* - `{ status: "ok", handle }` — verified handle, round-trip OK.
|
|
18310
|
+
* - `{ status: "invalid" }` — DID claims a handle but it doesn't
|
|
18311
|
+
* resolve back. The publisher's handle setup is broken; the admin
|
|
18312
|
+
* should see a clear "Invalid handle" indicator rather than the
|
|
18313
|
+
* raw DID.
|
|
18314
|
+
* - `{ status: "missing" }` — no handle claimed at all (no
|
|
18315
|
+
* `alsoKnownAs`), or the DID document couldn't be fetched (network
|
|
18316
|
+
* error, unsupported DID method).
|
|
18317
|
+
*/
|
|
18318
|
+
let actorResolver = null;
|
|
18319
|
+
async function getActorResolver() {
|
|
18320
|
+
if (actorResolver) return actorResolver;
|
|
18321
|
+
const { CompositeDidDocumentResolver, CompositeHandleResolver, DohJsonHandleResolver, LocalActorResolver, PlcDidDocumentResolver, WebDidDocumentResolver, WellKnownHandleResolver } = await import("@atcute/identity-resolver");
|
|
18322
|
+
actorResolver = new LocalActorResolver({
|
|
18323
|
+
handleResolver: new CompositeHandleResolver({ methods: {
|
|
18324
|
+
dns: new DohJsonHandleResolver({ dohUrl: "https://cloudflare-dns.com/dns-query" }),
|
|
18325
|
+
http: new WellKnownHandleResolver()
|
|
18326
|
+
} }),
|
|
18327
|
+
didDocumentResolver: new CompositeDidDocumentResolver({ methods: {
|
|
18328
|
+
plc: new PlcDidDocumentResolver(),
|
|
18329
|
+
web: new WebDidDocumentResolver()
|
|
18330
|
+
} })
|
|
18331
|
+
});
|
|
18332
|
+
return actorResolver;
|
|
18333
|
+
}
|
|
18334
|
+
/**
|
|
18335
|
+
* localStorage-backed cache for DID→handle resolutions. Handles are
|
|
18336
|
+
* stable for hours-to-days in practice, but bound the cache so a
|
|
18337
|
+
* compromised handle eventually flips back to "invalid" without a
|
|
18338
|
+
* forced refresh. 24h matches the typical atproto handle TTL.
|
|
18339
|
+
*
|
|
18340
|
+
* Failures (network errors, unsupported DID method) are *not* cached --
|
|
18341
|
+
* those should retry on the next render.
|
|
18342
|
+
*/
|
|
18343
|
+
const HANDLE_CACHE_TTL_MS = 1440 * 60 * 1e3;
|
|
18344
|
+
const HANDLE_CACHE_KEY_PREFIX = "emdash:did-handle:";
|
|
18345
|
+
function isCachedResolution(value) {
|
|
18346
|
+
if (typeof value !== "object" || value === null) return false;
|
|
18347
|
+
const candidate = value;
|
|
18348
|
+
return typeof candidate.expiresAt === "number" && typeof candidate.resolution === "object" && candidate.resolution !== null;
|
|
18349
|
+
}
|
|
18350
|
+
function readHandleCache(did) {
|
|
18351
|
+
if (typeof localStorage === "undefined") return null;
|
|
18352
|
+
try {
|
|
18353
|
+
const raw = localStorage.getItem(`${HANDLE_CACHE_KEY_PREFIX}${did}`);
|
|
18354
|
+
if (!raw) return null;
|
|
18355
|
+
const parsed = JSON.parse(raw);
|
|
18356
|
+
if (!isCachedResolution(parsed) || parsed.expiresAt < Date.now()) return null;
|
|
18357
|
+
return parsed.resolution;
|
|
18358
|
+
} catch {
|
|
18359
|
+
return null;
|
|
18360
|
+
}
|
|
18361
|
+
}
|
|
18362
|
+
function writeHandleCache(did, resolution) {
|
|
18363
|
+
if (typeof localStorage === "undefined") return;
|
|
18364
|
+
try {
|
|
18365
|
+
const entry = {
|
|
18366
|
+
resolution,
|
|
18367
|
+
expiresAt: Date.now() + HANDLE_CACHE_TTL_MS
|
|
18368
|
+
};
|
|
18369
|
+
localStorage.setItem(`${HANDLE_CACHE_KEY_PREFIX}${did}`, JSON.stringify(entry));
|
|
18370
|
+
} catch {}
|
|
18371
|
+
}
|
|
18372
|
+
async function resolveDidToHandle(did) {
|
|
18373
|
+
const cached = readHandleCache(did);
|
|
18374
|
+
if (cached) return cached;
|
|
18375
|
+
let result;
|
|
18376
|
+
try {
|
|
18377
|
+
const resolved = await (await getActorResolver()).resolve(did);
|
|
18378
|
+
if (resolved.handle === "handle.invalid") result = { status: "invalid" };
|
|
18379
|
+
else if (resolved.handle) result = {
|
|
18380
|
+
status: "ok",
|
|
18381
|
+
handle: resolved.handle
|
|
18382
|
+
};
|
|
18383
|
+
else result = { status: "missing" };
|
|
18384
|
+
} catch (err) {
|
|
18385
|
+
console.warn(`[registry] DID->handle resolution failed for ${did}:`, err);
|
|
18386
|
+
return { status: "missing" };
|
|
18387
|
+
}
|
|
18388
|
+
writeHandleCache(did, result);
|
|
18389
|
+
return result;
|
|
18390
|
+
}
|
|
18391
|
+
const INSTALL_ENDPOINT = `${API_BASE}/admin/plugins/registry/install`;
|
|
18392
|
+
/**
|
|
18393
|
+
* Install a plugin from the registry.
|
|
18394
|
+
*
|
|
18395
|
+
* Posts to the EmDash server, which re-resolves the same `(handle,
|
|
18396
|
+
* slug)` against the aggregator, re-verifies the bundle's checksum
|
|
18397
|
+
* against the signed release record, and writes the install. Surfaces
|
|
18398
|
+
* structured error codes (`RELEASE_YANKED`, `CHECKSUM_MISMATCH`,
|
|
18399
|
+
* `DECLARED_ACCESS_DRIFT`, etc.) that callers map to localized
|
|
18400
|
+
* messages.
|
|
18401
|
+
*/
|
|
18402
|
+
async function installRegistryPlugin(body) {
|
|
18403
|
+
return parseApiResponse(await apiFetch(INSTALL_ENDPOINT, {
|
|
18404
|
+
method: "POST",
|
|
18405
|
+
headers: { "Content-Type": "application/json" },
|
|
18406
|
+
body: JSON.stringify(body)
|
|
18407
|
+
}), i18n._({
|
|
18408
|
+
id: "/LWKyx",
|
|
18409
|
+
message: "Failed to install plugin"
|
|
18410
|
+
}));
|
|
18411
|
+
}
|
|
18412
|
+
/**
|
|
18413
|
+
* Server-side escalation gate raised by the update endpoint when the
|
|
18414
|
+
* target version widens the trust contract. Carries the diff the user
|
|
18415
|
+
* needs to see in the consent dialog before the call is retried with the
|
|
18416
|
+
* matching `confirm*` flag.
|
|
18417
|
+
*/
|
|
18418
|
+
var RegistryUpdateEscalationError = class extends Error {
|
|
18419
|
+
code;
|
|
18420
|
+
capabilityChanges;
|
|
18421
|
+
routeVisibilityChanges;
|
|
18422
|
+
constructor(code, message, capabilityChanges, routeVisibilityChanges) {
|
|
18423
|
+
super(message);
|
|
18424
|
+
this.name = "RegistryUpdateEscalationError";
|
|
18425
|
+
this.code = code;
|
|
18426
|
+
this.capabilityChanges = capabilityChanges;
|
|
18427
|
+
this.routeVisibilityChanges = routeVisibilityChanges;
|
|
18428
|
+
}
|
|
18429
|
+
};
|
|
18430
|
+
/**
|
|
18431
|
+
* Update a registry-source plugin to a newer version.
|
|
18432
|
+
* `POST /_emdash/api/admin/plugins/registry/:id/update`
|
|
18433
|
+
*
|
|
18434
|
+
* Called without `confirm*` flags first, this throws
|
|
18435
|
+
* `RegistryUpdateEscalationError` when the target version widens
|
|
18436
|
+
* permissions; the caller renders a consent dialog populated from the
|
|
18437
|
+
* error's diff, then re-calls with the matching `confirm*` flag once
|
|
18438
|
+
* the user agrees.
|
|
18439
|
+
*/
|
|
18440
|
+
async function updateRegistryPlugin(pluginId, opts = {}) {
|
|
18441
|
+
const response = await apiFetch(`${API_BASE}/admin/plugins/registry/${encodeURIComponent(pluginId)}/update`, {
|
|
18442
|
+
method: "POST",
|
|
18443
|
+
headers: { "Content-Type": "application/json" },
|
|
18444
|
+
body: JSON.stringify(opts)
|
|
18445
|
+
});
|
|
18446
|
+
if (response.ok) return;
|
|
18447
|
+
const escalation = parseEscalation(await response.clone().json().catch(() => void 0));
|
|
18448
|
+
if (escalation) throw escalation;
|
|
18449
|
+
await throwResponseError(response, i18n._({
|
|
18450
|
+
id: "8m84rO",
|
|
18451
|
+
message: "Failed to update plugin"
|
|
18452
|
+
}));
|
|
18453
|
+
}
|
|
18454
|
+
function parseEscalation(body) {
|
|
18455
|
+
if (!body || typeof body !== "object" || !("error" in body)) return null;
|
|
18456
|
+
const error = body.error;
|
|
18457
|
+
if (!error || typeof error !== "object" || !("code" in error)) return null;
|
|
18458
|
+
const code = error.code;
|
|
18459
|
+
if (code !== "CAPABILITY_ESCALATION" && code !== "ROUTE_VISIBILITY_ESCALATION") return null;
|
|
18460
|
+
const details = "details" in error && error.details && typeof error.details === "object" ? error.details : {};
|
|
18461
|
+
const capabilityChanges = normaliseCapabilityChanges("capabilityChanges" in details ? details.capabilityChanges : void 0);
|
|
18462
|
+
const routeVisibilityChanges = normaliseRouteVisibilityChanges("routeVisibilityChanges" in details ? details.routeVisibilityChanges : void 0);
|
|
18463
|
+
return new RegistryUpdateEscalationError(code, "message" in error && typeof error.message === "string" ? error.message : i18n._({
|
|
18464
|
+
id: "dgD7yY",
|
|
18465
|
+
message: "Plugin update requires re-consent"
|
|
18466
|
+
}), capabilityChanges, routeVisibilityChanges);
|
|
18467
|
+
}
|
|
18468
|
+
function normaliseCapabilityChanges(value) {
|
|
18469
|
+
if (!value || typeof value !== "object") return {
|
|
18470
|
+
added: [],
|
|
18471
|
+
removed: []
|
|
18472
|
+
};
|
|
18473
|
+
const v = value;
|
|
18474
|
+
return {
|
|
18475
|
+
added: Array.isArray(v.added) ? v.added.filter((s) => typeof s === "string") : [],
|
|
18476
|
+
removed: Array.isArray(v.removed) ? v.removed.filter((s) => typeof s === "string") : []
|
|
18477
|
+
};
|
|
18478
|
+
}
|
|
18479
|
+
function normaliseRouteVisibilityChanges(value) {
|
|
18480
|
+
if (!value || typeof value !== "object") return void 0;
|
|
18481
|
+
const v = value;
|
|
18482
|
+
if (!Array.isArray(v.newlyPublic)) return void 0;
|
|
18483
|
+
const newlyPublic = v.newlyPublic.filter((s) => typeof s === "string");
|
|
18484
|
+
return newlyPublic.length > 0 ? { newlyPublic } : void 0;
|
|
18485
|
+
}
|
|
18486
|
+
/**
|
|
18487
|
+
* Uninstall a registry-source plugin.
|
|
18488
|
+
* `POST /_emdash/api/admin/plugins/registry/:id/uninstall`
|
|
18489
|
+
*
|
|
18490
|
+
* The server refuses to uninstall non-registry sources, so calling this
|
|
18491
|
+
* with a marketplace or config plugin id is a no-op error rather than a
|
|
18492
|
+
* destructive cross-source action.
|
|
18493
|
+
*/
|
|
18494
|
+
async function uninstallRegistryPlugin(pluginId, opts = {}) {
|
|
18495
|
+
const response = await apiFetch(`${API_BASE}/admin/plugins/registry/${encodeURIComponent(pluginId)}/uninstall`, {
|
|
18496
|
+
method: "POST",
|
|
18497
|
+
headers: { "Content-Type": "application/json" },
|
|
18498
|
+
body: JSON.stringify(opts)
|
|
18499
|
+
});
|
|
18500
|
+
if (!response.ok) await throwResponseError(response, i18n._({
|
|
18501
|
+
id: "IDxl65",
|
|
18502
|
+
message: "Failed to uninstall plugin"
|
|
18503
|
+
}));
|
|
18504
|
+
}
|
|
18505
|
+
|
|
17710
18506
|
//#endregion
|
|
17711
18507
|
//#region src/components/PluginManager.tsx
|
|
17712
18508
|
/**
|
|
@@ -17796,7 +18592,7 @@ function PluginManager({ manifest }) {
|
|
|
17796
18592
|
if (!updates) return /* @__PURE__ */ new Map();
|
|
17797
18593
|
return new Map(updates.map((u) => [u.pluginId, u]));
|
|
17798
18594
|
}, [updates]);
|
|
17799
|
-
const
|
|
18595
|
+
const hasUpdatableSources = plugins?.some((p) => p.source === "marketplace" || p.source === "registry");
|
|
17800
18596
|
if (isLoading) return /* @__PURE__ */ jsxs("div", {
|
|
17801
18597
|
className: "space-y-6",
|
|
17802
18598
|
children: [/* @__PURE__ */ jsx("h1", {
|
|
@@ -17844,7 +18640,7 @@ function PluginManager({ manifest }) {
|
|
|
17844
18640
|
}), /* @__PURE__ */ jsxs("div", {
|
|
17845
18641
|
className: "flex items-center gap-3",
|
|
17846
18642
|
children: [
|
|
17847
|
-
|
|
18643
|
+
hasUpdatableSources && /* @__PURE__ */ jsx(Button, {
|
|
17848
18644
|
variant: "ghost",
|
|
17849
18645
|
onClick: () => void refetchUpdates(),
|
|
17850
18646
|
disabled: isCheckingUpdates,
|
|
@@ -17939,15 +18735,17 @@ function PluginCard({ plugin, updateInfo, onEnable, onDisable, isToggling, hasMa
|
|
|
17939
18735
|
const [expanded, setExpanded] = React$1.useState(false);
|
|
17940
18736
|
const [showUpdateConsent, setShowUpdateConsent] = React$1.useState(false);
|
|
17941
18737
|
const [showUninstallConfirm, setShowUninstallConfirm] = React$1.useState(false);
|
|
18738
|
+
const [registryEscalation, setRegistryEscalation] = React$1.useState(null);
|
|
17942
18739
|
const queryClient = useQueryClient();
|
|
17943
18740
|
const toastManager = Toast.useToastManager();
|
|
17944
18741
|
const isMarketplace = plugin.source === "marketplace";
|
|
17945
18742
|
const isRegistry = plugin.source === "registry";
|
|
17946
18743
|
const hasUpdate = !!updateInfo && updateInfo.installed !== updateInfo.latest;
|
|
17947
18744
|
const updateMutation = useMutation({
|
|
17948
|
-
mutationFn: () => updateMarketplacePlugin(plugin.id, { confirmCapabilities: true }),
|
|
18745
|
+
mutationFn: (opts) => isRegistry ? updateRegistryPlugin(plugin.id, opts) : updateMarketplacePlugin(plugin.id, { confirmCapabilities: true }),
|
|
17949
18746
|
onSuccess: () => {
|
|
17950
18747
|
setShowUpdateConsent(false);
|
|
18748
|
+
setRegistryEscalation(null);
|
|
17951
18749
|
queryClient.invalidateQueries({ queryKey: ["plugins"] });
|
|
17952
18750
|
queryClient.invalidateQueries({ queryKey: ["plugin-updates"] });
|
|
17953
18751
|
queryClient.invalidateQueries({ queryKey: ["manifest"] });
|
|
@@ -17965,10 +18763,29 @@ function PluginCard({ plugin, updateInfo, onEnable, onDisable, isToggling, hasMa
|
|
|
17965
18763
|
}
|
|
17966
18764
|
})
|
|
17967
18765
|
});
|
|
18766
|
+
},
|
|
18767
|
+
onError: (err) => {
|
|
18768
|
+
if (err instanceof RegistryUpdateEscalationError) {
|
|
18769
|
+
setRegistryEscalation(err);
|
|
18770
|
+
setShowUpdateConsent(true);
|
|
18771
|
+
}
|
|
17968
18772
|
}
|
|
17969
18773
|
});
|
|
18774
|
+
const handleUpdateClick = () => {
|
|
18775
|
+
if (isRegistry) {
|
|
18776
|
+
setRegistryEscalation(null);
|
|
18777
|
+
updateMutation.mutate({});
|
|
18778
|
+
} else setShowUpdateConsent(true);
|
|
18779
|
+
};
|
|
18780
|
+
const handleUpdateConfirm = () => {
|
|
18781
|
+
if (isRegistry) {
|
|
18782
|
+
const opts = { confirmCapabilityChanges: true };
|
|
18783
|
+
if (registryEscalation?.code === "ROUTE_VISIBILITY_ESCALATION") opts.confirmRouteVisibilityChanges = true;
|
|
18784
|
+
updateMutation.mutate(opts);
|
|
18785
|
+
} else updateMutation.mutate({});
|
|
18786
|
+
};
|
|
17970
18787
|
const uninstallMutation = useMutation({
|
|
17971
|
-
mutationFn: (deleteData) => uninstallMarketplacePlugin(plugin.id, { deleteData }),
|
|
18788
|
+
mutationFn: (deleteData) => isRegistry ? uninstallRegistryPlugin(plugin.id, { deleteData }) : uninstallMarketplacePlugin(plugin.id, { deleteData }),
|
|
17972
18789
|
onSuccess: () => {
|
|
17973
18790
|
setShowUninstallConfirm(false);
|
|
17974
18791
|
queryClient.invalidateQueries({ queryKey: ["plugins"] });
|
|
@@ -18097,7 +18914,7 @@ function PluginCard({ plugin, updateInfo, onEnable, onDisable, isToggling, hasMa
|
|
|
18097
18914
|
hasUpdate && /* @__PURE__ */ jsx(Button, {
|
|
18098
18915
|
variant: "outline",
|
|
18099
18916
|
size: "sm",
|
|
18100
|
-
onClick:
|
|
18917
|
+
onClick: handleUpdateClick,
|
|
18101
18918
|
disabled: updateMutation.isPending,
|
|
18102
18919
|
children: updateMutation.isPending ? _t2({
|
|
18103
18920
|
id: "RXbEvi",
|
|
@@ -18261,7 +19078,7 @@ function PluginCard({ plugin, updateInfo, onEnable, onDisable, isToggling, hasMa
|
|
|
18261
19078
|
] })
|
|
18262
19079
|
]
|
|
18263
19080
|
}),
|
|
18264
|
-
isMarketplace && /* @__PURE__ */ jsx("div", {
|
|
19081
|
+
(isMarketplace || isRegistry) && /* @__PURE__ */ jsx("div", {
|
|
18265
19082
|
className: "pt-2 border-t",
|
|
18266
19083
|
children: /* @__PURE__ */ jsx(Button, {
|
|
18267
19084
|
variant: "ghost",
|
|
@@ -18274,13 +19091,6 @@ function PluginCard({ plugin, updateInfo, onEnable, onDisable, isToggling, hasMa
|
|
|
18274
19091
|
message: "Uninstall"
|
|
18275
19092
|
})
|
|
18276
19093
|
})
|
|
18277
|
-
}),
|
|
18278
|
-
isRegistry && /* @__PURE__ */ jsx("div", {
|
|
18279
|
-
className: "pt-2 border-t text-xs text-kumo-subtle",
|
|
18280
|
-
children: _t2({
|
|
18281
|
-
id: "YvXVtr",
|
|
18282
|
-
message: "Uninstall is not yet available for registry plugins. Disable the plugin to stop it from running; full uninstall will land in a follow-up."
|
|
18283
|
-
})
|
|
18284
19094
|
})
|
|
18285
19095
|
]
|
|
18286
19096
|
})]
|
|
@@ -18289,12 +19099,14 @@ function PluginCard({ plugin, updateInfo, onEnable, onDisable, isToggling, hasMa
|
|
|
18289
19099
|
mode: "update",
|
|
18290
19100
|
pluginName: plugin.name,
|
|
18291
19101
|
capabilities: plugin.capabilities,
|
|
18292
|
-
newCapabilities: [],
|
|
19102
|
+
newCapabilities: registryEscalation?.capabilityChanges.added ?? [],
|
|
19103
|
+
newlyPublicRoutes: registryEscalation?.routeVisibilityChanges?.newlyPublic ?? [],
|
|
18293
19104
|
isPending: updateMutation.isPending,
|
|
18294
|
-
error: getMutationError(updateMutation.error),
|
|
18295
|
-
onConfirm:
|
|
19105
|
+
error: updateMutation.error instanceof RegistryUpdateEscalationError ? null : getMutationError(updateMutation.error),
|
|
19106
|
+
onConfirm: handleUpdateConfirm,
|
|
18296
19107
|
onCancel: () => {
|
|
18297
19108
|
setShowUpdateConsent(false);
|
|
19109
|
+
setRegistryEscalation(null);
|
|
18298
19110
|
updateMutation.reset();
|
|
18299
19111
|
}
|
|
18300
19112
|
}),
|
|
@@ -21951,235 +22763,6 @@ function Redirects() {
|
|
|
21951
22763
|
});
|
|
21952
22764
|
}
|
|
21953
22765
|
|
|
21954
|
-
//#endregion
|
|
21955
|
-
//#region src/lib/api/registry.ts
|
|
21956
|
-
let cachedDiscovery = null;
|
|
21957
|
-
async function getDiscoveryClient(config) {
|
|
21958
|
-
if (cachedDiscovery && cachedDiscovery.config.aggregatorUrl === config.aggregatorUrl && cachedDiscovery.config.acceptLabelers === config.acceptLabelers) return cachedDiscovery.client;
|
|
21959
|
-
const DiscoveryClient = (await import("@emdash-cms/registry-client/discovery")).DiscoveryClient;
|
|
21960
|
-
const discovery = new DiscoveryClient({
|
|
21961
|
-
aggregatorUrl: config.aggregatorUrl,
|
|
21962
|
-
acceptLabelers: config.acceptLabelers
|
|
21963
|
-
});
|
|
21964
|
-
const wrapped = {
|
|
21965
|
-
async searchPackages(opts) {
|
|
21966
|
-
return discovery.searchPackages({
|
|
21967
|
-
q: opts.q,
|
|
21968
|
-
cursor: opts.cursor,
|
|
21969
|
-
limit: opts.limit
|
|
21970
|
-
});
|
|
21971
|
-
},
|
|
21972
|
-
async resolvePackage(handle, slug) {
|
|
21973
|
-
return discovery.resolvePackage({
|
|
21974
|
-
handle,
|
|
21975
|
-
slug
|
|
21976
|
-
});
|
|
21977
|
-
},
|
|
21978
|
-
async getPackage(did, slug) {
|
|
21979
|
-
return discovery.getPackage({
|
|
21980
|
-
did,
|
|
21981
|
-
slug
|
|
21982
|
-
});
|
|
21983
|
-
},
|
|
21984
|
-
async getLatestRelease(did, slug) {
|
|
21985
|
-
return discovery.getLatestRelease({
|
|
21986
|
-
did,
|
|
21987
|
-
package: slug
|
|
21988
|
-
});
|
|
21989
|
-
},
|
|
21990
|
-
async listReleases(did, slug, cursor) {
|
|
21991
|
-
return discovery.listReleases({
|
|
21992
|
-
did,
|
|
21993
|
-
package: slug,
|
|
21994
|
-
cursor
|
|
21995
|
-
});
|
|
21996
|
-
}
|
|
21997
|
-
};
|
|
21998
|
-
cachedDiscovery = {
|
|
21999
|
-
config,
|
|
22000
|
-
client: wrapped
|
|
22001
|
-
};
|
|
22002
|
-
return wrapped;
|
|
22003
|
-
}
|
|
22004
|
-
/**
|
|
22005
|
-
* Returns whether a release should be considered installable given the
|
|
22006
|
-
* configured policy. Currently implements the minimum-release-age check
|
|
22007
|
-
* described in RFC 0001's "Pre-label gap and launch tempo" section,
|
|
22008
|
-
* plus the `minimumReleaseAgeExclude` allowlist.
|
|
22009
|
-
*
|
|
22010
|
-
* Returns `false` (release blocked) when the policy is configured but
|
|
22011
|
-
* the release is missing a valid `indexedAt` -- we fail closed rather
|
|
22012
|
-
* than silently letting unbounded-age releases through.
|
|
22013
|
-
*/
|
|
22014
|
-
function releasePassesPolicy(release, pkg, policy, now = Date.now()) {
|
|
22015
|
-
if (!policy?.minimumReleaseAgeSeconds) return true;
|
|
22016
|
-
if (releaseExemptFromMinimumAge(policy.minimumReleaseAgeExclude, pkg.did, pkg.slug)) return true;
|
|
22017
|
-
const indexedAt = Date.parse(release.indexedAt);
|
|
22018
|
-
if (!Number.isFinite(indexedAt)) return false;
|
|
22019
|
-
return (now - indexedAt) / 1e3 >= policy.minimumReleaseAgeSeconds;
|
|
22020
|
-
}
|
|
22021
|
-
/**
|
|
22022
|
-
* Canonicalize a capabilities list for set-style comparison. Mirrors
|
|
22023
|
-
* the server-side helper `canonicalCapabilitiesForDriftCheck` in
|
|
22024
|
-
* `packages/core/src/registry/config.ts` -- both sides must produce
|
|
22025
|
-
* the same canonical shape so the install handler's drift check is
|
|
22026
|
-
* stable across reorderings, duplicates, and junk entries.
|
|
22027
|
-
*
|
|
22028
|
-
* Filters non-strings, deduplicates, and sorts lexically.
|
|
22029
|
-
*/
|
|
22030
|
-
function canonicalCapabilitiesForDriftCheck(value) {
|
|
22031
|
-
if (!Array.isArray(value)) return [];
|
|
22032
|
-
const seen = /* @__PURE__ */ new Set();
|
|
22033
|
-
for (const entry of value) if (typeof entry === "string" && entry.length > 0) seen.add(entry);
|
|
22034
|
-
return [...seen].toSorted();
|
|
22035
|
-
}
|
|
22036
|
-
/**
|
|
22037
|
-
* Matches a `(publisher_did, slug)` against the
|
|
22038
|
-
* `minimumReleaseAgeExclude` allowlist. Mirrors the server-side helper
|
|
22039
|
-
* of the same name in `packages/core/src/registry/config.ts`.
|
|
22040
|
-
*
|
|
22041
|
-
* DID-only on purpose: handles are aggregator-supplied envelope data
|
|
22042
|
-
* and accepting them as a trust input would let a compromised
|
|
22043
|
-
* aggregator bypass the holdback by claiming any handle for any
|
|
22044
|
-
* package. DIDs are tied to the AT URI of the record itself.
|
|
22045
|
-
*
|
|
22046
|
-
* Entries from the config list have already been lowercased at
|
|
22047
|
-
* manifest build time, so this only needs to lowercase the runtime
|
|
22048
|
-
* values for comparison.
|
|
22049
|
-
*/
|
|
22050
|
-
function releaseExemptFromMinimumAge(exclude, publisherDid, slug) {
|
|
22051
|
-
if (!exclude || exclude.length === 0) return false;
|
|
22052
|
-
const didLower = publisherDid.toLowerCase();
|
|
22053
|
-
const fullDid = `${didLower}/${slug.toLowerCase()}`;
|
|
22054
|
-
for (const entry of exclude) {
|
|
22055
|
-
if (entry === didLower) return true;
|
|
22056
|
-
if (entry === fullDid) return true;
|
|
22057
|
-
}
|
|
22058
|
-
return false;
|
|
22059
|
-
}
|
|
22060
|
-
async function searchRegistryPackages(config, opts) {
|
|
22061
|
-
return (await getDiscoveryClient(config)).searchPackages(opts);
|
|
22062
|
-
}
|
|
22063
|
-
async function resolveRegistryPackage(config, handle, slug) {
|
|
22064
|
-
return (await getDiscoveryClient(config)).resolvePackage(handle, slug);
|
|
22065
|
-
}
|
|
22066
|
-
async function getRegistryPackage(config, did, slug) {
|
|
22067
|
-
return (await getDiscoveryClient(config)).getPackage(did, slug);
|
|
22068
|
-
}
|
|
22069
|
-
async function getLatestRegistryRelease(config, did, slug) {
|
|
22070
|
-
return (await getDiscoveryClient(config)).getLatestRelease(did, slug);
|
|
22071
|
-
}
|
|
22072
|
-
/**
|
|
22073
|
-
* Resolve a publisher DID to its claimed handle using the same
|
|
22074
|
-
* `LocalActorResolver` pattern as `@emdash-cms/plugin-cli` and
|
|
22075
|
-
* `@emdash-cms/auth-atproto`. Bidirectional verification (handle's
|
|
22076
|
-
* domain points back to the same DID) is part of the resolver --
|
|
22077
|
-
* `LocalActorResolver` returns the sentinel `"handle.invalid"` when
|
|
22078
|
-
* the `alsoKnownAs` handle is present but doesn't round-trip.
|
|
22079
|
-
*
|
|
22080
|
-
* Three distinct outcomes the UI can render:
|
|
22081
|
-
*
|
|
22082
|
-
* - `{ status: "ok", handle }` — verified handle, round-trip OK.
|
|
22083
|
-
* - `{ status: "invalid" }` — DID claims a handle but it doesn't
|
|
22084
|
-
* resolve back. The publisher's handle setup is broken; the admin
|
|
22085
|
-
* should see a clear "Invalid handle" indicator rather than the
|
|
22086
|
-
* raw DID.
|
|
22087
|
-
* - `{ status: "missing" }` — no handle claimed at all (no
|
|
22088
|
-
* `alsoKnownAs`), or the DID document couldn't be fetched (network
|
|
22089
|
-
* error, unsupported DID method).
|
|
22090
|
-
*/
|
|
22091
|
-
let actorResolver = null;
|
|
22092
|
-
async function getActorResolver() {
|
|
22093
|
-
if (actorResolver) return actorResolver;
|
|
22094
|
-
const { CompositeDidDocumentResolver, CompositeHandleResolver, DohJsonHandleResolver, LocalActorResolver, PlcDidDocumentResolver, WebDidDocumentResolver, WellKnownHandleResolver } = await import("@atcute/identity-resolver");
|
|
22095
|
-
actorResolver = new LocalActorResolver({
|
|
22096
|
-
handleResolver: new CompositeHandleResolver({ methods: {
|
|
22097
|
-
dns: new DohJsonHandleResolver({ dohUrl: "https://cloudflare-dns.com/dns-query" }),
|
|
22098
|
-
http: new WellKnownHandleResolver()
|
|
22099
|
-
} }),
|
|
22100
|
-
didDocumentResolver: new CompositeDidDocumentResolver({ methods: {
|
|
22101
|
-
plc: new PlcDidDocumentResolver(),
|
|
22102
|
-
web: new WebDidDocumentResolver()
|
|
22103
|
-
} })
|
|
22104
|
-
});
|
|
22105
|
-
return actorResolver;
|
|
22106
|
-
}
|
|
22107
|
-
/**
|
|
22108
|
-
* localStorage-backed cache for DID→handle resolutions. Handles are
|
|
22109
|
-
* stable for hours-to-days in practice, but bound the cache so a
|
|
22110
|
-
* compromised handle eventually flips back to "invalid" without a
|
|
22111
|
-
* forced refresh. 24h matches the typical atproto handle TTL.
|
|
22112
|
-
*
|
|
22113
|
-
* Failures (network errors, unsupported DID method) are *not* cached --
|
|
22114
|
-
* those should retry on the next render.
|
|
22115
|
-
*/
|
|
22116
|
-
const HANDLE_CACHE_TTL_MS = 1440 * 60 * 1e3;
|
|
22117
|
-
const HANDLE_CACHE_KEY_PREFIX = "emdash:did-handle:";
|
|
22118
|
-
function readHandleCache(did) {
|
|
22119
|
-
if (typeof localStorage === "undefined") return null;
|
|
22120
|
-
try {
|
|
22121
|
-
const raw = localStorage.getItem(`${HANDLE_CACHE_KEY_PREFIX}${did}`);
|
|
22122
|
-
if (!raw) return null;
|
|
22123
|
-
const parsed = JSON.parse(raw);
|
|
22124
|
-
if (!parsed || typeof parsed.expiresAt !== "number" || parsed.expiresAt < Date.now()) return null;
|
|
22125
|
-
return parsed.resolution;
|
|
22126
|
-
} catch {
|
|
22127
|
-
return null;
|
|
22128
|
-
}
|
|
22129
|
-
}
|
|
22130
|
-
function writeHandleCache(did, resolution) {
|
|
22131
|
-
if (typeof localStorage === "undefined") return;
|
|
22132
|
-
try {
|
|
22133
|
-
const entry = {
|
|
22134
|
-
resolution,
|
|
22135
|
-
expiresAt: Date.now() + HANDLE_CACHE_TTL_MS
|
|
22136
|
-
};
|
|
22137
|
-
localStorage.setItem(`${HANDLE_CACHE_KEY_PREFIX}${did}`, JSON.stringify(entry));
|
|
22138
|
-
} catch {}
|
|
22139
|
-
}
|
|
22140
|
-
async function resolveDidToHandle(did) {
|
|
22141
|
-
const cached = readHandleCache(did);
|
|
22142
|
-
if (cached) return cached;
|
|
22143
|
-
let result;
|
|
22144
|
-
try {
|
|
22145
|
-
const resolved = await (await getActorResolver()).resolve(did);
|
|
22146
|
-
if (resolved.handle === "handle.invalid") result = { status: "invalid" };
|
|
22147
|
-
else if (resolved.handle) result = {
|
|
22148
|
-
status: "ok",
|
|
22149
|
-
handle: resolved.handle
|
|
22150
|
-
};
|
|
22151
|
-
else result = { status: "missing" };
|
|
22152
|
-
} catch (err) {
|
|
22153
|
-
console.warn(`[registry] DID->handle resolution failed for ${did}:`, err);
|
|
22154
|
-
return { status: "missing" };
|
|
22155
|
-
}
|
|
22156
|
-
writeHandleCache(did, result);
|
|
22157
|
-
return result;
|
|
22158
|
-
}
|
|
22159
|
-
const INSTALL_ENDPOINT = `${API_BASE}/admin/plugins/registry/install`;
|
|
22160
|
-
/**
|
|
22161
|
-
* Install a plugin from the registry.
|
|
22162
|
-
*
|
|
22163
|
-
* Posts to the EmDash server, which re-resolves the same `(handle,
|
|
22164
|
-
* slug)` against the aggregator, re-verifies the bundle's checksum
|
|
22165
|
-
* against the signed release record, and writes the install. Surfaces
|
|
22166
|
-
* structured error codes (`RELEASE_YANKED`, `CHECKSUM_MISMATCH`,
|
|
22167
|
-
* `DECLARED_ACCESS_DRIFT`, etc.) that callers map to localized
|
|
22168
|
-
* messages.
|
|
22169
|
-
*/
|
|
22170
|
-
async function installRegistryPlugin(body) {
|
|
22171
|
-
const response = await apiFetch(INSTALL_ENDPOINT, {
|
|
22172
|
-
method: "POST",
|
|
22173
|
-
headers: { "Content-Type": "application/json" },
|
|
22174
|
-
body: JSON.stringify(body)
|
|
22175
|
-
});
|
|
22176
|
-
if (!response.ok) await throwResponseError(response, i18n._({
|
|
22177
|
-
id: "/LWKyx",
|
|
22178
|
-
message: "Failed to install plugin"
|
|
22179
|
-
}));
|
|
22180
|
-
return (await response.json()).data;
|
|
22181
|
-
}
|
|
22182
|
-
|
|
22183
22766
|
//#endregion
|
|
22184
22767
|
//#region src/components/PublisherHandle.tsx
|
|
22185
22768
|
/**
|
|
@@ -22473,7 +23056,7 @@ function RegistryPluginDetail({ pluginId, config }) {
|
|
|
22473
23056
|
const { data: installedPlugins } = useQuery({
|
|
22474
23057
|
queryKey: ["plugins"],
|
|
22475
23058
|
queryFn: async () => {
|
|
22476
|
-
const { fetchPlugins } = await import("./plugins-
|
|
23059
|
+
const { fetchPlugins } = await import("./plugins-BZzztFdK.js").then((n) => n.a);
|
|
22477
23060
|
return fetchPlugins();
|
|
22478
23061
|
}
|
|
22479
23062
|
});
|
|
@@ -22494,17 +23077,42 @@ function RegistryPluginDetail({ pluginId, config }) {
|
|
|
22494
23077
|
enabled: Boolean(publisher && slug)
|
|
22495
23078
|
});
|
|
22496
23079
|
const handleResult = usePublisherHandle(pkg?.did ?? "", pkg?.handle);
|
|
22497
|
-
const { data:
|
|
23080
|
+
const { data: releasesData } = useQuery({
|
|
22498
23081
|
queryKey: [
|
|
22499
23082
|
"registry",
|
|
22500
|
-
"
|
|
23083
|
+
"releases",
|
|
22501
23084
|
config.aggregatorUrl,
|
|
23085
|
+
config.acceptLabelers,
|
|
22502
23086
|
pkg?.did,
|
|
22503
23087
|
slug
|
|
22504
23088
|
],
|
|
22505
|
-
queryFn: () =>
|
|
23089
|
+
queryFn: () => listRegistryReleases(config, pkg.did, slug, { limit: 100 }),
|
|
22506
23090
|
enabled: Boolean(pkg?.did && slug)
|
|
22507
23091
|
});
|
|
23092
|
+
const releases = React$1.useMemo(() => (releasesData?.releases ?? []).filter((r) => r.release !== null && !isYanked(r)), [releasesData]);
|
|
23093
|
+
const hasFilteredAllReleases = (releasesData?.releases.length ?? 0) > 0 && releases.length === 0;
|
|
23094
|
+
const defaultVersion = React$1.useMemo(() => {
|
|
23095
|
+
if (!pkg || releases.length === 0) return void 0;
|
|
23096
|
+
return (releases.find((r) => releasePassesPolicy(r, {
|
|
23097
|
+
did: pkg.did,
|
|
23098
|
+
slug
|
|
23099
|
+
}, config.policy)) ?? releases[0])?.version;
|
|
23100
|
+
}, [
|
|
23101
|
+
pkg,
|
|
23102
|
+
releases,
|
|
23103
|
+
slug,
|
|
23104
|
+
config.policy
|
|
23105
|
+
]);
|
|
23106
|
+
const [selectedVersion, setSelectedVersion] = React$1.useState(void 0);
|
|
23107
|
+
const [prevPluginId, setPrevPluginId] = React$1.useState(pluginId);
|
|
23108
|
+
if (prevPluginId !== pluginId) {
|
|
23109
|
+
setPrevPluginId(pluginId);
|
|
23110
|
+
setSelectedVersion(void 0);
|
|
23111
|
+
}
|
|
23112
|
+
if (selectedVersion !== void 0 && releases.length > 0 && !releases.some((r) => r.version === selectedVersion)) setSelectedVersion(void 0);
|
|
23113
|
+
const effectiveVersion = selectedVersion ?? defaultVersion;
|
|
23114
|
+
const release = React$1.useMemo(() => releases.find((r) => r.version === effectiveVersion), [releases, effectiveVersion]);
|
|
23115
|
+
const isPreRelease = release ? isPreReleaseVersion(release.version) : false;
|
|
22508
23116
|
const ext = (release?.release?.extensions)?.["com.emdashcms.experimental.package.releaseExtension"];
|
|
22509
23117
|
const capabilities = Array.isArray(ext?.capabilities) ? canonicalCapabilitiesForDriftCheck(ext?.capabilities) : canonicalCapabilitiesForDriftCheck(declaredAccessToCapabilityList(ext?.declaredAccess));
|
|
22510
23118
|
const pkgProfile = pkg?.profile ?? null;
|
|
@@ -22590,7 +23198,7 @@ function RegistryPluginDetail({ pluginId, config }) {
|
|
|
22590
23198
|
children: [
|
|
22591
23199
|
/* @__PURE__ */ jsx(BackLink, {}),
|
|
22592
23200
|
/* @__PURE__ */ jsxs("div", {
|
|
22593
|
-
className: "flex items-start gap-4",
|
|
23201
|
+
className: "flex flex-wrap items-start gap-4",
|
|
22594
23202
|
children: [
|
|
22595
23203
|
/* @__PURE__ */ jsx("div", {
|
|
22596
23204
|
className: "rounded-xl bg-kumo-subtle p-3 text-kumo-subtle",
|
|
@@ -22630,40 +23238,88 @@ function RegistryPluginDetail({ pluginId, config }) {
|
|
|
22630
23238
|
})
|
|
22631
23239
|
]
|
|
22632
23240
|
}),
|
|
22633
|
-
release ? /* @__PURE__ */ jsxs("
|
|
22634
|
-
className: "
|
|
22635
|
-
children: [
|
|
22636
|
-
|
|
22637
|
-
|
|
22638
|
-
|
|
22639
|
-
|
|
22640
|
-
|
|
22641
|
-
|
|
22642
|
-
|
|
22643
|
-
|
|
22644
|
-
|
|
22645
|
-
|
|
22646
|
-
|
|
22647
|
-
|
|
23241
|
+
release ? /* @__PURE__ */ jsxs("div", {
|
|
23242
|
+
className: "mt-1 flex flex-wrap items-center gap-2",
|
|
23243
|
+
children: [/* @__PURE__ */ jsxs("p", {
|
|
23244
|
+
className: "text-xs text-kumo-subtle",
|
|
23245
|
+
children: [
|
|
23246
|
+
_t({
|
|
23247
|
+
id: "YYdC3A",
|
|
23248
|
+
message: "Version {0}",
|
|
23249
|
+
values: { 0: release.version }
|
|
23250
|
+
}),
|
|
23251
|
+
" · ",
|
|
23252
|
+
_t({
|
|
23253
|
+
id: "294VT5",
|
|
23254
|
+
message: "indexed {0}",
|
|
23255
|
+
values: { 0: formatDate(release.indexedAt) }
|
|
23256
|
+
})
|
|
23257
|
+
]
|
|
23258
|
+
}), isPreRelease ? /* @__PURE__ */ jsx(Badge, { children: _t({
|
|
23259
|
+
id: "omrja/",
|
|
23260
|
+
message: "Pre-release"
|
|
23261
|
+
}) }) : null]
|
|
22648
23262
|
}) : null
|
|
22649
23263
|
]
|
|
22650
23264
|
}),
|
|
22651
|
-
/* @__PURE__ */
|
|
22652
|
-
|
|
22653
|
-
|
|
22654
|
-
|
|
22655
|
-
|
|
22656
|
-
|
|
22657
|
-
|
|
22658
|
-
|
|
22659
|
-
|
|
22660
|
-
|
|
22661
|
-
|
|
22662
|
-
|
|
22663
|
-
|
|
22664
|
-
|
|
22665
|
-
|
|
22666
|
-
|
|
23265
|
+
/* @__PURE__ */ jsxs("div", {
|
|
23266
|
+
className: "flex min-w-0 flex-wrap items-center gap-2",
|
|
23267
|
+
children: [releases.length > 1 ? /* @__PURE__ */ jsx(Select, {
|
|
23268
|
+
"aria-label": _t({
|
|
23269
|
+
id: "eE0JZ4",
|
|
23270
|
+
message: "Version"
|
|
23271
|
+
}),
|
|
23272
|
+
className: "w-full max-w-[220px]",
|
|
23273
|
+
value: effectiveVersion ?? "",
|
|
23274
|
+
onValueChange: (v) => setSelectedVersion(v ?? void 0),
|
|
23275
|
+
renderValue: (v) => typeof v === "string" ? v : "",
|
|
23276
|
+
children: releases.map((r) => {
|
|
23277
|
+
const preRelease = isPreReleaseVersion(r.version);
|
|
23278
|
+
const policyBlocked = !releasePassesPolicy(r, {
|
|
23279
|
+
did: pkg.did,
|
|
23280
|
+
slug
|
|
23281
|
+
}, config.policy);
|
|
23282
|
+
return /* @__PURE__ */ jsx(Select.Option, {
|
|
23283
|
+
value: r.version,
|
|
23284
|
+
children: /* @__PURE__ */ jsxs("span", {
|
|
23285
|
+
className: "flex items-center gap-2",
|
|
23286
|
+
children: [
|
|
23287
|
+
/* @__PURE__ */ jsx("span", { children: r.version }),
|
|
23288
|
+
preRelease ? /* @__PURE__ */ jsx("span", {
|
|
23289
|
+
className: "text-xs text-kumo-subtle",
|
|
23290
|
+
children: _t({
|
|
23291
|
+
id: "D2C1D+",
|
|
23292
|
+
message: "(pre-release)"
|
|
23293
|
+
})
|
|
23294
|
+
}) : null,
|
|
23295
|
+
policyBlocked ? /* @__PURE__ */ jsx("span", {
|
|
23296
|
+
className: "text-xs text-kumo-subtle",
|
|
23297
|
+
children: _t({
|
|
23298
|
+
id: "W3SUIF",
|
|
23299
|
+
message: "(too new)"
|
|
23300
|
+
})
|
|
23301
|
+
}) : null
|
|
23302
|
+
]
|
|
23303
|
+
})
|
|
23304
|
+
}, r.version);
|
|
23305
|
+
})
|
|
23306
|
+
}) : null, isInstalled ? /* @__PURE__ */ jsx(Button, {
|
|
23307
|
+
variant: "secondary",
|
|
23308
|
+
disabled: true,
|
|
23309
|
+
children: _t({
|
|
23310
|
+
id: "eQkgKV",
|
|
23311
|
+
message: "Installed"
|
|
23312
|
+
})
|
|
23313
|
+
}) : /* @__PURE__ */ jsx(Button, {
|
|
23314
|
+
variant: "primary",
|
|
23315
|
+
disabled: !release || !policyOk || handleResult.status === "invalid",
|
|
23316
|
+
onClick: () => setShowConsent(true),
|
|
23317
|
+
children: _t({
|
|
23318
|
+
id: "TKQ7K+",
|
|
23319
|
+
message: "Install"
|
|
23320
|
+
})
|
|
23321
|
+
})]
|
|
23322
|
+
})
|
|
22667
23323
|
]
|
|
22668
23324
|
}),
|
|
22669
23325
|
handleResult.status === "invalid" ? /* @__PURE__ */ jsxs("div", {
|
|
@@ -22683,6 +23339,23 @@ function RegistryPluginDetail({ pluginId, config }) {
|
|
|
22683
23339
|
})
|
|
22684
23340
|
})] })]
|
|
22685
23341
|
}) : null,
|
|
23342
|
+
hasFilteredAllReleases ? /* @__PURE__ */ jsxs("div", {
|
|
23343
|
+
className: "flex items-start gap-3 rounded-md border border-kumo-warning bg-kumo-warning/10 p-4 text-kumo-warning",
|
|
23344
|
+
role: "status",
|
|
23345
|
+
children: [/* @__PURE__ */ jsx(Warning, { className: "mt-0.5 h-5 w-5 shrink-0" }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("p", {
|
|
23346
|
+
className: "font-medium",
|
|
23347
|
+
children: _t({
|
|
23348
|
+
id: "rqsq95",
|
|
23349
|
+
message: "No installable releases"
|
|
23350
|
+
})
|
|
23351
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
23352
|
+
className: "mt-1 text-sm text-kumo-default",
|
|
23353
|
+
children: _t({
|
|
23354
|
+
id: "o+La9d",
|
|
23355
|
+
message: "Every published release of this plugin has been withdrawn or could not be verified. Check back later, or contact the publisher."
|
|
23356
|
+
})
|
|
23357
|
+
})] })]
|
|
23358
|
+
}) : null,
|
|
22686
23359
|
release && !policyOk ? /* @__PURE__ */ jsxs("div", {
|
|
22687
23360
|
className: "flex items-start gap-3 rounded-md border border-kumo-warning bg-kumo-warning/10 p-4 text-kumo-warning",
|
|
22688
23361
|
role: "status",
|
|
@@ -22833,6 +23506,33 @@ function declaredAccessToCapabilityList(declaredAccess) {
|
|
|
22833
23506
|
}
|
|
22834
23507
|
return out;
|
|
22835
23508
|
}
|
|
23509
|
+
const PRE_RELEASE_VERSION_RE = /^\d+\.\d+\.\d+-/;
|
|
23510
|
+
/**
|
|
23511
|
+
* Detects semver pre-release identifiers (`1.0.0-alpha.1`, `2.0.0-rc.2`). The
|
|
23512
|
+
* release lexicon does not enforce semver shape, so a permissive `includes("-")`
|
|
23513
|
+
* check would light up the badge for malformed values like `-1.0.0` or
|
|
23514
|
+
* `abc-def`. Require a `MAJOR.MINOR.PATCH-` prefix instead.
|
|
23515
|
+
*/
|
|
23516
|
+
function isPreReleaseVersion(version) {
|
|
23517
|
+
return PRE_RELEASE_VERSION_RE.test(version);
|
|
23518
|
+
}
|
|
23519
|
+
const YANKED_LABEL_VALUE = "security:yanked";
|
|
23520
|
+
/**
|
|
23521
|
+
* Aggregators forward labels applied by their configured labellers. `security:yanked`
|
|
23522
|
+
* is a hard-enforcement label that publishers can self-apply (or that a labeller
|
|
23523
|
+
* applies on their behalf) to retract a release after publication. Sites whose
|
|
23524
|
+
* `acceptLabelers` config includes the labeller never see yanked releases at all
|
|
23525
|
+
* (server filtering), but sites without it receive yanked releases interleaved
|
|
23526
|
+
* with installable ones — filter them out so they never reach the picker.
|
|
23527
|
+
*
|
|
23528
|
+
* `neg` (negated labels) is intentionally ignored to match the server install
|
|
23529
|
+
* handler, which only checks `l.val === "security:yanked"`. Diverging here would
|
|
23530
|
+
* let the UI surface an install affordance the server will reject with
|
|
23531
|
+
* `RELEASE_YANKED`. Honoring `neg` on both sides is a separate follow-up.
|
|
23532
|
+
*/
|
|
23533
|
+
function isYanked(release) {
|
|
23534
|
+
return (release.labels ?? []).some((l) => l.val === YANKED_LABEL_VALUE);
|
|
23535
|
+
}
|
|
22836
23536
|
function formatDate(iso) {
|
|
22837
23537
|
try {
|
|
22838
23538
|
return new Date(iso).toLocaleDateString();
|
|
@@ -34218,6 +34918,13 @@ function AuthorMappingStep({ authorMappings, emdashUsers, onMappingChange, onCon
|
|
|
34218
34918
|
|
|
34219
34919
|
//#endregion
|
|
34220
34920
|
//#region src/routes/bylines.tsx
|
|
34921
|
+
/**
|
|
34922
|
+
* True when the load-more snapshot still matches the current filter state.
|
|
34923
|
+
* Used to discard appends from requests whose filters have changed mid-flight.
|
|
34924
|
+
*/
|
|
34925
|
+
function loadMoreSnapshotMatches(snapshot, current) {
|
|
34926
|
+
return snapshot.search === current.search && snapshot.guestFilter === current.guestFilter && snapshot.locale === current.locale;
|
|
34927
|
+
}
|
|
34221
34928
|
function toFormState(byline) {
|
|
34222
34929
|
if (!byline) return {
|
|
34223
34930
|
slug: "",
|
|
@@ -34243,21 +34950,39 @@ function getUserLabel(user) {
|
|
|
34243
34950
|
function BylinesPage() {
|
|
34244
34951
|
const { _: _t } = useLingui();
|
|
34245
34952
|
const queryClient = useQueryClient();
|
|
34953
|
+
const navigate = useNavigate$1();
|
|
34954
|
+
const { locale: routeLocale } = useSearch({ from: "/_admin/bylines" });
|
|
34246
34955
|
const [search, setSearch] = React$1.useState("");
|
|
34247
34956
|
const [guestFilter, setGuestFilter] = React$1.useState("all");
|
|
34248
34957
|
const [selectedId, setSelectedId] = React$1.useState(null);
|
|
34249
34958
|
const [showDeleteConfirm, setShowDeleteConfirm] = React$1.useState(false);
|
|
34250
34959
|
const [allItems, setAllItems] = React$1.useState([]);
|
|
34251
34960
|
const [nextCursor, setNextCursor] = React$1.useState(void 0);
|
|
34961
|
+
const { data: manifest } = useQuery({
|
|
34962
|
+
queryKey: ["manifest"],
|
|
34963
|
+
queryFn: fetchManifest
|
|
34964
|
+
});
|
|
34965
|
+
const i18n = useI18nConfig(manifest);
|
|
34966
|
+
const isMultiLocale = !!i18n && i18n.locales.length > 1;
|
|
34967
|
+
const activeLocale = routeLocale ?? i18n?.defaultLocale ?? void 0;
|
|
34968
|
+
const handleLocaleChange = (locale) => {
|
|
34969
|
+
navigate({
|
|
34970
|
+
to: "/bylines",
|
|
34971
|
+
search: { locale: locale || void 0 }
|
|
34972
|
+
});
|
|
34973
|
+
setSelectedId(null);
|
|
34974
|
+
};
|
|
34252
34975
|
const { data, isLoading, error } = useQuery({
|
|
34253
34976
|
queryKey: [
|
|
34254
34977
|
"bylines",
|
|
34255
34978
|
search,
|
|
34256
|
-
guestFilter
|
|
34979
|
+
guestFilter,
|
|
34980
|
+
activeLocale ?? null
|
|
34257
34981
|
],
|
|
34258
34982
|
queryFn: () => fetchBylines({
|
|
34259
34983
|
search: search || void 0,
|
|
34260
34984
|
isGuest: guestFilter === "all" ? void 0 : guestFilter === "guest",
|
|
34985
|
+
locale: activeLocale,
|
|
34261
34986
|
limit: 50
|
|
34262
34987
|
})
|
|
34263
34988
|
});
|
|
@@ -34273,28 +34998,44 @@ function BylinesPage() {
|
|
|
34273
34998
|
});
|
|
34274
34999
|
const users = usersData?.items ?? [];
|
|
34275
35000
|
const loadMoreMutation = useMutation({
|
|
34276
|
-
mutationFn: async () => {
|
|
34277
|
-
|
|
34278
|
-
|
|
34279
|
-
|
|
34280
|
-
|
|
34281
|
-
|
|
34282
|
-
|
|
34283
|
-
|
|
35001
|
+
mutationFn: async (snapshot) => {
|
|
35002
|
+
return {
|
|
35003
|
+
result: await fetchBylines({
|
|
35004
|
+
search: snapshot.search || void 0,
|
|
35005
|
+
isGuest: snapshot.guestFilter === "all" ? void 0 : snapshot.guestFilter === "guest",
|
|
35006
|
+
locale: snapshot.locale,
|
|
35007
|
+
limit: 50,
|
|
35008
|
+
cursor: snapshot.cursor
|
|
35009
|
+
}),
|
|
35010
|
+
snapshot
|
|
35011
|
+
};
|
|
34284
35012
|
},
|
|
34285
|
-
onSuccess: (result) => {
|
|
34286
|
-
if (
|
|
34287
|
-
|
|
34288
|
-
|
|
34289
|
-
|
|
35013
|
+
onSuccess: ({ result, snapshot }) => {
|
|
35014
|
+
if (!loadMoreSnapshotMatches(snapshot, {
|
|
35015
|
+
search,
|
|
35016
|
+
guestFilter,
|
|
35017
|
+
locale: activeLocale
|
|
35018
|
+
})) return;
|
|
35019
|
+
setAllItems((prev) => [...prev, ...result.items]);
|
|
35020
|
+
setNextCursor(result.nextCursor);
|
|
34290
35021
|
}
|
|
34291
35022
|
});
|
|
34292
35023
|
const items = allItems;
|
|
34293
|
-
const
|
|
35024
|
+
const { data: selectedRemote } = useQuery({
|
|
35025
|
+
queryKey: ["byline", selectedId],
|
|
35026
|
+
queryFn: () => selectedId ? fetchByline(selectedId) : Promise.resolve(null),
|
|
35027
|
+
enabled: !!selectedId
|
|
35028
|
+
});
|
|
35029
|
+
const selected = selectedRemote ?? items.find((item) => item.id === selectedId) ?? null;
|
|
34294
35030
|
const [form, setForm] = React$1.useState(() => toFormState(null));
|
|
34295
35031
|
React$1.useEffect(() => {
|
|
34296
35032
|
setForm(toFormState(selected));
|
|
34297
|
-
}, [
|
|
35033
|
+
}, [selected]);
|
|
35034
|
+
const { data: translationsData } = useQuery({
|
|
35035
|
+
queryKey: ["byline-translations", selectedId],
|
|
35036
|
+
queryFn: () => selectedId ? fetchBylineTranslations(selectedId) : Promise.resolve({ items: [] }),
|
|
35037
|
+
enabled: !!selectedId && isMultiLocale
|
|
35038
|
+
});
|
|
34298
35039
|
const createMutation = useMutation({
|
|
34299
35040
|
mutationFn: () => createByline({
|
|
34300
35041
|
slug: form.slug,
|
|
@@ -34302,7 +35043,8 @@ function BylinesPage() {
|
|
|
34302
35043
|
bio: form.bio || null,
|
|
34303
35044
|
websiteUrl: form.websiteUrl || null,
|
|
34304
35045
|
userId: form.userId,
|
|
34305
|
-
isGuest: form.isGuest
|
|
35046
|
+
isGuest: form.isGuest,
|
|
35047
|
+
locale: activeLocale
|
|
34306
35048
|
}),
|
|
34307
35049
|
onSuccess: (created) => {
|
|
34308
35050
|
queryClient.invalidateQueries({ queryKey: ["bylines"] });
|
|
@@ -34323,6 +35065,7 @@ function BylinesPage() {
|
|
|
34323
35065
|
},
|
|
34324
35066
|
onSuccess: () => {
|
|
34325
35067
|
queryClient.invalidateQueries({ queryKey: ["bylines"] });
|
|
35068
|
+
if (selectedId) queryClient.invalidateQueries({ queryKey: ["byline", selectedId] });
|
|
34326
35069
|
}
|
|
34327
35070
|
});
|
|
34328
35071
|
const deleteMutation = useMutation({
|
|
@@ -34336,6 +35079,26 @@ function BylinesPage() {
|
|
|
34336
35079
|
setShowDeleteConfirm(false);
|
|
34337
35080
|
}
|
|
34338
35081
|
});
|
|
35082
|
+
const [pendingTranslationLocale, setPendingTranslationLocale] = React$1.useState(null);
|
|
35083
|
+
const translateMutation = useMutation({
|
|
35084
|
+
mutationFn: (targetLocale) => {
|
|
35085
|
+
if (!selectedId) throw new Error("No byline selected");
|
|
35086
|
+
setPendingTranslationLocale(targetLocale);
|
|
35087
|
+
return createBylineTranslation(selectedId, { locale: targetLocale });
|
|
35088
|
+
},
|
|
35089
|
+
onSettled: () => {
|
|
35090
|
+
setPendingTranslationLocale(null);
|
|
35091
|
+
},
|
|
35092
|
+
onSuccess: (created) => {
|
|
35093
|
+
queryClient.invalidateQueries({ queryKey: ["bylines"] });
|
|
35094
|
+
if (selectedId) queryClient.invalidateQueries({ queryKey: ["byline-translations", selectedId] });
|
|
35095
|
+
navigate({
|
|
35096
|
+
to: "/bylines",
|
|
35097
|
+
search: { locale: created.locale }
|
|
35098
|
+
});
|
|
35099
|
+
setSelectedId(created.id);
|
|
35100
|
+
}
|
|
35101
|
+
});
|
|
34339
35102
|
if (isLoading) return /* @__PURE__ */ jsx("div", {
|
|
34340
35103
|
className: "flex items-center justify-center min-h-[30vh]",
|
|
34341
35104
|
children: /* @__PURE__ */ jsx(Loader, {})
|
|
@@ -34349,230 +35112,273 @@ function BylinesPage() {
|
|
|
34349
35112
|
})
|
|
34350
35113
|
});
|
|
34351
35114
|
const isSaving = createMutation.isPending || updateMutation.isPending;
|
|
34352
|
-
const mutationError = createMutation.error || updateMutation.error || deleteMutation.error;
|
|
35115
|
+
const mutationError = createMutation.error || updateMutation.error || deleteMutation.error || translateMutation.error;
|
|
34353
35116
|
return /* @__PURE__ */ jsxs("div", {
|
|
34354
|
-
className: "
|
|
35117
|
+
className: "space-y-4",
|
|
34355
35118
|
children: [
|
|
35119
|
+
isMultiLocale && i18n && activeLocale ? /* @__PURE__ */ jsxs("div", {
|
|
35120
|
+
className: "flex items-center justify-between",
|
|
35121
|
+
children: [/* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx("h1", {
|
|
35122
|
+
className: "text-2xl font-semibold",
|
|
35123
|
+
children: _t({
|
|
35124
|
+
id: "xLGa7J",
|
|
35125
|
+
message: "Bylines"
|
|
35126
|
+
})
|
|
35127
|
+
}) }), /* @__PURE__ */ jsx(LocaleSwitcher, {
|
|
35128
|
+
locales: i18n.locales,
|
|
35129
|
+
defaultLocale: i18n.defaultLocale,
|
|
35130
|
+
value: activeLocale,
|
|
35131
|
+
onChange: handleLocaleChange
|
|
35132
|
+
})]
|
|
35133
|
+
}) : null,
|
|
34356
35134
|
/* @__PURE__ */ jsxs("div", {
|
|
34357
|
-
className: "
|
|
35135
|
+
className: "grid grid-cols-1 gap-6 lg:grid-cols-[320px_1fr]",
|
|
34358
35136
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
34359
|
-
className: "
|
|
34360
|
-
children: [/* @__PURE__ */
|
|
34361
|
-
|
|
34362
|
-
|
|
34363
|
-
|
|
34364
|
-
|
|
34365
|
-
|
|
34366
|
-
|
|
34367
|
-
|
|
34368
|
-
|
|
34369
|
-
|
|
34370
|
-
className: "flex-
|
|
34371
|
-
children: /* @__PURE__ */ jsx(
|
|
34372
|
-
"
|
|
34373
|
-
|
|
34374
|
-
|
|
34375
|
-
|
|
34376
|
-
|
|
34377
|
-
onValueChange: (v) => setGuestFilter(v ?? "all"),
|
|
34378
|
-
items: {
|
|
34379
|
-
all: _t({
|
|
34380
|
-
id: "VK3dVJ",
|
|
34381
|
-
message: "All bylines"
|
|
34382
|
-
}),
|
|
34383
|
-
guest: _t({
|
|
34384
|
-
id: "+0jFzA",
|
|
34385
|
-
message: "Guest only"
|
|
35137
|
+
className: "rounded-lg border p-4",
|
|
35138
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
35139
|
+
className: "mb-4 space-y-2",
|
|
35140
|
+
children: [/* @__PURE__ */ jsx(Input, {
|
|
35141
|
+
placeholder: _t({
|
|
35142
|
+
id: "4L2LjM",
|
|
35143
|
+
message: "Search bylines"
|
|
35144
|
+
}),
|
|
35145
|
+
value: search,
|
|
35146
|
+
onChange: (e) => setSearch(e.target.value)
|
|
35147
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
35148
|
+
className: "flex items-center gap-2",
|
|
35149
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
35150
|
+
className: "flex-1",
|
|
35151
|
+
children: /* @__PURE__ */ jsx(Select, {
|
|
35152
|
+
"aria-label": _t({
|
|
35153
|
+
id: "kYxs0a",
|
|
35154
|
+
message: "Filter byline type"
|
|
34386
35155
|
}),
|
|
34387
|
-
|
|
34388
|
-
|
|
34389
|
-
|
|
34390
|
-
|
|
35156
|
+
value: guestFilter,
|
|
35157
|
+
onValueChange: (v) => setGuestFilter(v ?? "all"),
|
|
35158
|
+
items: {
|
|
35159
|
+
all: _t({
|
|
35160
|
+
id: "VK3dVJ",
|
|
35161
|
+
message: "All bylines"
|
|
35162
|
+
}),
|
|
35163
|
+
guest: _t({
|
|
35164
|
+
id: "+0jFzA",
|
|
35165
|
+
message: "Guest only"
|
|
35166
|
+
}),
|
|
35167
|
+
linked: _t({
|
|
35168
|
+
id: "RZ5T1J",
|
|
35169
|
+
message: "Linked only"
|
|
35170
|
+
})
|
|
35171
|
+
},
|
|
35172
|
+
className: "w-full"
|
|
35173
|
+
})
|
|
35174
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
35175
|
+
variant: "secondary",
|
|
35176
|
+
onClick: () => {
|
|
35177
|
+
setSelectedId(null);
|
|
35178
|
+
setForm(toFormState(null));
|
|
34391
35179
|
},
|
|
34392
|
-
|
|
34393
|
-
|
|
34394
|
-
|
|
34395
|
-
|
|
34396
|
-
|
|
34397
|
-
setSelectedId(null);
|
|
34398
|
-
setForm(toFormState(null));
|
|
34399
|
-
},
|
|
34400
|
-
children: _t({
|
|
34401
|
-
id: "isRobC",
|
|
34402
|
-
message: "New"
|
|
34403
|
-
})
|
|
35180
|
+
children: _t({
|
|
35181
|
+
id: "isRobC",
|
|
35182
|
+
message: "New"
|
|
35183
|
+
})
|
|
35184
|
+
})]
|
|
34404
35185
|
})]
|
|
35186
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
35187
|
+
className: "space-y-2 max-h-[70vh] overflow-auto",
|
|
35188
|
+
children: [
|
|
35189
|
+
items.map((item) => {
|
|
35190
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
35191
|
+
type: "button",
|
|
35192
|
+
onClick: () => setSelectedId(item.id),
|
|
35193
|
+
className: `w-full rounded border p-3 text-start ${item.id === selectedId ? "border-kumo-brand bg-kumo-brand/10" : "border-kumo-line"}`,
|
|
35194
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
35195
|
+
className: "font-medium",
|
|
35196
|
+
children: item.displayName
|
|
35197
|
+
}), /* @__PURE__ */ jsxs("p", {
|
|
35198
|
+
className: "text-xs text-kumo-subtle",
|
|
35199
|
+
children: [item.slug, item.isGuest ? _t({
|
|
35200
|
+
id: "89z9Fh",
|
|
35201
|
+
message: " - Guest"
|
|
35202
|
+
}) : item.userId ? _t({
|
|
35203
|
+
id: "X4uMa1",
|
|
35204
|
+
message: " - Linked"
|
|
35205
|
+
}) : ""]
|
|
35206
|
+
})]
|
|
35207
|
+
}, item.id);
|
|
35208
|
+
}),
|
|
35209
|
+
items.length === 0 && /* @__PURE__ */ jsx("p", {
|
|
35210
|
+
className: "text-sm text-kumo-subtle",
|
|
35211
|
+
children: _t({
|
|
35212
|
+
id: "Qc/a/A",
|
|
35213
|
+
message: "No bylines found"
|
|
35214
|
+
})
|
|
35215
|
+
}),
|
|
35216
|
+
nextCursor && /* @__PURE__ */ jsx(Button, {
|
|
35217
|
+
variant: "secondary",
|
|
35218
|
+
className: "w-full mt-2",
|
|
35219
|
+
onClick: () => loadMoreMutation.mutate({
|
|
35220
|
+
search,
|
|
35221
|
+
guestFilter,
|
|
35222
|
+
locale: activeLocale,
|
|
35223
|
+
cursor: nextCursor
|
|
35224
|
+
}),
|
|
35225
|
+
disabled: loadMoreMutation.isPending,
|
|
35226
|
+
children: loadMoreMutation.isPending ? _t({
|
|
35227
|
+
id: "Z3FXyt",
|
|
35228
|
+
message: "Loading..."
|
|
35229
|
+
}) : _t({
|
|
35230
|
+
id: "yQ2kGp",
|
|
35231
|
+
message: "Load more"
|
|
35232
|
+
})
|
|
35233
|
+
})
|
|
35234
|
+
]
|
|
34405
35235
|
})]
|
|
34406
35236
|
}), /* @__PURE__ */ jsxs("div", {
|
|
34407
|
-
className: "
|
|
35237
|
+
className: "rounded-lg border p-6 space-y-6",
|
|
34408
35238
|
children: [
|
|
34409
|
-
|
|
34410
|
-
|
|
34411
|
-
|
|
34412
|
-
|
|
34413
|
-
|
|
34414
|
-
|
|
34415
|
-
className: "font-medium",
|
|
34416
|
-
children: item.displayName
|
|
34417
|
-
}), /* @__PURE__ */ jsxs("p", {
|
|
34418
|
-
className: "text-xs text-kumo-subtle",
|
|
34419
|
-
children: [item.slug, item.isGuest ? _t({
|
|
34420
|
-
id: "89z9Fh",
|
|
34421
|
-
message: " - Guest"
|
|
34422
|
-
}) : item.userId ? _t({
|
|
34423
|
-
id: "X4uMa1",
|
|
34424
|
-
message: " - Linked"
|
|
34425
|
-
}) : ""]
|
|
34426
|
-
})]
|
|
34427
|
-
}, item.id);
|
|
34428
|
-
}),
|
|
34429
|
-
items.length === 0 && /* @__PURE__ */ jsx("p", {
|
|
34430
|
-
className: "text-sm text-kumo-subtle",
|
|
34431
|
-
children: _t({
|
|
34432
|
-
id: "Qc/a/A",
|
|
34433
|
-
message: "No bylines found"
|
|
34434
|
-
})
|
|
34435
|
-
}),
|
|
34436
|
-
nextCursor && /* @__PURE__ */ jsx(Button, {
|
|
34437
|
-
variant: "secondary",
|
|
34438
|
-
className: "w-full mt-2",
|
|
34439
|
-
onClick: () => loadMoreMutation.mutate(),
|
|
34440
|
-
disabled: loadMoreMutation.isPending,
|
|
34441
|
-
children: loadMoreMutation.isPending ? _t({
|
|
34442
|
-
id: "Z3FXyt",
|
|
34443
|
-
message: "Loading..."
|
|
35239
|
+
/* @__PURE__ */ jsx("h2", {
|
|
35240
|
+
className: "text-lg font-semibold",
|
|
35241
|
+
children: selected ? _t({
|
|
35242
|
+
id: "N6j2JH",
|
|
35243
|
+
message: "Edit {0}",
|
|
35244
|
+
values: { 0: selected.displayName }
|
|
34444
35245
|
}) : _t({
|
|
34445
|
-
id: "
|
|
34446
|
-
message: "
|
|
35246
|
+
id: "d6HWlw",
|
|
35247
|
+
message: "Create byline"
|
|
34447
35248
|
})
|
|
34448
|
-
})
|
|
34449
|
-
]
|
|
34450
|
-
})]
|
|
34451
|
-
}),
|
|
34452
|
-
/* @__PURE__ */ jsxs("div", {
|
|
34453
|
-
className: "rounded-lg border p-6",
|
|
34454
|
-
children: [/* @__PURE__ */ jsx("h2", {
|
|
34455
|
-
className: "text-lg font-semibold mb-4",
|
|
34456
|
-
children: selected ? _t({
|
|
34457
|
-
id: "N6j2JH",
|
|
34458
|
-
message: "Edit {0}",
|
|
34459
|
-
values: { 0: selected.displayName }
|
|
34460
|
-
}) : _t({
|
|
34461
|
-
id: "d6HWlw",
|
|
34462
|
-
message: "Create byline"
|
|
34463
|
-
})
|
|
34464
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
34465
|
-
className: "space-y-4",
|
|
34466
|
-
children: [
|
|
34467
|
-
/* @__PURE__ */ jsx(Input, {
|
|
34468
|
-
label: _t({
|
|
34469
|
-
id: "pfa8F0",
|
|
34470
|
-
message: "Display name"
|
|
34471
|
-
}),
|
|
34472
|
-
value: form.displayName,
|
|
34473
|
-
onChange: (e) => setForm((prev) => ({
|
|
34474
|
-
...prev,
|
|
34475
|
-
displayName: e.target.value
|
|
34476
|
-
}))
|
|
34477
|
-
}),
|
|
34478
|
-
/* @__PURE__ */ jsx(Input, {
|
|
34479
|
-
label: _t({
|
|
34480
|
-
id: "L85WcV",
|
|
34481
|
-
message: "Slug"
|
|
34482
|
-
}),
|
|
34483
|
-
value: form.slug,
|
|
34484
|
-
onChange: (e) => setForm((prev) => ({
|
|
34485
|
-
...prev,
|
|
34486
|
-
slug: e.target.value
|
|
34487
|
-
}))
|
|
34488
|
-
}),
|
|
34489
|
-
/* @__PURE__ */ jsx(Input, {
|
|
34490
|
-
label: _t({
|
|
34491
|
-
id: "XYcw7G",
|
|
34492
|
-
message: "Website URL"
|
|
34493
|
-
}),
|
|
34494
|
-
value: form.websiteUrl,
|
|
34495
|
-
onChange: (e) => setForm((prev) => ({
|
|
34496
|
-
...prev,
|
|
34497
|
-
websiteUrl: e.target.value
|
|
34498
|
-
}))
|
|
34499
|
-
}),
|
|
34500
|
-
/* @__PURE__ */ jsx(InputArea, {
|
|
34501
|
-
label: _t({
|
|
34502
|
-
id: "E+oKCO",
|
|
34503
|
-
message: "Bio"
|
|
34504
|
-
}),
|
|
34505
|
-
value: form.bio,
|
|
34506
|
-
onChange: (e) => setForm((prev) => ({
|
|
34507
|
-
...prev,
|
|
34508
|
-
bio: e.target.value
|
|
34509
|
-
})),
|
|
34510
|
-
rows: 5
|
|
34511
35249
|
}),
|
|
34512
|
-
/* @__PURE__ */
|
|
34513
|
-
|
|
34514
|
-
|
|
34515
|
-
|
|
34516
|
-
|
|
34517
|
-
|
|
34518
|
-
|
|
34519
|
-
|
|
34520
|
-
|
|
34521
|
-
|
|
34522
|
-
|
|
34523
|
-
|
|
34524
|
-
|
|
34525
|
-
},
|
|
34526
|
-
items: {
|
|
34527
|
-
"": _t({
|
|
34528
|
-
id: "n9o2+L",
|
|
34529
|
-
message: "No linked user"
|
|
35250
|
+
/* @__PURE__ */ jsxs("div", {
|
|
35251
|
+
className: "space-y-4",
|
|
35252
|
+
children: [
|
|
35253
|
+
/* @__PURE__ */ jsx(Input, {
|
|
35254
|
+
label: _t({
|
|
35255
|
+
id: "pfa8F0",
|
|
35256
|
+
message: "Display name"
|
|
35257
|
+
}),
|
|
35258
|
+
value: form.displayName,
|
|
35259
|
+
onChange: (e) => setForm((prev) => ({
|
|
35260
|
+
...prev,
|
|
35261
|
+
displayName: e.target.value
|
|
35262
|
+
}))
|
|
34530
35263
|
}),
|
|
34531
|
-
|
|
34532
|
-
|
|
34533
|
-
|
|
34534
|
-
|
|
34535
|
-
|
|
34536
|
-
|
|
34537
|
-
|
|
34538
|
-
|
|
34539
|
-
|
|
34540
|
-
|
|
34541
|
-
|
|
34542
|
-
|
|
34543
|
-
|
|
34544
|
-
|
|
34545
|
-
|
|
35264
|
+
/* @__PURE__ */ jsx(Input, {
|
|
35265
|
+
label: _t({
|
|
35266
|
+
id: "L85WcV",
|
|
35267
|
+
message: "Slug"
|
|
35268
|
+
}),
|
|
35269
|
+
value: form.slug,
|
|
35270
|
+
onChange: (e) => setForm((prev) => ({
|
|
35271
|
+
...prev,
|
|
35272
|
+
slug: e.target.value
|
|
35273
|
+
}))
|
|
35274
|
+
}),
|
|
35275
|
+
/* @__PURE__ */ jsx(Input, {
|
|
35276
|
+
label: _t({
|
|
35277
|
+
id: "XYcw7G",
|
|
35278
|
+
message: "Website URL"
|
|
35279
|
+
}),
|
|
35280
|
+
value: form.websiteUrl,
|
|
35281
|
+
onChange: (e) => setForm((prev) => ({
|
|
35282
|
+
...prev,
|
|
35283
|
+
websiteUrl: e.target.value
|
|
35284
|
+
}))
|
|
35285
|
+
}),
|
|
35286
|
+
/* @__PURE__ */ jsx(InputArea, {
|
|
35287
|
+
label: _t({
|
|
35288
|
+
id: "E+oKCO",
|
|
35289
|
+
message: "Bio"
|
|
35290
|
+
}),
|
|
35291
|
+
value: form.bio,
|
|
35292
|
+
onChange: (e) => setForm((prev) => ({
|
|
35293
|
+
...prev,
|
|
35294
|
+
bio: e.target.value
|
|
35295
|
+
})),
|
|
35296
|
+
rows: 5
|
|
35297
|
+
}),
|
|
35298
|
+
/* @__PURE__ */ jsx(Select, {
|
|
35299
|
+
label: _t({
|
|
35300
|
+
id: "nti0N3",
|
|
35301
|
+
message: "Linked user"
|
|
35302
|
+
}),
|
|
35303
|
+
value: form.userId ?? "",
|
|
35304
|
+
onValueChange: (v) => {
|
|
35305
|
+
const val = v || null;
|
|
35306
|
+
setForm((prev) => ({
|
|
35307
|
+
...prev,
|
|
35308
|
+
userId: val,
|
|
35309
|
+
isGuest: val ? false : prev.isGuest
|
|
35310
|
+
}));
|
|
35311
|
+
},
|
|
35312
|
+
items: {
|
|
35313
|
+
"": _t({
|
|
35314
|
+
id: "n9o2+L",
|
|
35315
|
+
message: "No linked user"
|
|
35316
|
+
}),
|
|
35317
|
+
...Object.fromEntries(users.map((u) => [u.id, getUserLabel(u)]))
|
|
35318
|
+
},
|
|
35319
|
+
className: "w-full"
|
|
35320
|
+
}),
|
|
35321
|
+
/* @__PURE__ */ jsx(Switch, {
|
|
35322
|
+
label: _t({
|
|
35323
|
+
id: "6CRaxp",
|
|
35324
|
+
message: "Guest byline"
|
|
35325
|
+
}),
|
|
35326
|
+
checked: form.isGuest,
|
|
35327
|
+
onCheckedChange: (checked) => setForm((prev) => ({
|
|
35328
|
+
...prev,
|
|
35329
|
+
isGuest: checked,
|
|
35330
|
+
userId: checked ? null : prev.userId
|
|
35331
|
+
}))
|
|
35332
|
+
}),
|
|
35333
|
+
/* @__PURE__ */ jsx(DialogError, { message: getMutationError(mutationError) }),
|
|
35334
|
+
/* @__PURE__ */ jsxs("div", {
|
|
35335
|
+
className: "flex gap-2 pt-2",
|
|
35336
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
35337
|
+
onClick: () => {
|
|
35338
|
+
if (selected) updateMutation.mutate();
|
|
35339
|
+
else createMutation.mutate();
|
|
35340
|
+
},
|
|
35341
|
+
disabled: !form.displayName || !form.slug || isSaving,
|
|
35342
|
+
children: isSaving ? _t({
|
|
35343
|
+
id: "XvjC4F",
|
|
35344
|
+
message: "Saving..."
|
|
35345
|
+
}) : selected ? _t({
|
|
35346
|
+
id: "tfDRzk",
|
|
35347
|
+
message: "Save"
|
|
35348
|
+
}) : _t({
|
|
35349
|
+
id: "hYgDIe",
|
|
35350
|
+
message: "Create"
|
|
35351
|
+
})
|
|
35352
|
+
}), selected && /* @__PURE__ */ jsx(Button, {
|
|
35353
|
+
variant: "destructive",
|
|
35354
|
+
onClick: () => setShowDeleteConfirm(true),
|
|
35355
|
+
disabled: deleteMutation.isPending,
|
|
35356
|
+
children: _t({
|
|
35357
|
+
id: "cnGeoo",
|
|
35358
|
+
message: "Delete"
|
|
35359
|
+
})
|
|
35360
|
+
})]
|
|
35361
|
+
})
|
|
35362
|
+
]
|
|
34546
35363
|
}),
|
|
34547
|
-
/* @__PURE__ */ jsx(
|
|
34548
|
-
|
|
34549
|
-
|
|
34550
|
-
|
|
34551
|
-
|
|
34552
|
-
|
|
34553
|
-
|
|
35364
|
+
selected && isMultiLocale && i18n ? /* @__PURE__ */ jsx("div", {
|
|
35365
|
+
className: "border-t pt-6",
|
|
35366
|
+
children: /* @__PURE__ */ jsx(TranslationsPanel, {
|
|
35367
|
+
locales: i18n.locales,
|
|
35368
|
+
defaultLocale: i18n.defaultLocale,
|
|
35369
|
+
currentLocale: selected.locale,
|
|
35370
|
+
translations: translationsData?.items ?? [],
|
|
35371
|
+
onOpen: (summary) => {
|
|
35372
|
+
navigate({
|
|
35373
|
+
to: "/bylines",
|
|
35374
|
+
search: { locale: summary.locale }
|
|
35375
|
+
});
|
|
35376
|
+
setSelectedId(summary.id);
|
|
34554
35377
|
},
|
|
34555
|
-
|
|
34556
|
-
|
|
34557
|
-
|
|
34558
|
-
|
|
34559
|
-
}) : selected ? _t({
|
|
34560
|
-
id: "tfDRzk",
|
|
34561
|
-
message: "Save"
|
|
34562
|
-
}) : _t({
|
|
34563
|
-
id: "hYgDIe",
|
|
34564
|
-
message: "Create"
|
|
34565
|
-
})
|
|
34566
|
-
}), selected && /* @__PURE__ */ jsx(Button, {
|
|
34567
|
-
variant: "destructive",
|
|
34568
|
-
onClick: () => setShowDeleteConfirm(true),
|
|
34569
|
-
disabled: deleteMutation.isPending,
|
|
34570
|
-
children: _t({
|
|
34571
|
-
id: "cnGeoo",
|
|
34572
|
-
message: "Delete"
|
|
34573
|
-
})
|
|
34574
|
-
})]
|
|
34575
|
-
})
|
|
35378
|
+
onCreate: (locale) => translateMutation.mutate(locale),
|
|
35379
|
+
pendingLocale: pendingTranslationLocale
|
|
35380
|
+
})
|
|
35381
|
+
}) : null
|
|
34576
35382
|
]
|
|
34577
35383
|
})]
|
|
34578
35384
|
}),
|
|
@@ -36140,10 +36946,11 @@ function ContentNewPage() {
|
|
|
36140
36946
|
queryKey: ["manifest"],
|
|
36141
36947
|
queryFn: fetchManifest
|
|
36142
36948
|
});
|
|
36949
|
+
const pickerLocale = locale ?? manifest?.i18n?.defaultLocale;
|
|
36143
36950
|
const createMutation = useMutation({
|
|
36144
36951
|
mutationFn: (data) => createContent(collection, {
|
|
36145
36952
|
...data,
|
|
36146
|
-
locale
|
|
36953
|
+
locale: pickerLocale
|
|
36147
36954
|
}),
|
|
36148
36955
|
onSuccess: (result) => {
|
|
36149
36956
|
queryClient.invalidateQueries({ queryKey: ["content", collection] });
|
|
@@ -36157,14 +36964,23 @@ function ContentNewPage() {
|
|
|
36157
36964
|
}
|
|
36158
36965
|
});
|
|
36159
36966
|
const pluginBlocks = React$1.useMemo(() => manifest ? getPluginBlocks(manifest) : [], [manifest]);
|
|
36160
|
-
const { data: bylinesData } = useQuery({
|
|
36161
|
-
queryKey: [
|
|
36162
|
-
|
|
36967
|
+
const { data: bylinesData, isSuccess: bylinesLoaded } = useQuery({
|
|
36968
|
+
queryKey: [
|
|
36969
|
+
"bylines",
|
|
36970
|
+
"picker",
|
|
36971
|
+
pickerLocale ?? null
|
|
36972
|
+
],
|
|
36973
|
+
queryFn: () => fetchBylines({
|
|
36974
|
+
locale: pickerLocale,
|
|
36975
|
+
limit: 100
|
|
36976
|
+
}),
|
|
36977
|
+
enabled: !!manifest
|
|
36163
36978
|
});
|
|
36164
36979
|
const createBylineMutation = useMutation({
|
|
36165
36980
|
mutationFn: (input) => createByline({
|
|
36166
36981
|
...input,
|
|
36167
|
-
isGuest: true
|
|
36982
|
+
isGuest: true,
|
|
36983
|
+
locale: pickerLocale
|
|
36168
36984
|
}),
|
|
36169
36985
|
onSuccess: () => {
|
|
36170
36986
|
queryClient.invalidateQueries({ queryKey: ["bylines"] });
|
|
@@ -36190,10 +37006,13 @@ function ContentNewPage() {
|
|
|
36190
37006
|
collectionLabel: collectionConfig.labelSingular || collectionConfig.label,
|
|
36191
37007
|
fields: collectionConfig.fields,
|
|
36192
37008
|
isNew: true,
|
|
37009
|
+
entryLocale: pickerLocale,
|
|
37010
|
+
i18n: manifest?.i18n,
|
|
36193
37011
|
isSaving: createMutation.isPending,
|
|
36194
37012
|
onSave: handleSave,
|
|
36195
37013
|
pluginBlocks,
|
|
36196
37014
|
availableBylines: bylinesData?.items,
|
|
37015
|
+
availableBylinesLoaded: bylinesLoaded,
|
|
36197
37016
|
selectedBylines,
|
|
36198
37017
|
onBylinesChange: setSelectedBylines,
|
|
36199
37018
|
onQuickCreateByline: async (input) => {
|
|
@@ -36303,14 +37122,24 @@ function ContentEditPage() {
|
|
|
36303
37122
|
enabled: !!currentUser && currentUser.role >= ROLE_EDITOR,
|
|
36304
37123
|
staleTime: 300 * 1e3
|
|
36305
37124
|
});
|
|
36306
|
-
const
|
|
36307
|
-
|
|
36308
|
-
|
|
37125
|
+
const itemLocale = rawItem?.locale ?? void 0;
|
|
37126
|
+
const { data: bylinesData, isSuccess: bylinesLoaded } = useQuery({
|
|
37127
|
+
queryKey: [
|
|
37128
|
+
"bylines",
|
|
37129
|
+
"picker",
|
|
37130
|
+
itemLocale ?? null
|
|
37131
|
+
],
|
|
37132
|
+
queryFn: () => fetchBylines({
|
|
37133
|
+
locale: itemLocale,
|
|
37134
|
+
limit: 100
|
|
37135
|
+
}),
|
|
37136
|
+
enabled: !!itemLocale
|
|
36309
37137
|
});
|
|
36310
37138
|
const createBylineMutation = useMutation({
|
|
36311
37139
|
mutationFn: (input) => createByline({
|
|
36312
37140
|
...input,
|
|
36313
|
-
isGuest: true
|
|
37141
|
+
isGuest: true,
|
|
37142
|
+
locale: itemLocale
|
|
36314
37143
|
}),
|
|
36315
37144
|
onSuccess: () => {
|
|
36316
37145
|
queryClient.invalidateQueries({ queryKey: ["bylines"] });
|
|
@@ -36693,6 +37522,7 @@ function ContentEditPage() {
|
|
|
36693
37522
|
hasSeo: collectionConfig.hasSeo,
|
|
36694
37523
|
onSeoChange: handleSeoChange,
|
|
36695
37524
|
availableBylines: bylinesData?.items,
|
|
37525
|
+
availableBylinesLoaded: bylinesLoaded,
|
|
36696
37526
|
onQuickCreateByline: async (input) => {
|
|
36697
37527
|
return await createBylineMutation.mutateAsync(input);
|
|
36698
37528
|
},
|
|
@@ -36985,7 +37815,7 @@ function MarketplaceBrowsePage() {
|
|
|
36985
37815
|
const { data: plugins } = useQuery({
|
|
36986
37816
|
queryKey: ["plugins"],
|
|
36987
37817
|
queryFn: async () => {
|
|
36988
|
-
const { fetchPlugins } = await import("./plugins-
|
|
37818
|
+
const { fetchPlugins } = await import("./plugins-BZzztFdK.js").then((n) => n.a);
|
|
36989
37819
|
return fetchPlugins();
|
|
36990
37820
|
}
|
|
36991
37821
|
});
|
|
@@ -37016,7 +37846,7 @@ function MarketplaceDetailPage() {
|
|
|
37016
37846
|
const { data: plugins } = useQuery({
|
|
37017
37847
|
queryKey: ["plugins"],
|
|
37018
37848
|
queryFn: async () => {
|
|
37019
|
-
const { fetchPlugins } = await import("./plugins-
|
|
37849
|
+
const { fetchPlugins } = await import("./plugins-BZzztFdK.js").then((n) => n.a);
|
|
37020
37850
|
return fetchPlugins();
|
|
37021
37851
|
}
|
|
37022
37852
|
});
|
|
@@ -37100,10 +37930,15 @@ const usersRoute = createRoute({
|
|
|
37100
37930
|
path: "/users",
|
|
37101
37931
|
component: UsersPage
|
|
37102
37932
|
});
|
|
37933
|
+
function parseBylinesLocaleSearch(search) {
|
|
37934
|
+
if (typeof search.locale === "string" && search.locale.length > 0) return { locale: search.locale };
|
|
37935
|
+
return { locale: void 0 };
|
|
37936
|
+
}
|
|
37103
37937
|
const bylinesRoute = createRoute({
|
|
37104
37938
|
getParentRoute: () => adminLayoutRoute,
|
|
37105
37939
|
path: "/bylines",
|
|
37106
|
-
component: BylinesPage
|
|
37940
|
+
component: BylinesPage,
|
|
37941
|
+
validateSearch: parseBylinesLocaleSearch
|
|
37107
37942
|
});
|
|
37108
37943
|
const contentTypesListRoute = createRoute({
|
|
37109
37944
|
getParentRoute: () => adminLayoutRoute,
|
|
@@ -37488,5 +38323,5 @@ function AdminApp({ pluginAdmins = EMPTY_PLUGINS, authProviders = EMPTY_AUTH_PRO
|
|
|
37488
38323
|
}
|
|
37489
38324
|
|
|
37490
38325
|
//#endregion
|
|
37491
|
-
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, 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, 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 };
|
|
38326
|
+
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 };
|
|
37492
38327
|
//# sourceMappingURL=index.js.map
|