@emdash-cms/admin 0.13.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-rC0KuZjM.js → LocaleDirectionProvider-C-w4lOU2.js} +17 -17
- package/dist/{LocaleDirectionProvider-rC0KuZjM.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 +1559 -573
- 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-BB7Ta5GU.js +0 -6
- package/dist/messages-BB7Ta5GU.js.map +0 -1
- package/dist/messages-BMyaLK4h.js +0 -6
- package/dist/messages-BMyaLK4h.js.map +0 -1
- package/dist/messages-BRIi_T5q.js +0 -6
- package/dist/messages-BRIi_T5q.js.map +0 -1
- package/dist/messages-Be0FUVz_.js +0 -6
- package/dist/messages-Be0FUVz_.js.map +0 -1
- package/dist/messages-BvE8RBIJ.js +0 -6
- package/dist/messages-BvE8RBIJ.js.map +0 -1
- package/dist/messages-CBHhMxhB.js +0 -6
- package/dist/messages-CBHhMxhB.js.map +0 -1
- package/dist/messages-CQJpnG4Q.js +0 -6
- package/dist/messages-CQJpnG4Q.js.map +0 -1
- package/dist/messages-CZpDTTb_.js +0 -6
- package/dist/messages-CZpDTTb_.js.map +0 -1
- package/dist/messages-CemjGhIt.js +0 -6
- package/dist/messages-CemjGhIt.js.map +0 -1
- package/dist/messages-Czya1QvU.js +0 -6
- package/dist/messages-Czya1QvU.js.map +0 -1
- package/dist/messages-DR7ouL8H.js +0 -6
- package/dist/messages-DR7ouL8H.js.map +0 -1
- package/dist/messages-DyiZEfUj.js +0 -6
- package/dist/messages-DyiZEfUj.js.map +0 -1
- package/dist/messages-JMuy3Tpu.js +0 -6
- package/dist/messages-JMuy3Tpu.js.map +0 -1
- package/dist/messages-h9IORlKh.js +0 -6
- package/dist/messages-h9IORlKh.js.map +0 -1
- package/dist/messages-iu8SG58F.js +0 -6
- package/dist/messages-iu8SG58F.js.map +0 -1
- package/dist/messages-uD16X789.js +0 -6
- package/dist/messages-uD16X789.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 await discovery.searchPackages({
|
|
21967
|
-
q: opts.q,
|
|
21968
|
-
cursor: opts.cursor,
|
|
21969
|
-
limit: opts.limit
|
|
21970
|
-
});
|
|
21971
|
-
},
|
|
21972
|
-
async resolvePackage(handle, slug) {
|
|
21973
|
-
return await discovery.resolvePackage({
|
|
21974
|
-
handle,
|
|
21975
|
-
slug
|
|
21976
|
-
});
|
|
21977
|
-
},
|
|
21978
|
-
async getPackage(did, slug) {
|
|
21979
|
-
return await discovery.getPackage({
|
|
21980
|
-
did,
|
|
21981
|
-
slug
|
|
21982
|
-
});
|
|
21983
|
-
},
|
|
21984
|
-
async getLatestRelease(did, slug) {
|
|
21985
|
-
return await discovery.getLatestRelease({
|
|
21986
|
-
did,
|
|
21987
|
-
package: slug
|
|
21988
|
-
});
|
|
21989
|
-
},
|
|
21990
|
-
async listReleases(did, slug, cursor) {
|
|
21991
|
-
return await 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
|
/**
|
|
@@ -22392,7 +22975,9 @@ function RegistryBrowse({ config, installedRegistryUris = /* @__PURE__ */ new Se
|
|
|
22392
22975
|
function RegistryPackageCard({ pkg, installed }) {
|
|
22393
22976
|
const { _: _t2 } = useLingui();
|
|
22394
22977
|
const linkSegment = usePublisherHandle(pkg.did, pkg.handle).handle ?? pkg.did;
|
|
22395
|
-
const
|
|
22978
|
+
const name = pkg.profile?.name;
|
|
22979
|
+
const description = pkg.profile?.description;
|
|
22980
|
+
const license = pkg.profile?.license;
|
|
22396
22981
|
const verified = (pkg.labels ?? []).some((l) => l.val === "verified");
|
|
22397
22982
|
return /* @__PURE__ */ jsx(Link$1, {
|
|
22398
22983
|
to: "/plugins/marketplace/$pluginId",
|
|
@@ -22410,7 +22995,7 @@ function RegistryPackageCard({ pkg, installed }) {
|
|
|
22410
22995
|
className: "flex items-center gap-2",
|
|
22411
22996
|
children: [/* @__PURE__ */ jsx("h2", {
|
|
22412
22997
|
className: "truncate font-semibold",
|
|
22413
|
-
children:
|
|
22998
|
+
children: name ?? pkg.slug
|
|
22414
22999
|
}), verified ? /* @__PURE__ */ jsx(ShieldCheck, {
|
|
22415
23000
|
className: "h-4 w-4 shrink-0 text-kumo-brand",
|
|
22416
23001
|
"aria-label": _t2({
|
|
@@ -22424,9 +23009,13 @@ function RegistryPackageCard({ pkg, installed }) {
|
|
|
22424
23009
|
aggregatorHandle: pkg.handle,
|
|
22425
23010
|
variant: "card"
|
|
22426
23011
|
}),
|
|
22427
|
-
|
|
23012
|
+
description ? /* @__PURE__ */ jsx("p", {
|
|
22428
23013
|
className: "mt-2 line-clamp-2 text-sm text-kumo-default",
|
|
22429
|
-
children:
|
|
23014
|
+
children: description
|
|
23015
|
+
}) : null,
|
|
23016
|
+
license ? /* @__PURE__ */ jsx("p", {
|
|
23017
|
+
className: "mt-2 text-xs text-kumo-subtle",
|
|
23018
|
+
children: license
|
|
22430
23019
|
}) : null,
|
|
22431
23020
|
installed ? /* @__PURE__ */ jsx("div", {
|
|
22432
23021
|
className: "mt-3",
|
|
@@ -22467,7 +23056,7 @@ function RegistryPluginDetail({ pluginId, config }) {
|
|
|
22467
23056
|
const { data: installedPlugins } = useQuery({
|
|
22468
23057
|
queryKey: ["plugins"],
|
|
22469
23058
|
queryFn: async () => {
|
|
22470
|
-
const { fetchPlugins } = await import("./plugins-
|
|
23059
|
+
const { fetchPlugins } = await import("./plugins-BZzztFdK.js").then((n) => n.a);
|
|
22471
23060
|
return fetchPlugins();
|
|
22472
23061
|
}
|
|
22473
23062
|
});
|
|
@@ -22488,20 +23077,63 @@ function RegistryPluginDetail({ pluginId, config }) {
|
|
|
22488
23077
|
enabled: Boolean(publisher && slug)
|
|
22489
23078
|
});
|
|
22490
23079
|
const handleResult = usePublisherHandle(pkg?.did ?? "", pkg?.handle);
|
|
22491
|
-
const { data:
|
|
23080
|
+
const { data: releasesData } = useQuery({
|
|
22492
23081
|
queryKey: [
|
|
22493
23082
|
"registry",
|
|
22494
|
-
"
|
|
23083
|
+
"releases",
|
|
22495
23084
|
config.aggregatorUrl,
|
|
23085
|
+
config.acceptLabelers,
|
|
22496
23086
|
pkg?.did,
|
|
22497
23087
|
slug
|
|
22498
23088
|
],
|
|
22499
|
-
queryFn: () =>
|
|
23089
|
+
queryFn: () => listRegistryReleases(config, pkg.did, slug, { limit: 100 }),
|
|
22500
23090
|
enabled: Boolean(pkg?.did && slug)
|
|
22501
23091
|
});
|
|
22502
|
-
const
|
|
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;
|
|
23116
|
+
const ext = (release?.release?.extensions)?.["com.emdashcms.experimental.package.releaseExtension"];
|
|
22503
23117
|
const capabilities = Array.isArray(ext?.capabilities) ? canonicalCapabilitiesForDriftCheck(ext?.capabilities) : canonicalCapabilitiesForDriftCheck(declaredAccessToCapabilityList(ext?.declaredAccess));
|
|
22504
|
-
const
|
|
23118
|
+
const pkgProfile = pkg?.profile ?? null;
|
|
23119
|
+
const displayName = pkgProfile?.name;
|
|
23120
|
+
const description = pkgProfile?.description;
|
|
23121
|
+
const licenseText = pkgProfile?.license;
|
|
23122
|
+
const keywordList = pkgProfile?.keywords ?? [];
|
|
23123
|
+
const authorList = (pkgProfile?.authors ?? []).map((a) => ({
|
|
23124
|
+
name: a.name,
|
|
23125
|
+
url: safeExternalHref(a.url),
|
|
23126
|
+
email: safeEmail(a.email)
|
|
23127
|
+
}));
|
|
23128
|
+
const securityList = (pkgProfile?.security ?? []).flatMap((c) => {
|
|
23129
|
+
const url = safeExternalHref(c.url);
|
|
23130
|
+
const email = safeEmail(c.email);
|
|
23131
|
+
return url || email ? [{
|
|
23132
|
+
url,
|
|
23133
|
+
email
|
|
23134
|
+
}] : [];
|
|
23135
|
+
});
|
|
23136
|
+
const repoHref = safeExternalHref(release?.release?.repo);
|
|
22505
23137
|
const verified = (pkg?.labels ?? []).some((l) => l.val === "verified");
|
|
22506
23138
|
const policyOk = release && pkg ? releasePassesPolicy(release, {
|
|
22507
23139
|
did: pkg.did,
|
|
@@ -22566,7 +23198,7 @@ function RegistryPluginDetail({ pluginId, config }) {
|
|
|
22566
23198
|
children: [
|
|
22567
23199
|
/* @__PURE__ */ jsx(BackLink, {}),
|
|
22568
23200
|
/* @__PURE__ */ jsxs("div", {
|
|
22569
|
-
className: "flex items-start gap-4",
|
|
23201
|
+
className: "flex flex-wrap items-start gap-4",
|
|
22570
23202
|
children: [
|
|
22571
23203
|
/* @__PURE__ */ jsx("div", {
|
|
22572
23204
|
className: "rounded-xl bg-kumo-subtle p-3 text-kumo-subtle",
|
|
@@ -22582,7 +23214,7 @@ function RegistryPluginDetail({ pluginId, config }) {
|
|
|
22582
23214
|
className: "flex items-center gap-2",
|
|
22583
23215
|
children: [/* @__PURE__ */ jsx("h1", {
|
|
22584
23216
|
className: "truncate text-3xl font-bold",
|
|
22585
|
-
children:
|
|
23217
|
+
children: displayName ?? slug
|
|
22586
23218
|
}), verified ? /* @__PURE__ */ jsx(ShieldCheck, {
|
|
22587
23219
|
className: "h-5 w-5 shrink-0 text-kumo-brand",
|
|
22588
23220
|
"aria-label": _t({
|
|
@@ -22606,40 +23238,88 @@ function RegistryPluginDetail({ pluginId, config }) {
|
|
|
22606
23238
|
})
|
|
22607
23239
|
]
|
|
22608
23240
|
}),
|
|
22609
|
-
release ? /* @__PURE__ */ jsxs("
|
|
22610
|
-
className: "
|
|
22611
|
-
children: [
|
|
22612
|
-
|
|
22613
|
-
|
|
22614
|
-
|
|
22615
|
-
|
|
22616
|
-
|
|
22617
|
-
|
|
22618
|
-
|
|
22619
|
-
|
|
22620
|
-
|
|
22621
|
-
|
|
22622
|
-
|
|
22623
|
-
|
|
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]
|
|
22624
23262
|
}) : null
|
|
22625
23263
|
]
|
|
22626
23264
|
}),
|
|
22627
|
-
/* @__PURE__ */
|
|
22628
|
-
|
|
22629
|
-
|
|
22630
|
-
|
|
22631
|
-
|
|
22632
|
-
|
|
22633
|
-
|
|
22634
|
-
|
|
22635
|
-
|
|
22636
|
-
|
|
22637
|
-
|
|
22638
|
-
|
|
22639
|
-
|
|
22640
|
-
|
|
22641
|
-
|
|
22642
|
-
|
|
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
|
+
})
|
|
22643
23323
|
]
|
|
22644
23324
|
}),
|
|
22645
23325
|
handleResult.status === "invalid" ? /* @__PURE__ */ jsxs("div", {
|
|
@@ -22659,6 +23339,23 @@ function RegistryPluginDetail({ pluginId, config }) {
|
|
|
22659
23339
|
})
|
|
22660
23340
|
})] })]
|
|
22661
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,
|
|
22662
23359
|
release && !policyOk ? /* @__PURE__ */ jsxs("div", {
|
|
22663
23360
|
className: "flex items-start gap-3 rounded-md border border-kumo-warning bg-kumo-warning/10 p-4 text-kumo-warning",
|
|
22664
23361
|
role: "status",
|
|
@@ -22677,10 +23374,82 @@ function RegistryPluginDetail({ pluginId, config }) {
|
|
|
22677
23374
|
})
|
|
22678
23375
|
})] })]
|
|
22679
23376
|
}) : null,
|
|
22680
|
-
|
|
23377
|
+
description ? /* @__PURE__ */ jsx("p", {
|
|
22681
23378
|
className: "text-base text-kumo-default",
|
|
22682
|
-
children:
|
|
23379
|
+
children: description
|
|
23380
|
+
}) : null,
|
|
23381
|
+
licenseText || repoHref || keywordList.length > 0 ? /* @__PURE__ */ jsxs("section", {
|
|
23382
|
+
className: "flex flex-wrap items-center gap-2",
|
|
23383
|
+
children: [
|
|
23384
|
+
licenseText ? /* @__PURE__ */ jsx(LicenseBadge, { license: licenseText }) : null,
|
|
23385
|
+
keywordList.map((k) => /* @__PURE__ */ jsx(Badge, { children: k }, k)),
|
|
23386
|
+
repoHref ? /* @__PURE__ */ jsx(LinkButton, {
|
|
23387
|
+
href: repoHref,
|
|
23388
|
+
external: true,
|
|
23389
|
+
variant: "secondary",
|
|
23390
|
+
children: _t({
|
|
23391
|
+
id: "n/I+da",
|
|
23392
|
+
message: "View source"
|
|
23393
|
+
})
|
|
23394
|
+
}) : null
|
|
23395
|
+
]
|
|
22683
23396
|
}) : null,
|
|
23397
|
+
authorList.length > 0 ? /* @__PURE__ */ jsxs("section", { children: [/* @__PURE__ */ jsx("h2", {
|
|
23398
|
+
className: "text-sm font-semibold text-kumo-subtle",
|
|
23399
|
+
children: _t({
|
|
23400
|
+
id: "dpqPBg",
|
|
23401
|
+
message: "Authors"
|
|
23402
|
+
})
|
|
23403
|
+
}), /* @__PURE__ */ jsx("ul", {
|
|
23404
|
+
className: "mt-2 space-y-1",
|
|
23405
|
+
children: authorList.map((a, i) => /* @__PURE__ */ jsxs("li", {
|
|
23406
|
+
className: "flex flex-wrap items-center gap-x-3 gap-y-1 text-sm",
|
|
23407
|
+
children: [
|
|
23408
|
+
/* @__PURE__ */ jsx("span", {
|
|
23409
|
+
className: "font-medium text-kumo-default",
|
|
23410
|
+
children: a.name
|
|
23411
|
+
}),
|
|
23412
|
+
a.url ? /* @__PURE__ */ jsx("a", {
|
|
23413
|
+
href: a.url,
|
|
23414
|
+
target: "_blank",
|
|
23415
|
+
rel: "noreferrer",
|
|
23416
|
+
className: "text-kumo-brand hover:underline",
|
|
23417
|
+
children: _t({
|
|
23418
|
+
id: "On0aF2",
|
|
23419
|
+
message: "Website"
|
|
23420
|
+
})
|
|
23421
|
+
}) : null,
|
|
23422
|
+
a.email ? /* @__PURE__ */ jsx("a", {
|
|
23423
|
+
href: `mailto:${a.email}`,
|
|
23424
|
+
className: "text-kumo-brand hover:underline",
|
|
23425
|
+
children: a.email
|
|
23426
|
+
}) : null
|
|
23427
|
+
]
|
|
23428
|
+
}, `${a.name}-${i}`))
|
|
23429
|
+
})] }) : null,
|
|
23430
|
+
securityList.length > 0 ? /* @__PURE__ */ jsxs("section", { children: [/* @__PURE__ */ jsx("h2", {
|
|
23431
|
+
className: "text-sm font-semibold text-kumo-subtle",
|
|
23432
|
+
children: _t({
|
|
23433
|
+
id: "FDZXmz",
|
|
23434
|
+
message: "Security contacts"
|
|
23435
|
+
})
|
|
23436
|
+
}), /* @__PURE__ */ jsx("ul", {
|
|
23437
|
+
className: "mt-2 space-y-1",
|
|
23438
|
+
children: securityList.map((c, i) => /* @__PURE__ */ jsxs("li", {
|
|
23439
|
+
className: "flex flex-wrap items-center gap-x-3 gap-y-1 text-sm",
|
|
23440
|
+
children: [c.email ? /* @__PURE__ */ jsx("a", {
|
|
23441
|
+
href: `mailto:${c.email}`,
|
|
23442
|
+
className: "text-kumo-brand hover:underline",
|
|
23443
|
+
children: c.email
|
|
23444
|
+
}) : null, c.url ? /* @__PURE__ */ jsx("a", {
|
|
23445
|
+
href: c.url,
|
|
23446
|
+
target: "_blank",
|
|
23447
|
+
rel: "noreferrer",
|
|
23448
|
+
className: "text-kumo-brand hover:underline",
|
|
23449
|
+
children: c.url
|
|
23450
|
+
}) : null]
|
|
23451
|
+
}, `${c.email ?? c.url ?? "contact"}-${i}`))
|
|
23452
|
+
})] }) : null,
|
|
22684
23453
|
capabilities.length > 0 ? /* @__PURE__ */ jsxs("section", { children: [/* @__PURE__ */ jsx("h2", {
|
|
22685
23454
|
className: "text-sm font-semibold text-kumo-subtle",
|
|
22686
23455
|
children: _t({
|
|
@@ -22693,7 +23462,7 @@ function RegistryPluginDetail({ pluginId, config }) {
|
|
|
22693
23462
|
})] }) : null,
|
|
22694
23463
|
showConsent && release ? /* @__PURE__ */ jsx(CapabilityConsentDialog, {
|
|
22695
23464
|
mode: "install",
|
|
22696
|
-
pluginName:
|
|
23465
|
+
pluginName: displayName ?? slug,
|
|
22697
23466
|
capabilities,
|
|
22698
23467
|
isPending: installMutation.isPending,
|
|
22699
23468
|
error: getMutationError(installMutation.error),
|
|
@@ -22737,6 +23506,33 @@ function declaredAccessToCapabilityList(declaredAccess) {
|
|
|
22737
23506
|
}
|
|
22738
23507
|
return out;
|
|
22739
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
|
+
}
|
|
22740
23536
|
function formatDate(iso) {
|
|
22741
23537
|
try {
|
|
22742
23538
|
return new Date(iso).toLocaleDateString();
|
|
@@ -22744,6 +23540,61 @@ function formatDate(iso) {
|
|
|
22744
23540
|
return iso;
|
|
22745
23541
|
}
|
|
22746
23542
|
}
|
|
23543
|
+
/**
|
|
23544
|
+
* SPDX page URL for a license, or `null` when the value isn't a single
|
|
23545
|
+
* SPDX identifier (compound expressions like "MIT OR Apache-2.0" and the
|
|
23546
|
+
* literal "proprietary" have no canonical spdx.org page).
|
|
23547
|
+
*/
|
|
23548
|
+
/**
|
|
23549
|
+
* Validate an untrusted aggregator-supplied URL for use in an `href`.
|
|
23550
|
+
* Returns the normalised URL only when it is an absolute `http(s)` URL;
|
|
23551
|
+
* everything else (relative, `javascript:`, `data:`, garbage, non-string)
|
|
23552
|
+
* returns `null`. The profile/release records are pass-throughs from a
|
|
23553
|
+
* remote service, so an unsanitised `href` is stored XSS in the
|
|
23554
|
+
* authenticated admin origin.
|
|
23555
|
+
*/
|
|
23556
|
+
function safeExternalHref(value) {
|
|
23557
|
+
if (typeof value !== "string" || value.length === 0) return null;
|
|
23558
|
+
let parsed;
|
|
23559
|
+
try {
|
|
23560
|
+
parsed = new URL(value);
|
|
23561
|
+
} catch {
|
|
23562
|
+
return null;
|
|
23563
|
+
}
|
|
23564
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return null;
|
|
23565
|
+
return parsed.href;
|
|
23566
|
+
}
|
|
23567
|
+
const EMAIL_RE = /^[^\s@<>()[\]\\,;:"?&=%/]+@[^\s@<>()[\]\\,;:"?&=%/]+\.[^\s@<>()[\]\\,;:"?&=%/]+$/;
|
|
23568
|
+
function safeEmail(value) {
|
|
23569
|
+
if (typeof value !== "string") return null;
|
|
23570
|
+
const email = value.trim();
|
|
23571
|
+
if (email.length === 0 || email.length > 320) return null;
|
|
23572
|
+
return EMAIL_RE.test(email) ? email : null;
|
|
23573
|
+
}
|
|
23574
|
+
const SPDX_SINGLE_ID_RE = /^[A-Za-z0-9.+-]+$/;
|
|
23575
|
+
function spdxLicenseHref(license) {
|
|
23576
|
+
const id = license.trim();
|
|
23577
|
+
if (!SPDX_SINGLE_ID_RE.test(id)) return null;
|
|
23578
|
+
if (id.toLowerCase() === "proprietary") return null;
|
|
23579
|
+
return `https://spdx.org/licenses/${id}.html`;
|
|
23580
|
+
}
|
|
23581
|
+
function LicenseBadge({ license }) {
|
|
23582
|
+
const { _: _t3 } = useLingui();
|
|
23583
|
+
const href = spdxLicenseHref(license);
|
|
23584
|
+
if (!href) return /* @__PURE__ */ jsx(Badge, { children: license });
|
|
23585
|
+
return /* @__PURE__ */ jsx("a", {
|
|
23586
|
+
href,
|
|
23587
|
+
target: "_blank",
|
|
23588
|
+
rel: "noreferrer",
|
|
23589
|
+
"aria-label": _t3({
|
|
23590
|
+
id: "4Q1BOX",
|
|
23591
|
+
message: "View the {license} license on spdx.org",
|
|
23592
|
+
values: { license }
|
|
23593
|
+
}),
|
|
23594
|
+
className: "hover:opacity-80",
|
|
23595
|
+
children: /* @__PURE__ */ jsx(Badge, { children: license })
|
|
23596
|
+
});
|
|
23597
|
+
}
|
|
22747
23598
|
function formatHoldback(seconds) {
|
|
22748
23599
|
if (seconds <= 0) return "0s";
|
|
22749
23600
|
if (seconds < 3600) return `${Math.round(seconds / 60)} min`;
|
|
@@ -34067,6 +34918,13 @@ function AuthorMappingStep({ authorMappings, emdashUsers, onMappingChange, onCon
|
|
|
34067
34918
|
|
|
34068
34919
|
//#endregion
|
|
34069
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
|
+
}
|
|
34070
34928
|
function toFormState(byline) {
|
|
34071
34929
|
if (!byline) return {
|
|
34072
34930
|
slug: "",
|
|
@@ -34092,21 +34950,39 @@ function getUserLabel(user) {
|
|
|
34092
34950
|
function BylinesPage() {
|
|
34093
34951
|
const { _: _t } = useLingui();
|
|
34094
34952
|
const queryClient = useQueryClient();
|
|
34953
|
+
const navigate = useNavigate$1();
|
|
34954
|
+
const { locale: routeLocale } = useSearch({ from: "/_admin/bylines" });
|
|
34095
34955
|
const [search, setSearch] = React$1.useState("");
|
|
34096
34956
|
const [guestFilter, setGuestFilter] = React$1.useState("all");
|
|
34097
34957
|
const [selectedId, setSelectedId] = React$1.useState(null);
|
|
34098
34958
|
const [showDeleteConfirm, setShowDeleteConfirm] = React$1.useState(false);
|
|
34099
34959
|
const [allItems, setAllItems] = React$1.useState([]);
|
|
34100
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
|
+
};
|
|
34101
34975
|
const { data, isLoading, error } = useQuery({
|
|
34102
34976
|
queryKey: [
|
|
34103
34977
|
"bylines",
|
|
34104
34978
|
search,
|
|
34105
|
-
guestFilter
|
|
34979
|
+
guestFilter,
|
|
34980
|
+
activeLocale ?? null
|
|
34106
34981
|
],
|
|
34107
34982
|
queryFn: () => fetchBylines({
|
|
34108
34983
|
search: search || void 0,
|
|
34109
34984
|
isGuest: guestFilter === "all" ? void 0 : guestFilter === "guest",
|
|
34985
|
+
locale: activeLocale,
|
|
34110
34986
|
limit: 50
|
|
34111
34987
|
})
|
|
34112
34988
|
});
|
|
@@ -34122,28 +34998,44 @@ function BylinesPage() {
|
|
|
34122
34998
|
});
|
|
34123
34999
|
const users = usersData?.items ?? [];
|
|
34124
35000
|
const loadMoreMutation = useMutation({
|
|
34125
|
-
mutationFn: async () => {
|
|
34126
|
-
|
|
34127
|
-
|
|
34128
|
-
|
|
34129
|
-
|
|
34130
|
-
|
|
34131
|
-
|
|
34132
|
-
|
|
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
|
+
};
|
|
34133
35012
|
},
|
|
34134
|
-
onSuccess: (result) => {
|
|
34135
|
-
if (
|
|
34136
|
-
|
|
34137
|
-
|
|
34138
|
-
|
|
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);
|
|
34139
35021
|
}
|
|
34140
35022
|
});
|
|
34141
35023
|
const items = allItems;
|
|
34142
|
-
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;
|
|
34143
35030
|
const [form, setForm] = React$1.useState(() => toFormState(null));
|
|
34144
35031
|
React$1.useEffect(() => {
|
|
34145
35032
|
setForm(toFormState(selected));
|
|
34146
|
-
}, [
|
|
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
|
+
});
|
|
34147
35039
|
const createMutation = useMutation({
|
|
34148
35040
|
mutationFn: () => createByline({
|
|
34149
35041
|
slug: form.slug,
|
|
@@ -34151,7 +35043,8 @@ function BylinesPage() {
|
|
|
34151
35043
|
bio: form.bio || null,
|
|
34152
35044
|
websiteUrl: form.websiteUrl || null,
|
|
34153
35045
|
userId: form.userId,
|
|
34154
|
-
isGuest: form.isGuest
|
|
35046
|
+
isGuest: form.isGuest,
|
|
35047
|
+
locale: activeLocale
|
|
34155
35048
|
}),
|
|
34156
35049
|
onSuccess: (created) => {
|
|
34157
35050
|
queryClient.invalidateQueries({ queryKey: ["bylines"] });
|
|
@@ -34172,6 +35065,7 @@ function BylinesPage() {
|
|
|
34172
35065
|
},
|
|
34173
35066
|
onSuccess: () => {
|
|
34174
35067
|
queryClient.invalidateQueries({ queryKey: ["bylines"] });
|
|
35068
|
+
if (selectedId) queryClient.invalidateQueries({ queryKey: ["byline", selectedId] });
|
|
34175
35069
|
}
|
|
34176
35070
|
});
|
|
34177
35071
|
const deleteMutation = useMutation({
|
|
@@ -34185,6 +35079,26 @@ function BylinesPage() {
|
|
|
34185
35079
|
setShowDeleteConfirm(false);
|
|
34186
35080
|
}
|
|
34187
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
|
+
});
|
|
34188
35102
|
if (isLoading) return /* @__PURE__ */ jsx("div", {
|
|
34189
35103
|
className: "flex items-center justify-center min-h-[30vh]",
|
|
34190
35104
|
children: /* @__PURE__ */ jsx(Loader, {})
|
|
@@ -34198,230 +35112,273 @@ function BylinesPage() {
|
|
|
34198
35112
|
})
|
|
34199
35113
|
});
|
|
34200
35114
|
const isSaving = createMutation.isPending || updateMutation.isPending;
|
|
34201
|
-
const mutationError = createMutation.error || updateMutation.error || deleteMutation.error;
|
|
35115
|
+
const mutationError = createMutation.error || updateMutation.error || deleteMutation.error || translateMutation.error;
|
|
34202
35116
|
return /* @__PURE__ */ jsxs("div", {
|
|
34203
|
-
className: "
|
|
35117
|
+
className: "space-y-4",
|
|
34204
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,
|
|
34205
35134
|
/* @__PURE__ */ jsxs("div", {
|
|
34206
|
-
className: "
|
|
35135
|
+
className: "grid grid-cols-1 gap-6 lg:grid-cols-[320px_1fr]",
|
|
34207
35136
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
34208
|
-
className: "
|
|
34209
|
-
children: [/* @__PURE__ */
|
|
34210
|
-
|
|
34211
|
-
|
|
34212
|
-
|
|
34213
|
-
|
|
34214
|
-
|
|
34215
|
-
|
|
34216
|
-
|
|
34217
|
-
|
|
34218
|
-
|
|
34219
|
-
className: "flex-
|
|
34220
|
-
children: /* @__PURE__ */ jsx(
|
|
34221
|
-
"
|
|
34222
|
-
|
|
34223
|
-
|
|
34224
|
-
|
|
34225
|
-
|
|
34226
|
-
onValueChange: (v) => setGuestFilter(v ?? "all"),
|
|
34227
|
-
items: {
|
|
34228
|
-
all: _t({
|
|
34229
|
-
id: "VK3dVJ",
|
|
34230
|
-
message: "All bylines"
|
|
34231
|
-
}),
|
|
34232
|
-
guest: _t({
|
|
34233
|
-
id: "+0jFzA",
|
|
34234
|
-
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"
|
|
34235
35155
|
}),
|
|
34236
|
-
|
|
34237
|
-
|
|
34238
|
-
|
|
34239
|
-
|
|
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));
|
|
34240
35179
|
},
|
|
34241
|
-
|
|
34242
|
-
|
|
34243
|
-
|
|
34244
|
-
|
|
34245
|
-
|
|
34246
|
-
setSelectedId(null);
|
|
34247
|
-
setForm(toFormState(null));
|
|
34248
|
-
},
|
|
34249
|
-
children: _t({
|
|
34250
|
-
id: "isRobC",
|
|
34251
|
-
message: "New"
|
|
34252
|
-
})
|
|
35180
|
+
children: _t({
|
|
35181
|
+
id: "isRobC",
|
|
35182
|
+
message: "New"
|
|
35183
|
+
})
|
|
35184
|
+
})]
|
|
34253
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
|
+
]
|
|
34254
35235
|
})]
|
|
34255
35236
|
}), /* @__PURE__ */ jsxs("div", {
|
|
34256
|
-
className: "
|
|
35237
|
+
className: "rounded-lg border p-6 space-y-6",
|
|
34257
35238
|
children: [
|
|
34258
|
-
|
|
34259
|
-
|
|
34260
|
-
|
|
34261
|
-
|
|
34262
|
-
|
|
34263
|
-
|
|
34264
|
-
className: "font-medium",
|
|
34265
|
-
children: item.displayName
|
|
34266
|
-
}), /* @__PURE__ */ jsxs("p", {
|
|
34267
|
-
className: "text-xs text-kumo-subtle",
|
|
34268
|
-
children: [item.slug, item.isGuest ? _t({
|
|
34269
|
-
id: "89z9Fh",
|
|
34270
|
-
message: " - Guest"
|
|
34271
|
-
}) : item.userId ? _t({
|
|
34272
|
-
id: "X4uMa1",
|
|
34273
|
-
message: " - Linked"
|
|
34274
|
-
}) : ""]
|
|
34275
|
-
})]
|
|
34276
|
-
}, item.id);
|
|
34277
|
-
}),
|
|
34278
|
-
items.length === 0 && /* @__PURE__ */ jsx("p", {
|
|
34279
|
-
className: "text-sm text-kumo-subtle",
|
|
34280
|
-
children: _t({
|
|
34281
|
-
id: "Qc/a/A",
|
|
34282
|
-
message: "No bylines found"
|
|
34283
|
-
})
|
|
34284
|
-
}),
|
|
34285
|
-
nextCursor && /* @__PURE__ */ jsx(Button, {
|
|
34286
|
-
variant: "secondary",
|
|
34287
|
-
className: "w-full mt-2",
|
|
34288
|
-
onClick: () => loadMoreMutation.mutate(),
|
|
34289
|
-
disabled: loadMoreMutation.isPending,
|
|
34290
|
-
children: loadMoreMutation.isPending ? _t({
|
|
34291
|
-
id: "Z3FXyt",
|
|
34292
|
-
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 }
|
|
34293
35245
|
}) : _t({
|
|
34294
|
-
id: "
|
|
34295
|
-
message: "
|
|
35246
|
+
id: "d6HWlw",
|
|
35247
|
+
message: "Create byline"
|
|
34296
35248
|
})
|
|
34297
|
-
})
|
|
34298
|
-
]
|
|
34299
|
-
})]
|
|
34300
|
-
}),
|
|
34301
|
-
/* @__PURE__ */ jsxs("div", {
|
|
34302
|
-
className: "rounded-lg border p-6",
|
|
34303
|
-
children: [/* @__PURE__ */ jsx("h2", {
|
|
34304
|
-
className: "text-lg font-semibold mb-4",
|
|
34305
|
-
children: selected ? _t({
|
|
34306
|
-
id: "N6j2JH",
|
|
34307
|
-
message: "Edit {0}",
|
|
34308
|
-
values: { 0: selected.displayName }
|
|
34309
|
-
}) : _t({
|
|
34310
|
-
id: "d6HWlw",
|
|
34311
|
-
message: "Create byline"
|
|
34312
|
-
})
|
|
34313
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
34314
|
-
className: "space-y-4",
|
|
34315
|
-
children: [
|
|
34316
|
-
/* @__PURE__ */ jsx(Input, {
|
|
34317
|
-
label: _t({
|
|
34318
|
-
id: "pfa8F0",
|
|
34319
|
-
message: "Display name"
|
|
34320
|
-
}),
|
|
34321
|
-
value: form.displayName,
|
|
34322
|
-
onChange: (e) => setForm((prev) => ({
|
|
34323
|
-
...prev,
|
|
34324
|
-
displayName: e.target.value
|
|
34325
|
-
}))
|
|
34326
|
-
}),
|
|
34327
|
-
/* @__PURE__ */ jsx(Input, {
|
|
34328
|
-
label: _t({
|
|
34329
|
-
id: "L85WcV",
|
|
34330
|
-
message: "Slug"
|
|
34331
|
-
}),
|
|
34332
|
-
value: form.slug,
|
|
34333
|
-
onChange: (e) => setForm((prev) => ({
|
|
34334
|
-
...prev,
|
|
34335
|
-
slug: e.target.value
|
|
34336
|
-
}))
|
|
34337
|
-
}),
|
|
34338
|
-
/* @__PURE__ */ jsx(Input, {
|
|
34339
|
-
label: _t({
|
|
34340
|
-
id: "XYcw7G",
|
|
34341
|
-
message: "Website URL"
|
|
34342
|
-
}),
|
|
34343
|
-
value: form.websiteUrl,
|
|
34344
|
-
onChange: (e) => setForm((prev) => ({
|
|
34345
|
-
...prev,
|
|
34346
|
-
websiteUrl: e.target.value
|
|
34347
|
-
}))
|
|
34348
|
-
}),
|
|
34349
|
-
/* @__PURE__ */ jsx(InputArea, {
|
|
34350
|
-
label: _t({
|
|
34351
|
-
id: "E+oKCO",
|
|
34352
|
-
message: "Bio"
|
|
34353
|
-
}),
|
|
34354
|
-
value: form.bio,
|
|
34355
|
-
onChange: (e) => setForm((prev) => ({
|
|
34356
|
-
...prev,
|
|
34357
|
-
bio: e.target.value
|
|
34358
|
-
})),
|
|
34359
|
-
rows: 5
|
|
34360
35249
|
}),
|
|
34361
|
-
/* @__PURE__ */
|
|
34362
|
-
|
|
34363
|
-
|
|
34364
|
-
|
|
34365
|
-
|
|
34366
|
-
|
|
34367
|
-
|
|
34368
|
-
|
|
34369
|
-
|
|
34370
|
-
|
|
34371
|
-
|
|
34372
|
-
|
|
34373
|
-
|
|
34374
|
-
},
|
|
34375
|
-
items: {
|
|
34376
|
-
"": _t({
|
|
34377
|
-
id: "n9o2+L",
|
|
34378
|
-
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
|
+
}))
|
|
34379
35263
|
}),
|
|
34380
|
-
|
|
34381
|
-
|
|
34382
|
-
|
|
34383
|
-
|
|
34384
|
-
|
|
34385
|
-
|
|
34386
|
-
|
|
34387
|
-
|
|
34388
|
-
|
|
34389
|
-
|
|
34390
|
-
|
|
34391
|
-
|
|
34392
|
-
|
|
34393
|
-
|
|
34394
|
-
|
|
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
|
+
]
|
|
34395
35363
|
}),
|
|
34396
|
-
/* @__PURE__ */ jsx(
|
|
34397
|
-
|
|
34398
|
-
|
|
34399
|
-
|
|
34400
|
-
|
|
34401
|
-
|
|
34402
|
-
|
|
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);
|
|
34403
35377
|
},
|
|
34404
|
-
|
|
34405
|
-
|
|
34406
|
-
|
|
34407
|
-
|
|
34408
|
-
}) : selected ? _t({
|
|
34409
|
-
id: "tfDRzk",
|
|
34410
|
-
message: "Save"
|
|
34411
|
-
}) : _t({
|
|
34412
|
-
id: "hYgDIe",
|
|
34413
|
-
message: "Create"
|
|
34414
|
-
})
|
|
34415
|
-
}), selected && /* @__PURE__ */ jsx(Button, {
|
|
34416
|
-
variant: "destructive",
|
|
34417
|
-
onClick: () => setShowDeleteConfirm(true),
|
|
34418
|
-
disabled: deleteMutation.isPending,
|
|
34419
|
-
children: _t({
|
|
34420
|
-
id: "cnGeoo",
|
|
34421
|
-
message: "Delete"
|
|
34422
|
-
})
|
|
34423
|
-
})]
|
|
34424
|
-
})
|
|
35378
|
+
onCreate: (locale) => translateMutation.mutate(locale),
|
|
35379
|
+
pendingLocale: pendingTranslationLocale
|
|
35380
|
+
})
|
|
35381
|
+
}) : null
|
|
34425
35382
|
]
|
|
34426
35383
|
})]
|
|
34427
35384
|
}),
|
|
@@ -35989,10 +36946,11 @@ function ContentNewPage() {
|
|
|
35989
36946
|
queryKey: ["manifest"],
|
|
35990
36947
|
queryFn: fetchManifest
|
|
35991
36948
|
});
|
|
36949
|
+
const pickerLocale = locale ?? manifest?.i18n?.defaultLocale;
|
|
35992
36950
|
const createMutation = useMutation({
|
|
35993
36951
|
mutationFn: (data) => createContent(collection, {
|
|
35994
36952
|
...data,
|
|
35995
|
-
locale
|
|
36953
|
+
locale: pickerLocale
|
|
35996
36954
|
}),
|
|
35997
36955
|
onSuccess: (result) => {
|
|
35998
36956
|
queryClient.invalidateQueries({ queryKey: ["content", collection] });
|
|
@@ -36006,14 +36964,23 @@ function ContentNewPage() {
|
|
|
36006
36964
|
}
|
|
36007
36965
|
});
|
|
36008
36966
|
const pluginBlocks = React$1.useMemo(() => manifest ? getPluginBlocks(manifest) : [], [manifest]);
|
|
36009
|
-
const { data: bylinesData } = useQuery({
|
|
36010
|
-
queryKey: [
|
|
36011
|
-
|
|
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
|
|
36012
36978
|
});
|
|
36013
36979
|
const createBylineMutation = useMutation({
|
|
36014
36980
|
mutationFn: (input) => createByline({
|
|
36015
36981
|
...input,
|
|
36016
|
-
isGuest: true
|
|
36982
|
+
isGuest: true,
|
|
36983
|
+
locale: pickerLocale
|
|
36017
36984
|
}),
|
|
36018
36985
|
onSuccess: () => {
|
|
36019
36986
|
queryClient.invalidateQueries({ queryKey: ["bylines"] });
|
|
@@ -36039,10 +37006,13 @@ function ContentNewPage() {
|
|
|
36039
37006
|
collectionLabel: collectionConfig.labelSingular || collectionConfig.label,
|
|
36040
37007
|
fields: collectionConfig.fields,
|
|
36041
37008
|
isNew: true,
|
|
37009
|
+
entryLocale: pickerLocale,
|
|
37010
|
+
i18n: manifest?.i18n,
|
|
36042
37011
|
isSaving: createMutation.isPending,
|
|
36043
37012
|
onSave: handleSave,
|
|
36044
37013
|
pluginBlocks,
|
|
36045
37014
|
availableBylines: bylinesData?.items,
|
|
37015
|
+
availableBylinesLoaded: bylinesLoaded,
|
|
36046
37016
|
selectedBylines,
|
|
36047
37017
|
onBylinesChange: setSelectedBylines,
|
|
36048
37018
|
onQuickCreateByline: async (input) => {
|
|
@@ -36152,14 +37122,24 @@ function ContentEditPage() {
|
|
|
36152
37122
|
enabled: !!currentUser && currentUser.role >= ROLE_EDITOR,
|
|
36153
37123
|
staleTime: 300 * 1e3
|
|
36154
37124
|
});
|
|
36155
|
-
const
|
|
36156
|
-
|
|
36157
|
-
|
|
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
|
|
36158
37137
|
});
|
|
36159
37138
|
const createBylineMutation = useMutation({
|
|
36160
37139
|
mutationFn: (input) => createByline({
|
|
36161
37140
|
...input,
|
|
36162
|
-
isGuest: true
|
|
37141
|
+
isGuest: true,
|
|
37142
|
+
locale: itemLocale
|
|
36163
37143
|
}),
|
|
36164
37144
|
onSuccess: () => {
|
|
36165
37145
|
queryClient.invalidateQueries({ queryKey: ["bylines"] });
|
|
@@ -36542,6 +37522,7 @@ function ContentEditPage() {
|
|
|
36542
37522
|
hasSeo: collectionConfig.hasSeo,
|
|
36543
37523
|
onSeoChange: handleSeoChange,
|
|
36544
37524
|
availableBylines: bylinesData?.items,
|
|
37525
|
+
availableBylinesLoaded: bylinesLoaded,
|
|
36545
37526
|
onQuickCreateByline: async (input) => {
|
|
36546
37527
|
return await createBylineMutation.mutateAsync(input);
|
|
36547
37528
|
},
|
|
@@ -36834,7 +37815,7 @@ function MarketplaceBrowsePage() {
|
|
|
36834
37815
|
const { data: plugins } = useQuery({
|
|
36835
37816
|
queryKey: ["plugins"],
|
|
36836
37817
|
queryFn: async () => {
|
|
36837
|
-
const { fetchPlugins } = await import("./plugins-
|
|
37818
|
+
const { fetchPlugins } = await import("./plugins-BZzztFdK.js").then((n) => n.a);
|
|
36838
37819
|
return fetchPlugins();
|
|
36839
37820
|
}
|
|
36840
37821
|
});
|
|
@@ -36865,7 +37846,7 @@ function MarketplaceDetailPage() {
|
|
|
36865
37846
|
const { data: plugins } = useQuery({
|
|
36866
37847
|
queryKey: ["plugins"],
|
|
36867
37848
|
queryFn: async () => {
|
|
36868
|
-
const { fetchPlugins } = await import("./plugins-
|
|
37849
|
+
const { fetchPlugins } = await import("./plugins-BZzztFdK.js").then((n) => n.a);
|
|
36869
37850
|
return fetchPlugins();
|
|
36870
37851
|
}
|
|
36871
37852
|
});
|
|
@@ -36949,10 +37930,15 @@ const usersRoute = createRoute({
|
|
|
36949
37930
|
path: "/users",
|
|
36950
37931
|
component: UsersPage
|
|
36951
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
|
+
}
|
|
36952
37937
|
const bylinesRoute = createRoute({
|
|
36953
37938
|
getParentRoute: () => adminLayoutRoute,
|
|
36954
37939
|
path: "/bylines",
|
|
36955
|
-
component: BylinesPage
|
|
37940
|
+
component: BylinesPage,
|
|
37941
|
+
validateSearch: parseBylinesLocaleSearch
|
|
36956
37942
|
});
|
|
36957
37943
|
const contentTypesListRoute = createRoute({
|
|
36958
37944
|
getParentRoute: () => adminLayoutRoute,
|
|
@@ -37337,5 +38323,5 @@ function AdminApp({ pluginAdmins = EMPTY_PLUGINS, authProviders = EMPTY_AUTH_PRO
|
|
|
37337
38323
|
}
|
|
37338
38324
|
|
|
37339
38325
|
//#endregion
|
|
37340
|
-
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 };
|
|
37341
38327
|
//# sourceMappingURL=index.js.map
|