@emdash-cms/admin 0.15.0 → 0.16.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-C-w4lOU2.js → LocaleDirectionProvider-BNEVyvgW.js} +17 -17
- package/dist/{LocaleDirectionProvider-C-w4lOU2.js.map → LocaleDirectionProvider-BNEVyvgW.js.map} +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +562 -193
- 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-6iBQvP4l.js +6 -0
- package/dist/messages-6iBQvP4l.js.map +1 -0
- package/dist/messages-B3qgKgae.js +6 -0
- package/dist/messages-B3qgKgae.js.map +1 -0
- package/dist/messages-B8kb5Oko.js +6 -0
- package/dist/messages-B8kb5Oko.js.map +1 -0
- package/dist/messages-BhSzAEGL.js +6 -0
- package/dist/messages-BhSzAEGL.js.map +1 -0
- package/dist/messages-C0wfBdmD.js +6 -0
- package/dist/messages-C0wfBdmD.js.map +1 -0
- package/dist/messages-CboPeBM0.js +6 -0
- package/dist/messages-CboPeBM0.js.map +1 -0
- package/dist/messages-CehVxd9q.js +6 -0
- package/dist/messages-CehVxd9q.js.map +1 -0
- package/dist/messages-D2R4PbiQ.js +6 -0
- package/dist/messages-D2R4PbiQ.js.map +1 -0
- package/dist/messages-DHha7f8K.js +6 -0
- package/dist/messages-DHha7f8K.js.map +1 -0
- package/dist/messages-DIehX9BP.js +6 -0
- package/dist/messages-DIehX9BP.js.map +1 -0
- package/dist/messages-DKdkwHEw.js +6 -0
- package/dist/messages-DKdkwHEw.js.map +1 -0
- package/dist/messages-DMJobUj_.js +6 -0
- package/dist/messages-DMJobUj_.js.map +1 -0
- package/dist/messages-Ddgurz-D.js +6 -0
- package/dist/messages-Ddgurz-D.js.map +1 -0
- package/dist/messages-LeijTgr3.js +6 -0
- package/dist/messages-LeijTgr3.js.map +1 -0
- package/dist/messages-S5UPBqIz.js +6 -0
- package/dist/messages-S5UPBqIz.js.map +1 -0
- package/dist/messages-vQ-B-9ct.js +6 -0
- package/dist/messages-vQ-B-9ct.js.map +1 -0
- package/dist/plugins-BZzztFdK.js.map +1 -1
- package/dist/styles.css +2 -2
- package/package.json +9 -9
- package/dist/messages-B1vNWgt7.js +0 -6
- package/dist/messages-B1vNWgt7.js.map +0 -1
- package/dist/messages-B4SjKBpv.js +0 -6
- package/dist/messages-B4SjKBpv.js.map +0 -1
- package/dist/messages-BMFLZDHo.js +0 -6
- package/dist/messages-BMFLZDHo.js.map +0 -1
- package/dist/messages-BWDz4uQu.js +0 -6
- package/dist/messages-BWDz4uQu.js.map +0 -1
- package/dist/messages-BXt6OWYC.js +0 -6
- package/dist/messages-BXt6OWYC.js.map +0 -1
- package/dist/messages-CA-O-TjM.js +0 -6
- package/dist/messages-CA-O-TjM.js.map +0 -1
- package/dist/messages-CX3dih9c.js +0 -6
- package/dist/messages-CX3dih9c.js.map +0 -1
- package/dist/messages-CXZHQHKt.js +0 -6
- package/dist/messages-CXZHQHKt.js.map +0 -1
- package/dist/messages-Cz2f6Su-.js +0 -6
- package/dist/messages-Cz2f6Su-.js.map +0 -1
- package/dist/messages-DW1BWiJ5.js +0 -6
- package/dist/messages-DW1BWiJ5.js.map +0 -1
- package/dist/messages-DpMKfmmV.js +0 -6
- package/dist/messages-DpMKfmmV.js.map +0 -1
- package/dist/messages-hYPn9ilH.js +0 -6
- package/dist/messages-hYPn9ilH.js.map +0 -1
- package/dist/messages-qYhBOhtE.js +0 -6
- package/dist/messages-qYhBOhtE.js.map +0 -1
- package/dist/messages-rxCTcw_6.js +0 -6
- package/dist/messages-rxCTcw_6.js.map +0 -1
- package/dist/messages-wpqHOBfn.js +0 -6
- package/dist/messages-wpqHOBfn.js.map +0 -1
- package/dist/messages-zuRDwn8n.js +0 -6
- package/dist/messages-zuRDwn8n.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { c as fetchAuthMode, d as throwResponseError, i as fetchPlugins, l as fetchManifest, n as enablePlugin, o as API_BASE, r as fetchPlugin, s as apiFetch, t as disablePlugin, u as parseApiResponse } from "./plugins-BZzztFdK.js";
|
|
2
|
-
import { a as SUPPORTED_LOCALES, i as DEFAULT_LOCALE, l as resolveLocale, n as useLocale, o as SUPPORTED_LOCALE_CODES, t as LocaleDirectionProvider } from "./LocaleDirectionProvider-
|
|
2
|
+
import { a as SUPPORTED_LOCALES, i as DEFAULT_LOCALE, l as resolveLocale, n as useLocale, o as SUPPORTED_LOCALE_CODES, t as LocaleDirectionProvider } from "./LocaleDirectionProvider-BNEVyvgW.js";
|
|
3
3
|
import "./locales/index.js";
|
|
4
4
|
import { Autocomplete, Badge, Button, Checkbox, CommandPalette, Dialog, Input, InputArea, Label, LinkButton, Loader, Popover, Select, Sidebar as KumoSidebar, Switch, Tabs, Toast, Toasty, Tooltip, buttonVariants, useSidebar } from "@cloudflare/kumo";
|
|
5
5
|
import { i18n } from "@lingui/core";
|
|
@@ -39,6 +39,7 @@ import { isAllowedUri } from "@tiptap/extension-link";
|
|
|
39
39
|
import { BlockRenderer } from "@emdash-cms/blocks";
|
|
40
40
|
import DOMPurify from "dompurify";
|
|
41
41
|
import { Marked, Renderer } from "marked";
|
|
42
|
+
import { checkEnvCompatibility, hostEnvFromVersions } from "@emdash-cms/registry-client/env";
|
|
42
43
|
import { useHotkeys } from "react-hotkeys-hook";
|
|
43
44
|
|
|
44
45
|
//#region src/components/ThemeProvider.tsx
|
|
@@ -7957,19 +7958,17 @@ function PluginBlockModal({ block, initialValues, onClose, onInsert }) {
|
|
|
7957
7958
|
size: dialogSize,
|
|
7958
7959
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
7959
7960
|
className: "flex items-start justify-between gap-4 mb-4",
|
|
7960
|
-
children: [/* @__PURE__ */
|
|
7961
|
+
children: [/* @__PURE__ */ jsx(Dialog.Title, {
|
|
7961
7962
|
className: "text-lg font-semibold leading-none tracking-tight",
|
|
7962
|
-
children:
|
|
7963
|
-
|
|
7964
|
-
|
|
7965
|
-
|
|
7966
|
-
|
|
7967
|
-
|
|
7968
|
-
|
|
7969
|
-
}
|
|
7970
|
-
|
|
7971
|
-
block?.label || ""
|
|
7972
|
-
]
|
|
7963
|
+
children: isEditing ? _t2({
|
|
7964
|
+
id: "N6j2JH",
|
|
7965
|
+
message: "Edit {0}",
|
|
7966
|
+
values: { 0: block?.label || "" }
|
|
7967
|
+
}) : _t2({
|
|
7968
|
+
id: "yQNfsE",
|
|
7969
|
+
message: "Insert {0}",
|
|
7970
|
+
values: { 0: block?.label || "" }
|
|
7971
|
+
})
|
|
7973
7972
|
}), /* @__PURE__ */ jsx(Dialog.Close, {
|
|
7974
7973
|
"aria-label": _t2({
|
|
7975
7974
|
id: "yz7wBu",
|
|
@@ -10319,6 +10318,7 @@ function termExactMatches(term, input) {
|
|
|
10319
10318
|
* - Checkbox tree for hierarchical taxonomies (categories)
|
|
10320
10319
|
* - Tag input for flat taxonomies (tags)
|
|
10321
10320
|
*/
|
|
10321
|
+
const EMPTY_TERMS = [];
|
|
10322
10322
|
/**
|
|
10323
10323
|
* Fetch taxonomy definitions
|
|
10324
10324
|
*/
|
|
@@ -10386,11 +10386,13 @@ function CategoryCheckboxTree({ term, level = 0, selectedIds, onToggle }) {
|
|
|
10386
10386
|
function TagInput({ terms, selectedIds, onAdd, onRemove, onCreate, isCreating, label }) {
|
|
10387
10387
|
const { _: _t } = useLingui();
|
|
10388
10388
|
const [input, setInput] = React$1.useState("");
|
|
10389
|
+
const [isOpen, setIsOpen] = React$1.useState(false);
|
|
10389
10390
|
const selectedTerms = terms.filter((term) => selectedIds.has(term.id));
|
|
10390
10391
|
const trimmedInput = input.trim();
|
|
10391
10392
|
const suggestions = React$1.useMemo(() => {
|
|
10392
|
-
|
|
10393
|
-
|
|
10393
|
+
const availableTerms = terms.filter((term) => !selectedIds.has(term.id));
|
|
10394
|
+
if (!trimmedInput) return availableTerms.slice(0, 5);
|
|
10395
|
+
return availableTerms.filter((term) => termMatches(term, trimmedInput)).slice(0, 5);
|
|
10394
10396
|
}, [
|
|
10395
10397
|
trimmedInput,
|
|
10396
10398
|
terms,
|
|
@@ -10404,11 +10406,18 @@ function TagInput({ terms, selectedIds, onAdd, onRemove, onCreate, isCreating, l
|
|
|
10404
10406
|
const handleSelect = (term) => {
|
|
10405
10407
|
onAdd(term.id);
|
|
10406
10408
|
setInput("");
|
|
10409
|
+
setIsOpen(false);
|
|
10407
10410
|
};
|
|
10408
10411
|
const handleCreate = () => {
|
|
10409
10412
|
if (!trimmedInput || isCreating) return;
|
|
10410
10413
|
onCreate(trimmedInput);
|
|
10411
10414
|
setInput("");
|
|
10415
|
+
setIsOpen(false);
|
|
10416
|
+
};
|
|
10417
|
+
const handleBlur = (e) => {
|
|
10418
|
+
const nextFocused = e.relatedTarget;
|
|
10419
|
+
if (nextFocused instanceof Node && e.currentTarget.contains(nextFocused)) return;
|
|
10420
|
+
setIsOpen(false);
|
|
10412
10421
|
};
|
|
10413
10422
|
const handleKeyDown = (e) => {
|
|
10414
10423
|
if (e.key === "Enter") {
|
|
@@ -10437,9 +10446,14 @@ function TagInput({ terms, selectedIds, onAdd, onRemove, onCreate, isCreating, l
|
|
|
10437
10446
|
}, term.id))
|
|
10438
10447
|
}), /* @__PURE__ */ jsxs("div", {
|
|
10439
10448
|
className: "relative",
|
|
10449
|
+
onBlur: handleBlur,
|
|
10440
10450
|
children: [/* @__PURE__ */ jsx(Input, {
|
|
10441
10451
|
value: input,
|
|
10442
|
-
onChange: (e) =>
|
|
10452
|
+
onChange: (e) => {
|
|
10453
|
+
setInput(e.target.value);
|
|
10454
|
+
setIsOpen(true);
|
|
10455
|
+
},
|
|
10456
|
+
onFocus: () => setIsOpen(true),
|
|
10443
10457
|
onKeyDown: handleKeyDown,
|
|
10444
10458
|
placeholder: _t({
|
|
10445
10459
|
id: "SnJVrE",
|
|
@@ -10451,15 +10465,17 @@ function TagInput({ terms, selectedIds, onAdd, onRemove, onCreate, isCreating, l
|
|
|
10451
10465
|
values: { label }
|
|
10452
10466
|
}),
|
|
10453
10467
|
className: "text-sm"
|
|
10454
|
-
}), (suggestions.length > 0 || showCreateOption) && /* @__PURE__ */ jsxs("div", {
|
|
10468
|
+
}), isOpen && (suggestions.length > 0 || showCreateOption) && /* @__PURE__ */ jsxs("div", {
|
|
10455
10469
|
className: "absolute top-full start-0 end-0 mt-1 bg-kumo-overlay border rounded-md shadow-lg z-10",
|
|
10456
10470
|
children: [suggestions.map((term) => /* @__PURE__ */ jsx("button", {
|
|
10457
10471
|
type: "button",
|
|
10472
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
10458
10473
|
onClick: () => handleSelect(term),
|
|
10459
10474
|
className: "w-full text-start px-3 py-2 text-sm hover:bg-kumo-tint",
|
|
10460
10475
|
children: term.label
|
|
10461
10476
|
}, term.id)), showCreateOption && /* @__PURE__ */ jsxs("button", {
|
|
10462
10477
|
type: "button",
|
|
10478
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
10463
10479
|
onClick: handleCreate,
|
|
10464
10480
|
disabled: isCreating,
|
|
10465
10481
|
className: "w-full text-start px-3 py-2 text-sm hover:bg-kumo-tint text-kumo-accent flex items-center gap-1 border-t",
|
|
@@ -10485,11 +10501,11 @@ function TaxonomySection({ taxonomy, collection, entryId, onChange }) {
|
|
|
10485
10501
|
const toastManager = Toast.useToastManager();
|
|
10486
10502
|
const [newCategoryLabel, setNewCategoryLabel] = React$1.useState("");
|
|
10487
10503
|
const [showCategoryInput, setShowCategoryInput] = React$1.useState(false);
|
|
10488
|
-
const { data: terms =
|
|
10504
|
+
const { data: terms = EMPTY_TERMS } = useQuery({
|
|
10489
10505
|
queryKey: ["taxonomy-terms", taxonomy.name],
|
|
10490
10506
|
queryFn: () => fetchTerms$1(taxonomy.name)
|
|
10491
10507
|
});
|
|
10492
|
-
const { data: entryTerms =
|
|
10508
|
+
const { data: entryTerms = EMPTY_TERMS } = useQuery({
|
|
10493
10509
|
queryKey: [
|
|
10494
10510
|
"entry-terms",
|
|
10495
10511
|
collection,
|
|
@@ -18032,6 +18048,85 @@ function formatInstallCount(count) {
|
|
|
18032
18048
|
return String(count);
|
|
18033
18049
|
}
|
|
18034
18050
|
|
|
18051
|
+
//#endregion
|
|
18052
|
+
//#region src/lib/markdown.ts
|
|
18053
|
+
const HTML_ESCAPE_MAP = {
|
|
18054
|
+
"&": "&",
|
|
18055
|
+
"<": "<",
|
|
18056
|
+
">": ">",
|
|
18057
|
+
"\"": """,
|
|
18058
|
+
"'": "'"
|
|
18059
|
+
};
|
|
18060
|
+
const HTML_ESCAPE_RE = /[&<>"']/g;
|
|
18061
|
+
function escapeHtml(str) {
|
|
18062
|
+
return str.replace(HTML_ESCAPE_RE, (ch) => HTML_ESCAPE_MAP[ch]);
|
|
18063
|
+
}
|
|
18064
|
+
const renderer = new Renderer();
|
|
18065
|
+
renderer.link = function({ href, tokens }) {
|
|
18066
|
+
const inner = this.parser.parseInline(tokens);
|
|
18067
|
+
if (!SAFE_URL_RE.test(href)) return inner;
|
|
18068
|
+
return `<a href="${escapeHtml(href)}" target="_blank" rel="noopener noreferrer">${inner}</a>`;
|
|
18069
|
+
};
|
|
18070
|
+
renderer.image = ({ text }) => escapeHtml(text);
|
|
18071
|
+
renderer.html = () => "";
|
|
18072
|
+
const md = new Marked({
|
|
18073
|
+
renderer,
|
|
18074
|
+
async: false
|
|
18075
|
+
});
|
|
18076
|
+
/** Allowed tags and attributes for DOMPurify — only standard markdown output. */
|
|
18077
|
+
const SANITIZE_CONFIG = {
|
|
18078
|
+
ALLOWED_TAGS: [
|
|
18079
|
+
"h1",
|
|
18080
|
+
"h2",
|
|
18081
|
+
"h3",
|
|
18082
|
+
"h4",
|
|
18083
|
+
"h5",
|
|
18084
|
+
"h6",
|
|
18085
|
+
"p",
|
|
18086
|
+
"a",
|
|
18087
|
+
"ul",
|
|
18088
|
+
"ol",
|
|
18089
|
+
"li",
|
|
18090
|
+
"blockquote",
|
|
18091
|
+
"pre",
|
|
18092
|
+
"code",
|
|
18093
|
+
"em",
|
|
18094
|
+
"strong",
|
|
18095
|
+
"del",
|
|
18096
|
+
"br",
|
|
18097
|
+
"hr",
|
|
18098
|
+
"table",
|
|
18099
|
+
"thead",
|
|
18100
|
+
"tbody",
|
|
18101
|
+
"tr",
|
|
18102
|
+
"th",
|
|
18103
|
+
"td",
|
|
18104
|
+
"details",
|
|
18105
|
+
"summary",
|
|
18106
|
+
"sup",
|
|
18107
|
+
"sub"
|
|
18108
|
+
],
|
|
18109
|
+
ALLOWED_ATTR: [
|
|
18110
|
+
"href",
|
|
18111
|
+
"target",
|
|
18112
|
+
"rel"
|
|
18113
|
+
]
|
|
18114
|
+
};
|
|
18115
|
+
/**
|
|
18116
|
+
* Render untrusted Markdown to a sanitized HTML string safe for
|
|
18117
|
+
* `dangerouslySetInnerHTML`.
|
|
18118
|
+
*
|
|
18119
|
+
* Defense in depth: the `marked` renderer drops raw HTML, drops images, and
|
|
18120
|
+
* emits only `https?:` links (escaped, with `rel="noopener noreferrer"`);
|
|
18121
|
+
* DOMPurify then re-sanitizes the output against a tag/attribute allowlist
|
|
18122
|
+
* (no `script`/`style`/`img`/`iframe`, no inline styles or event handlers).
|
|
18123
|
+
*/
|
|
18124
|
+
function renderMarkdown(markdown) {
|
|
18125
|
+
const result = md.parse(markdown);
|
|
18126
|
+
const html = typeof result === "string" ? result : "";
|
|
18127
|
+
return DOMPurify.sanitize(html, SANITIZE_CONFIG);
|
|
18128
|
+
}
|
|
18129
|
+
|
|
18035
18130
|
//#endregion
|
|
18036
18131
|
//#region src/components/CapabilityConsentDialog.tsx
|
|
18037
18132
|
/**
|
|
@@ -18284,6 +18379,77 @@ function releaseExemptFromMinimumAge(exclude, publisherDid, slug) {
|
|
|
18284
18379
|
}
|
|
18285
18380
|
return false;
|
|
18286
18381
|
}
|
|
18382
|
+
/**
|
|
18383
|
+
* The FAIR-recognised long-form section keys, in display order. Publishers may
|
|
18384
|
+
* also ship unrecognised keys (the lexicon's `sections` map is open), but the
|
|
18385
|
+
* admin renders only this known set so an aggregator can't inject a section
|
|
18386
|
+
* with an attacker-chosen heading; everything else is ignored.
|
|
18387
|
+
*/
|
|
18388
|
+
const SECTION_ORDER = [
|
|
18389
|
+
"description",
|
|
18390
|
+
"installation",
|
|
18391
|
+
"faq",
|
|
18392
|
+
"changelog",
|
|
18393
|
+
"security"
|
|
18394
|
+
];
|
|
18395
|
+
/**
|
|
18396
|
+
* Select the non-empty long-form sections off a package profile, in
|
|
18397
|
+
* `SECTION_ORDER`. `profile.sections` is a lexicon-validated map of Markdown
|
|
18398
|
+
* strings (or `null` when the aggregator returned a non-conforming record), so
|
|
18399
|
+
* each value is narrowed to a non-whitespace string before inclusion. Empty,
|
|
18400
|
+
* missing, whitespace-only, and non-string entries are dropped, so callers can
|
|
18401
|
+
* suppress the whole sections UI when the result is empty.
|
|
18402
|
+
*/
|
|
18403
|
+
function presentSections(profile) {
|
|
18404
|
+
const sections = profile?.sections;
|
|
18405
|
+
if (!sections || typeof sections !== "object") return [];
|
|
18406
|
+
const map = sections;
|
|
18407
|
+
const out = [];
|
|
18408
|
+
for (const key of SECTION_ORDER) {
|
|
18409
|
+
const value = map[key];
|
|
18410
|
+
if (typeof value === "string" && value.trim().length > 0) out.push({
|
|
18411
|
+
key,
|
|
18412
|
+
markdown: value
|
|
18413
|
+
});
|
|
18414
|
+
}
|
|
18415
|
+
return out;
|
|
18416
|
+
}
|
|
18417
|
+
/**
|
|
18418
|
+
* Narrow a release record's `sbom` field to the fields the admin renders.
|
|
18419
|
+
* Returns `null` unless the value is an object carrying at least one usable
|
|
18420
|
+
* field (`format` or `url`); every field is independently optional per the
|
|
18421
|
+
* lexicon. `sbom` is lexicon-validated at the DiscoveryClient boundary, but the
|
|
18422
|
+
* record is a publisher pass-through, so its inner shape still needs narrowing.
|
|
18423
|
+
*/
|
|
18424
|
+
function extractSbom(value) {
|
|
18425
|
+
if (!value || typeof value !== "object") return null;
|
|
18426
|
+
const v = value;
|
|
18427
|
+
const sbom = {};
|
|
18428
|
+
if (typeof v.format === "string" && v.format.length > 0) sbom.format = v.format;
|
|
18429
|
+
if (typeof v.url === "string" && v.url.length > 0) sbom.url = v.url;
|
|
18430
|
+
if (typeof v.checksum === "string") sbom.checksum = v.checksum;
|
|
18431
|
+
if (!sbom.format && !sbom.url) return null;
|
|
18432
|
+
return sbom;
|
|
18433
|
+
}
|
|
18434
|
+
/**
|
|
18435
|
+
* Validate an SBOM document URL for use in a download `href`. Returns the
|
|
18436
|
+
* normalised URL only when it is an absolute `http(s)` URL; everything else
|
|
18437
|
+
* (relative, `javascript:`, `data:`, non-string) returns `null`. The release
|
|
18438
|
+
* record is a remote pass-through, so an unsanitised SBOM `href` would be
|
|
18439
|
+
* stored XSS in the authenticated admin origin. The browser fetches the SBOM
|
|
18440
|
+
* client-side on click — no server proxy, so SSRF isn't a concern here.
|
|
18441
|
+
*/
|
|
18442
|
+
function sbomDownloadHref(value) {
|
|
18443
|
+
if (typeof value !== "string" || value.length === 0) return null;
|
|
18444
|
+
let parsed;
|
|
18445
|
+
try {
|
|
18446
|
+
parsed = new URL(value);
|
|
18447
|
+
} catch {
|
|
18448
|
+
return null;
|
|
18449
|
+
}
|
|
18450
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return null;
|
|
18451
|
+
return parsed.href;
|
|
18452
|
+
}
|
|
18287
18453
|
async function searchRegistryPackages(config, opts) {
|
|
18288
18454
|
return (await getDiscoveryClient(config)).searchPackages(opts);
|
|
18289
18455
|
}
|
|
@@ -18297,6 +18463,17 @@ async function listRegistryReleases(config, did, slug, opts) {
|
|
|
18297
18463
|
return (await getDiscoveryClient(config)).listReleases(did, slug, opts);
|
|
18298
18464
|
}
|
|
18299
18465
|
/**
|
|
18466
|
+
* Derive the host environment versions (`env:emdash`, `env:astro`) the running
|
|
18467
|
+
* EmDash install advertises, so a release's `requires` constraints can be
|
|
18468
|
+
* evaluated client-side before offering install. Reads the already-fetched
|
|
18469
|
+
* admin manifest (`version`, `astroVersion`) rather than issuing a second
|
|
18470
|
+
* request. The dev-skip / astro-omit rule is shared with the server gate via
|
|
18471
|
+
* `hostEnvFromVersions`.
|
|
18472
|
+
*/
|
|
18473
|
+
function hostEnvFromManifest(manifest) {
|
|
18474
|
+
return hostEnvFromVersions(manifest?.version, manifest?.astroVersion);
|
|
18475
|
+
}
|
|
18476
|
+
/**
|
|
18300
18477
|
* Resolve a publisher DID to its claimed handle using the same
|
|
18301
18478
|
* `LocalActorResolver` pattern as `@emdash-cms/plugin-cli` and
|
|
18302
18479
|
* `@emdash-cms/auth-atproto`. Bidirectional verification (handle's
|
|
@@ -18388,6 +18565,66 @@ async function resolveDidToHandle(did) {
|
|
|
18388
18565
|
writeHandleCache(did, result);
|
|
18389
18566
|
return result;
|
|
18390
18567
|
}
|
|
18568
|
+
const ARTIFACT_PROXY_ENDPOINT = `${API_BASE}/admin/plugins/registry/artifact`;
|
|
18569
|
+
/**
|
|
18570
|
+
* Build the URL of the server-side artifact proxy for an artifact addressed by
|
|
18571
|
+
* its `(did, slug, version, kind, index)` coordinates. The browser never sends
|
|
18572
|
+
* the publisher's URL — the proxy resolves the *declared* URL from the release
|
|
18573
|
+
* record, applies SSRF defences, enforces an image content-type allowlist, and
|
|
18574
|
+
* serves the bytes back same-origin.
|
|
18575
|
+
*
|
|
18576
|
+
* Empty `version` (latest) and `index` (non-screenshot kinds) are omitted.
|
|
18577
|
+
*/
|
|
18578
|
+
function artifactProxyUrl(coords) {
|
|
18579
|
+
const params = new URLSearchParams();
|
|
18580
|
+
params.set("did", coords.did);
|
|
18581
|
+
params.set("slug", coords.slug);
|
|
18582
|
+
params.set("kind", coords.kind);
|
|
18583
|
+
if (coords.version) params.set("version", coords.version);
|
|
18584
|
+
if (coords.kind === "screenshot" && coords.index !== void 0) params.set("index", String(coords.index));
|
|
18585
|
+
return `${ARTIFACT_PROXY_ENDPOINT}?${params.toString()}`;
|
|
18586
|
+
}
|
|
18587
|
+
/**
|
|
18588
|
+
* Narrow one entry of a release's `artifacts` map to the fields we render.
|
|
18589
|
+
* Returns `null` when the value isn't an object carrying a usable `url`
|
|
18590
|
+
* (presence gate), keeping only the dimensions for layout.
|
|
18591
|
+
*
|
|
18592
|
+
* Records are lexicon-validated at the DiscoveryClient boundary, but
|
|
18593
|
+
* `artifacts` is an aggregator pass-through, so each entry still needs
|
|
18594
|
+
* shape-narrowing.
|
|
18595
|
+
*/
|
|
18596
|
+
function asMediaArtifact(value) {
|
|
18597
|
+
if (!value || typeof value !== "object") return null;
|
|
18598
|
+
const v = value;
|
|
18599
|
+
if (typeof v.url !== "string" || v.url.length === 0) return null;
|
|
18600
|
+
const artifact = {};
|
|
18601
|
+
if (typeof v.width === "number") artifact.width = v.width;
|
|
18602
|
+
if (typeof v.height === "number") artifact.height = v.height;
|
|
18603
|
+
return artifact;
|
|
18604
|
+
}
|
|
18605
|
+
/**
|
|
18606
|
+
* Pull icon, banner, and the screenshot gallery out of a release's `artifacts`
|
|
18607
|
+
* map, keeping presence and dimensions only. The lexicon types `screenshots`
|
|
18608
|
+
* as an array of artifacts; entries without a usable `url` are dropped, and
|
|
18609
|
+
* gallery order is preserved so screenshot indices line up with the proxy's.
|
|
18610
|
+
*/
|
|
18611
|
+
function extractMediaArtifacts(artifacts) {
|
|
18612
|
+
const result = { screenshots: [] };
|
|
18613
|
+
if (!artifacts || typeof artifacts !== "object") return result;
|
|
18614
|
+
const map = artifacts;
|
|
18615
|
+
const icon = asMediaArtifact(map.icon);
|
|
18616
|
+
if (icon) result.icon = icon;
|
|
18617
|
+
const banner = asMediaArtifact(map.banner);
|
|
18618
|
+
if (banner) result.banner = banner;
|
|
18619
|
+
if (Array.isArray(map.screenshots)) map.screenshots.forEach((entry, index) => {
|
|
18620
|
+
const artifact = asMediaArtifact(entry);
|
|
18621
|
+
if (artifact) result.screenshots.push({
|
|
18622
|
+
...artifact,
|
|
18623
|
+
index
|
|
18624
|
+
});
|
|
18625
|
+
});
|
|
18626
|
+
return result;
|
|
18627
|
+
}
|
|
18391
18628
|
const INSTALL_ENDPOINT = `${API_BASE}/admin/plugins/registry/install`;
|
|
18392
18629
|
/**
|
|
18393
18630
|
* Install a plugin from the registry.
|
|
@@ -18895,13 +19132,10 @@ function PluginCard({ plugin, updateInfo, onEnable, onDisable, isToggling, hasMa
|
|
|
18895
19132
|
const label = CAPABILITY_LABELS[c];
|
|
18896
19133
|
return label ? _t2(label) : c;
|
|
18897
19134
|
}).join(", "),
|
|
18898
|
-
children: [/* @__PURE__ */ jsx(ShieldCheck, { className: "h-3 w-3" }),
|
|
18899
|
-
id: "
|
|
18900
|
-
message: "{0}
|
|
18901
|
-
values: {
|
|
18902
|
-
0: plugin.capabilities.length,
|
|
18903
|
-
1: plugin.capabilities.length !== 1 ? "s" : ""
|
|
18904
|
-
}
|
|
19135
|
+
children: [/* @__PURE__ */ jsx(ShieldCheck, { className: "h-3 w-3" }), i18n._({
|
|
19136
|
+
id: "dB/Iw4",
|
|
19137
|
+
message: "{0, plural, one {# permission} other {# permissions}}",
|
|
19138
|
+
values: { 0: plugin.capabilities.length }
|
|
18905
19139
|
})]
|
|
18906
19140
|
})
|
|
18907
19141
|
]
|
|
@@ -19662,72 +19896,6 @@ function ScreenshotLightbox({ screenshots, index, isBlurred = false, onClose, on
|
|
|
19662
19896
|
]
|
|
19663
19897
|
});
|
|
19664
19898
|
}
|
|
19665
|
-
const HTML_ESCAPE_MAP = {
|
|
19666
|
-
"&": "&",
|
|
19667
|
-
"<": "<",
|
|
19668
|
-
">": ">",
|
|
19669
|
-
"\"": """,
|
|
19670
|
-
"'": "'"
|
|
19671
|
-
};
|
|
19672
|
-
const HTML_ESCAPE_RE = /[&<>"']/g;
|
|
19673
|
-
function escapeHtml(str) {
|
|
19674
|
-
return str.replace(HTML_ESCAPE_RE, (ch) => HTML_ESCAPE_MAP[ch]);
|
|
19675
|
-
}
|
|
19676
|
-
const renderer = new Renderer();
|
|
19677
|
-
renderer.link = ({ href, text }) => {
|
|
19678
|
-
if (!SAFE_URL_RE.test(href)) return escapeHtml(text);
|
|
19679
|
-
return `<a href="${escapeHtml(href)}" target="_blank" rel="noopener noreferrer">${escapeHtml(text)}</a>`;
|
|
19680
|
-
};
|
|
19681
|
-
renderer.image = ({ text }) => escapeHtml(text);
|
|
19682
|
-
renderer.html = () => "";
|
|
19683
|
-
const md = new Marked({
|
|
19684
|
-
renderer,
|
|
19685
|
-
async: false
|
|
19686
|
-
});
|
|
19687
|
-
/** Allowed tags and attributes for DOMPurify — only standard markdown output. */
|
|
19688
|
-
const SANITIZE_CONFIG = {
|
|
19689
|
-
ALLOWED_TAGS: [
|
|
19690
|
-
"h1",
|
|
19691
|
-
"h2",
|
|
19692
|
-
"h3",
|
|
19693
|
-
"h4",
|
|
19694
|
-
"h5",
|
|
19695
|
-
"h6",
|
|
19696
|
-
"p",
|
|
19697
|
-
"a",
|
|
19698
|
-
"ul",
|
|
19699
|
-
"ol",
|
|
19700
|
-
"li",
|
|
19701
|
-
"blockquote",
|
|
19702
|
-
"pre",
|
|
19703
|
-
"code",
|
|
19704
|
-
"em",
|
|
19705
|
-
"strong",
|
|
19706
|
-
"del",
|
|
19707
|
-
"br",
|
|
19708
|
-
"hr",
|
|
19709
|
-
"table",
|
|
19710
|
-
"thead",
|
|
19711
|
-
"tbody",
|
|
19712
|
-
"tr",
|
|
19713
|
-
"th",
|
|
19714
|
-
"td",
|
|
19715
|
-
"details",
|
|
19716
|
-
"summary",
|
|
19717
|
-
"sup",
|
|
19718
|
-
"sub"
|
|
19719
|
-
],
|
|
19720
|
-
ALLOWED_ATTR: [
|
|
19721
|
-
"href",
|
|
19722
|
-
"target",
|
|
19723
|
-
"rel"
|
|
19724
|
-
]
|
|
19725
|
-
};
|
|
19726
|
-
function renderMarkdown(markdown) {
|
|
19727
|
-
const result = md.parse(markdown);
|
|
19728
|
-
const html = typeof result === "string" ? result : "";
|
|
19729
|
-
return DOMPurify.sanitize(html, SANITIZE_CONFIG);
|
|
19730
|
-
}
|
|
19731
19899
|
function formatBytes(bytes) {
|
|
19732
19900
|
if (bytes < 1024) return `${bytes} B`;
|
|
19733
19901
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
@@ -23060,6 +23228,11 @@ function RegistryPluginDetail({ pluginId, config }) {
|
|
|
23060
23228
|
return fetchPlugins();
|
|
23061
23229
|
}
|
|
23062
23230
|
});
|
|
23231
|
+
const { data: manifest } = useQuery({
|
|
23232
|
+
queryKey: ["manifest"],
|
|
23233
|
+
queryFn: fetchManifest
|
|
23234
|
+
});
|
|
23235
|
+
const hostEnv = React$1.useMemo(() => hostEnvFromManifest(manifest), [manifest]);
|
|
23063
23236
|
const slashIdx = pluginId.lastIndexOf("/");
|
|
23064
23237
|
const publisher = slashIdx > 0 ? pluginId.slice(0, slashIdx) : "";
|
|
23065
23238
|
const slug = slashIdx > 0 ? pluginId.slice(slashIdx + 1) : "";
|
|
@@ -23134,11 +23307,51 @@ function RegistryPluginDetail({ pluginId, config }) {
|
|
|
23134
23307
|
}] : [];
|
|
23135
23308
|
});
|
|
23136
23309
|
const repoHref = safeExternalHref(release?.release?.repo);
|
|
23137
|
-
const
|
|
23310
|
+
const verifiedLabel = (pkg?.labels ?? []).find((l) => l.val === "verified");
|
|
23311
|
+
const verified = Boolean(verifiedLabel);
|
|
23312
|
+
const verifiedLabeller = typeof verifiedLabel?.src === "string" ? verifiedLabel.src : null;
|
|
23313
|
+
const sections = presentSections(pkgProfile);
|
|
23314
|
+
const [selectedSection, setSelectedSection] = React$1.useState(void 0);
|
|
23315
|
+
const defaultSection = sections[0]?.key;
|
|
23316
|
+
const activeSection = selectedSection && sections.some((s) => s.key === selectedSection) ? selectedSection : defaultSection;
|
|
23317
|
+
const activePane = sections.find((s) => s.key === activeSection);
|
|
23318
|
+
const sbom = extractSbom(release?.release?.sbom);
|
|
23319
|
+
const sbomHref = sbom ? sbomDownloadHref(sbom.url) : null;
|
|
23320
|
+
const lastUpdated = typeof pkgProfile?.lastUpdated === "string" ? pkgProfile.lastUpdated : null;
|
|
23321
|
+
const mediaArtifacts = extractMediaArtifacts(release?.release?.artifacts);
|
|
23322
|
+
const artifactDid = pkg?.did;
|
|
23323
|
+
const artifactVersion = release?.version;
|
|
23324
|
+
const iconSrc = mediaArtifacts.icon && artifactDid ? artifactProxyUrl({
|
|
23325
|
+
did: artifactDid,
|
|
23326
|
+
slug,
|
|
23327
|
+
version: artifactVersion,
|
|
23328
|
+
kind: "icon"
|
|
23329
|
+
}) : null;
|
|
23330
|
+
const bannerSrc = mediaArtifacts.banner && artifactDid ? artifactProxyUrl({
|
|
23331
|
+
did: artifactDid,
|
|
23332
|
+
slug,
|
|
23333
|
+
version: artifactVersion,
|
|
23334
|
+
kind: "banner"
|
|
23335
|
+
}) : null;
|
|
23336
|
+
const screenshots = artifactDid ? mediaArtifacts.screenshots.map((shot) => ({
|
|
23337
|
+
...shot,
|
|
23338
|
+
src: artifactProxyUrl({
|
|
23339
|
+
did: artifactDid,
|
|
23340
|
+
slug,
|
|
23341
|
+
version: artifactVersion,
|
|
23342
|
+
kind: "screenshot",
|
|
23343
|
+
index: shot.index
|
|
23344
|
+
})
|
|
23345
|
+
})) : [];
|
|
23138
23346
|
const policyOk = release && pkg ? releasePassesPolicy(release, {
|
|
23139
23347
|
did: pkg.did,
|
|
23140
23348
|
slug
|
|
23141
23349
|
}, config.policy) : true;
|
|
23350
|
+
const envMismatches = React$1.useMemo(() => {
|
|
23351
|
+
if (!release) return [];
|
|
23352
|
+
return checkEnvCompatibility(release.release?.requires, hostEnv);
|
|
23353
|
+
}, [release, hostEnv]);
|
|
23354
|
+
const envOk = envMismatches.length === 0;
|
|
23142
23355
|
const installedEntry = React$1.useMemo(() => {
|
|
23143
23356
|
if (!pkg || !installedPlugins) return void 0;
|
|
23144
23357
|
return installedPlugins.find((p) => p.source === "registry" && p.registryPublisherDid === pkg.did && p.registrySlug === slug);
|
|
@@ -23197,14 +23410,33 @@ function RegistryPluginDetail({ pluginId, config }) {
|
|
|
23197
23410
|
className: "space-y-6",
|
|
23198
23411
|
children: [
|
|
23199
23412
|
/* @__PURE__ */ jsx(BackLink, {}),
|
|
23413
|
+
bannerSrc ? /* @__PURE__ */ jsx("img", {
|
|
23414
|
+
src: bannerSrc,
|
|
23415
|
+
alt: _t({
|
|
23416
|
+
id: "aOmjNp",
|
|
23417
|
+
message: "{0} banner",
|
|
23418
|
+
values: { 0: displayName ?? slug }
|
|
23419
|
+
}),
|
|
23420
|
+
className: "h-40 w-full rounded-xl object-cover",
|
|
23421
|
+
loading: "lazy"
|
|
23422
|
+
}) : null,
|
|
23200
23423
|
/* @__PURE__ */ jsxs("div", {
|
|
23201
23424
|
className: "flex flex-wrap items-start gap-4",
|
|
23202
23425
|
children: [
|
|
23203
23426
|
/* @__PURE__ */ jsx("div", {
|
|
23204
|
-
className: "rounded-xl bg-kumo-subtle
|
|
23205
|
-
children: /* @__PURE__ */ jsx("
|
|
23427
|
+
className: "overflow-hidden rounded-xl bg-kumo-subtle text-kumo-subtle",
|
|
23428
|
+
children: iconSrc ? /* @__PURE__ */ jsx("img", {
|
|
23429
|
+
src: iconSrc,
|
|
23430
|
+
alt: _t({
|
|
23431
|
+
id: "dVaAiX",
|
|
23432
|
+
message: "{0} icon",
|
|
23433
|
+
values: { 0: displayName ?? slug }
|
|
23434
|
+
}),
|
|
23435
|
+
className: "block h-16 w-16 object-cover",
|
|
23436
|
+
loading: "lazy"
|
|
23437
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
23206
23438
|
"aria-hidden": true,
|
|
23207
|
-
className: "block h-
|
|
23439
|
+
className: "block h-16 w-16"
|
|
23208
23440
|
})
|
|
23209
23441
|
}),
|
|
23210
23442
|
/* @__PURE__ */ jsxs("div", {
|
|
@@ -23215,11 +23447,30 @@ function RegistryPluginDetail({ pluginId, config }) {
|
|
|
23215
23447
|
children: [/* @__PURE__ */ jsx("h1", {
|
|
23216
23448
|
className: "truncate text-3xl font-bold",
|
|
23217
23449
|
children: displayName ?? slug
|
|
23218
|
-
}), verified ? /* @__PURE__ */ jsx(
|
|
23219
|
-
|
|
23220
|
-
|
|
23221
|
-
|
|
23222
|
-
|
|
23450
|
+
}), verified ? /* @__PURE__ */ jsx(Tooltip, {
|
|
23451
|
+
content: verifiedLabeller ? _t({
|
|
23452
|
+
id: "/rITsG",
|
|
23453
|
+
message: "Verified publisher. A labeller ({verifiedLabeller}) has confirmed this publisher's identity.",
|
|
23454
|
+
values: { verifiedLabeller }
|
|
23455
|
+
}) : _t({
|
|
23456
|
+
id: "PCUvJ4",
|
|
23457
|
+
message: "Verified publisher. A labeller has confirmed this publisher's identity."
|
|
23458
|
+
}),
|
|
23459
|
+
render: /* @__PURE__ */ jsx("button", {
|
|
23460
|
+
type: "button",
|
|
23461
|
+
className: "inline-flex shrink-0 cursor-help rounded-full focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-kumo-brand",
|
|
23462
|
+
"aria-label": verifiedLabeller ? _t({
|
|
23463
|
+
id: "s7Woxs",
|
|
23464
|
+
message: "Verified publisher, confirmed by labeller {verifiedLabeller}",
|
|
23465
|
+
values: { verifiedLabeller }
|
|
23466
|
+
}) : _t({
|
|
23467
|
+
id: "70QfpA",
|
|
23468
|
+
message: "Verified publisher"
|
|
23469
|
+
}),
|
|
23470
|
+
children: /* @__PURE__ */ jsx(ShieldCheck, {
|
|
23471
|
+
className: "h-5 w-5 text-kumo-brand",
|
|
23472
|
+
"aria-hidden": true
|
|
23473
|
+
})
|
|
23223
23474
|
})
|
|
23224
23475
|
}) : null]
|
|
23225
23476
|
}),
|
|
@@ -23240,25 +23491,61 @@ function RegistryPluginDetail({ pluginId, config }) {
|
|
|
23240
23491
|
}),
|
|
23241
23492
|
release ? /* @__PURE__ */ jsxs("div", {
|
|
23242
23493
|
className: "mt-1 flex flex-wrap items-center gap-2",
|
|
23243
|
-
children: [
|
|
23244
|
-
|
|
23245
|
-
|
|
23246
|
-
_t({
|
|
23494
|
+
children: [
|
|
23495
|
+
/* @__PURE__ */ jsx("p", {
|
|
23496
|
+
className: "text-xs text-kumo-subtle",
|
|
23497
|
+
children: _t({
|
|
23247
23498
|
id: "YYdC3A",
|
|
23248
23499
|
message: "Version {0}",
|
|
23249
23500
|
values: { 0: release.version }
|
|
23250
|
-
}),
|
|
23251
|
-
" · ",
|
|
23252
|
-
_t({
|
|
23253
|
-
id: "294VT5",
|
|
23254
|
-
message: "indexed {0}",
|
|
23255
|
-
values: { 0: formatDate(release.indexedAt) }
|
|
23256
23501
|
})
|
|
23257
|
-
|
|
23258
|
-
|
|
23259
|
-
|
|
23260
|
-
|
|
23261
|
-
|
|
23502
|
+
}),
|
|
23503
|
+
isPreRelease ? /* @__PURE__ */ jsx(Badge, { children: _t({
|
|
23504
|
+
id: "omrja/",
|
|
23505
|
+
message: "Pre-release"
|
|
23506
|
+
}) }) : null,
|
|
23507
|
+
sbom?.format ? /* @__PURE__ */ jsx(Badge, { children: _t({
|
|
23508
|
+
id: "mQmHhZ",
|
|
23509
|
+
message: "SBOM · {0}",
|
|
23510
|
+
values: { 0: sbom.format }
|
|
23511
|
+
}) }) : sbom ? /* @__PURE__ */ jsx(Badge, { children: _t({
|
|
23512
|
+
id: "SBWpbn",
|
|
23513
|
+
message: "SBOM"
|
|
23514
|
+
}) }) : null,
|
|
23515
|
+
sbomHref ? /* @__PURE__ */ jsx("a", {
|
|
23516
|
+
href: sbomHref,
|
|
23517
|
+
target: "_blank",
|
|
23518
|
+
rel: "noopener noreferrer",
|
|
23519
|
+
download: true,
|
|
23520
|
+
className: "text-xs text-kumo-brand hover:underline",
|
|
23521
|
+
children: _t({
|
|
23522
|
+
id: "gm9IDy",
|
|
23523
|
+
message: "Download SBOM"
|
|
23524
|
+
})
|
|
23525
|
+
}) : null
|
|
23526
|
+
]
|
|
23527
|
+
}) : null,
|
|
23528
|
+
release ? /* @__PURE__ */ jsxs("dl", {
|
|
23529
|
+
className: "mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-kumo-subtle",
|
|
23530
|
+
children: [lastUpdated ? /* @__PURE__ */ jsxs("div", {
|
|
23531
|
+
className: "flex items-center gap-1",
|
|
23532
|
+
children: [/* @__PURE__ */ jsx("dt", {
|
|
23533
|
+
className: "font-medium",
|
|
23534
|
+
children: _t({
|
|
23535
|
+
id: "+b7T3G",
|
|
23536
|
+
message: "Updated"
|
|
23537
|
+
})
|
|
23538
|
+
}), /* @__PURE__ */ jsx("dd", { children: formatDate(lastUpdated) })]
|
|
23539
|
+
}) : null, /* @__PURE__ */ jsxs("div", {
|
|
23540
|
+
className: "flex items-center gap-1",
|
|
23541
|
+
children: [/* @__PURE__ */ jsx("dt", {
|
|
23542
|
+
className: "font-medium",
|
|
23543
|
+
children: _t({
|
|
23544
|
+
id: "I3/r1f",
|
|
23545
|
+
message: "Indexed"
|
|
23546
|
+
})
|
|
23547
|
+
}), /* @__PURE__ */ jsx("dd", { children: formatDate(release.indexedAt) })]
|
|
23548
|
+
})]
|
|
23262
23549
|
}) : null
|
|
23263
23550
|
]
|
|
23264
23551
|
}),
|
|
@@ -23312,7 +23599,7 @@ function RegistryPluginDetail({ pluginId, config }) {
|
|
|
23312
23599
|
})
|
|
23313
23600
|
}) : /* @__PURE__ */ jsx(Button, {
|
|
23314
23601
|
variant: "primary",
|
|
23315
|
-
disabled: !release || !policyOk || handleResult.status === "invalid",
|
|
23602
|
+
disabled: !release || !policyOk || !envOk || handleResult.status === "invalid",
|
|
23316
23603
|
onClick: () => setShowConsent(true),
|
|
23317
23604
|
children: _t({
|
|
23318
23605
|
id: "TKQ7K+",
|
|
@@ -23374,10 +23661,66 @@ function RegistryPluginDetail({ pluginId, config }) {
|
|
|
23374
23661
|
})
|
|
23375
23662
|
})] })]
|
|
23376
23663
|
}) : null,
|
|
23664
|
+
release && !envOk ? /* @__PURE__ */ jsxs("div", {
|
|
23665
|
+
className: "flex items-start gap-3 rounded-md border border-kumo-warning bg-kumo-warning/10 p-4 text-kumo-warning",
|
|
23666
|
+
role: "status",
|
|
23667
|
+
children: [/* @__PURE__ */ jsx(Warning, { className: "mt-0.5 h-5 w-5 shrink-0" }), /* @__PURE__ */ jsxs("div", { children: [
|
|
23668
|
+
/* @__PURE__ */ jsx("p", {
|
|
23669
|
+
className: "font-medium",
|
|
23670
|
+
children: _t({
|
|
23671
|
+
id: "25SzC8",
|
|
23672
|
+
message: "Not compatible with this environment"
|
|
23673
|
+
})
|
|
23674
|
+
}),
|
|
23675
|
+
/* @__PURE__ */ jsx("p", {
|
|
23676
|
+
className: "mt-1 text-sm text-kumo-default",
|
|
23677
|
+
children: _t({
|
|
23678
|
+
id: "RwFsM0",
|
|
23679
|
+
message: "This release requires a newer environment than your site currently runs. Upgrade before installing."
|
|
23680
|
+
})
|
|
23681
|
+
}),
|
|
23682
|
+
/* @__PURE__ */ jsx("ul", {
|
|
23683
|
+
className: "mt-2 space-y-1 text-sm text-kumo-default",
|
|
23684
|
+
children: envMismatches.map((m) => /* @__PURE__ */ jsx("li", { children: _t({
|
|
23685
|
+
id: "XMcEUU",
|
|
23686
|
+
message: "{0} {1} required — you have {2}.",
|
|
23687
|
+
values: {
|
|
23688
|
+
0: envLabel(m.key),
|
|
23689
|
+
1: m.required,
|
|
23690
|
+
2: m.host
|
|
23691
|
+
}
|
|
23692
|
+
}) }, m.key))
|
|
23693
|
+
})
|
|
23694
|
+
] })]
|
|
23695
|
+
}) : null,
|
|
23377
23696
|
description ? /* @__PURE__ */ jsx("p", {
|
|
23378
23697
|
className: "text-base text-kumo-default",
|
|
23379
23698
|
children: description
|
|
23380
23699
|
}) : null,
|
|
23700
|
+
screenshots.length > 0 ? /* @__PURE__ */ jsx("section", {
|
|
23701
|
+
"aria-label": _t({
|
|
23702
|
+
id: "lqROaa",
|
|
23703
|
+
message: "Screenshots"
|
|
23704
|
+
}),
|
|
23705
|
+
children: /* @__PURE__ */ jsx("ul", {
|
|
23706
|
+
className: "flex snap-x gap-3 overflow-x-auto pb-2",
|
|
23707
|
+
children: screenshots.map((shot, i) => /* @__PURE__ */ jsx("li", {
|
|
23708
|
+
className: "shrink-0 snap-start",
|
|
23709
|
+
children: /* @__PURE__ */ jsx("img", {
|
|
23710
|
+
src: shot.src,
|
|
23711
|
+
alt: _t({
|
|
23712
|
+
id: "3rcds3",
|
|
23713
|
+
message: "Screenshot {0}",
|
|
23714
|
+
values: { 0: i + 1 }
|
|
23715
|
+
}),
|
|
23716
|
+
width: shot.width,
|
|
23717
|
+
height: shot.height,
|
|
23718
|
+
className: "h-48 w-auto rounded-lg border border-kumo-default object-cover",
|
|
23719
|
+
loading: "lazy"
|
|
23720
|
+
})
|
|
23721
|
+
}, shot.index))
|
|
23722
|
+
})
|
|
23723
|
+
}) : null,
|
|
23381
23724
|
licenseText || repoHref || keywordList.length > 0 ? /* @__PURE__ */ jsxs("section", {
|
|
23382
23725
|
className: "flex flex-wrap items-center gap-2",
|
|
23383
23726
|
children: [
|
|
@@ -23460,6 +23803,27 @@ function RegistryPluginDetail({ pluginId, config }) {
|
|
|
23460
23803
|
className: "mt-2 flex flex-wrap gap-2",
|
|
23461
23804
|
children: capabilities.map((c) => /* @__PURE__ */ jsx(Badge, { children: c }, c))
|
|
23462
23805
|
})] }) : null,
|
|
23806
|
+
sections.length > 0 && activePane ? /* @__PURE__ */ jsxs("section", {
|
|
23807
|
+
"aria-label": _t({
|
|
23808
|
+
id: "TYxt+3",
|
|
23809
|
+
message: "Plugin details"
|
|
23810
|
+
}),
|
|
23811
|
+
children: [sections.length > 1 ? /* @__PURE__ */ jsx(Tabs, {
|
|
23812
|
+
variant: "underline",
|
|
23813
|
+
value: activeSection,
|
|
23814
|
+
onValueChange: (v) => setSelectedSection(v),
|
|
23815
|
+
tabs: sections.map((s) => ({
|
|
23816
|
+
value: s.key,
|
|
23817
|
+
label: _t(SECTION_LABELS[s.key])
|
|
23818
|
+
}))
|
|
23819
|
+
}) : /* @__PURE__ */ jsx("h2", {
|
|
23820
|
+
className: "text-sm font-semibold text-kumo-subtle",
|
|
23821
|
+
children: _t(SECTION_LABELS[activePane.key])
|
|
23822
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
23823
|
+
className: "prose prose-sm mt-4 max-w-none rounded-lg border bg-kumo-base p-6",
|
|
23824
|
+
children: /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: renderMarkdown(activePane.markdown) } })
|
|
23825
|
+
})]
|
|
23826
|
+
}) : null,
|
|
23463
23827
|
showConsent && release ? /* @__PURE__ */ jsx(CapabilityConsentDialog, {
|
|
23464
23828
|
mode: "install",
|
|
23465
23829
|
pluginName: displayName ?? slug,
|
|
@@ -23475,6 +23839,28 @@ function RegistryPluginDetail({ pluginId, config }) {
|
|
|
23475
23839
|
]
|
|
23476
23840
|
});
|
|
23477
23841
|
}
|
|
23842
|
+
const SECTION_LABELS = {
|
|
23843
|
+
description: {
|
|
23844
|
+
id: "Nu4oKW",
|
|
23845
|
+
message: "Description"
|
|
23846
|
+
},
|
|
23847
|
+
installation: {
|
|
23848
|
+
id: "Xry+oU",
|
|
23849
|
+
message: "Installation"
|
|
23850
|
+
},
|
|
23851
|
+
faq: {
|
|
23852
|
+
id: "/lDBHm",
|
|
23853
|
+
message: "FAQ"
|
|
23854
|
+
},
|
|
23855
|
+
changelog: {
|
|
23856
|
+
id: "CWe7wB",
|
|
23857
|
+
message: "Changelog"
|
|
23858
|
+
},
|
|
23859
|
+
security: {
|
|
23860
|
+
id: "a3LDKx",
|
|
23861
|
+
message: "Security"
|
|
23862
|
+
}
|
|
23863
|
+
};
|
|
23478
23864
|
function BackLink() {
|
|
23479
23865
|
const { _: _t2 } = useLingui();
|
|
23480
23866
|
return /* @__PURE__ */ jsxs(Link$1, {
|
|
@@ -23516,6 +23902,16 @@ const PRE_RELEASE_VERSION_RE = /^\d+\.\d+\.\d+-/;
|
|
|
23516
23902
|
function isPreReleaseVersion(version) {
|
|
23517
23903
|
return PRE_RELEASE_VERSION_RE.test(version);
|
|
23518
23904
|
}
|
|
23905
|
+
/**
|
|
23906
|
+
* Human-readable name for a `requires` env key. The known EmDash environments
|
|
23907
|
+
* get their proper product names; anything else falls back to the key with the
|
|
23908
|
+
* `env:` prefix stripped (product names, not localised strings).
|
|
23909
|
+
*/
|
|
23910
|
+
function envLabel(key) {
|
|
23911
|
+
if (key === "env:emdash") return "EmDash";
|
|
23912
|
+
if (key === "env:astro") return "Astro";
|
|
23913
|
+
return key.startsWith("env:") ? key.slice(4) : key;
|
|
23914
|
+
}
|
|
23519
23915
|
const YANKED_LABEL_VALUE = "security:yanked";
|
|
23520
23916
|
/**
|
|
23521
23917
|
* Aggregators forward labels applied by their configured labellers. `security:yanked`
|
|
@@ -28847,15 +29243,6 @@ function SidebarNav({ manifest }) {
|
|
|
28847
29243
|
border-top: 1px solid rgba(255,255,255,0.08);
|
|
28848
29244
|
}
|
|
28849
29245
|
|
|
28850
|
-
/* Keep all nav icons visible when sidebar collapses to icon mode */
|
|
28851
|
-
.emdash-sidebar[data-state="collapsed"] [data-sidebar="group-content"] {
|
|
28852
|
-
grid-template-rows: 1fr !important;
|
|
28853
|
-
}
|
|
28854
|
-
/* Mobile drawer: kumo's Sheet has no data-state attribute, so group-content
|
|
28855
|
-
stays at grid-rows-[0fr] (hidden). Force it open in the mobile sidebar. */
|
|
28856
|
-
.emdash-sidebar[data-mobile="true"] [data-sidebar="group-content"] {
|
|
28857
|
-
grid-template-rows: 1fr !important;
|
|
28858
|
-
}
|
|
28859
29246
|
/* Collapsed separators — thin centered line */
|
|
28860
29247
|
.emdash-sidebar[data-state="collapsed"] [data-sidebar="separator"] {
|
|
28861
29248
|
margin: 0.375rem 0.625rem;
|
|
@@ -28939,52 +29326,36 @@ function SidebarNav({ manifest }) {
|
|
|
28939
29326
|
isActive: isItemActive("/", currentPath)
|
|
28940
29327
|
}) }) }),
|
|
28941
29328
|
/* @__PURE__ */ jsx(KumoSidebar.Separator, {}),
|
|
28942
|
-
visibleContent.length > 1 && /* @__PURE__ */ jsxs(KumoSidebar.Group, {
|
|
28943
|
-
|
|
28944
|
-
|
|
28945
|
-
|
|
28946
|
-
|
|
28947
|
-
|
|
28948
|
-
|
|
28949
|
-
message: "Content"
|
|
28950
|
-
})
|
|
28951
|
-
}), /* @__PURE__ */ jsx(KumoSidebar.GroupContent, { children: /* @__PURE__ */ jsx(KumoSidebar.Menu, { children: renderNavItems(visibleContent.filter((i) => i.to !== "/")) }) })]
|
|
28952
|
-
}),
|
|
29329
|
+
visibleContent.length > 1 && /* @__PURE__ */ jsxs(KumoSidebar.Group, { children: [/* @__PURE__ */ jsx(KumoSidebar.GroupLabel, {
|
|
29330
|
+
className: "[&>span]:text-start [&_svg]:rtl:-scale-x-100 [&_svg]:rtl:-scale-y-100",
|
|
29331
|
+
children: _t({
|
|
29332
|
+
id: "4b3oEV",
|
|
29333
|
+
message: "Content"
|
|
29334
|
+
})
|
|
29335
|
+
}), /* @__PURE__ */ jsx(KumoSidebar.Menu, { children: renderNavItems(visibleContent.filter((i) => i.to !== "/")) })] }),
|
|
28953
29336
|
/* @__PURE__ */ jsx(KumoSidebar.Separator, {}),
|
|
28954
|
-
visibleManage.length > 0 && /* @__PURE__ */ jsxs(KumoSidebar.Group, {
|
|
28955
|
-
|
|
28956
|
-
|
|
28957
|
-
|
|
28958
|
-
|
|
28959
|
-
|
|
28960
|
-
|
|
28961
|
-
message: "Manage"
|
|
28962
|
-
})
|
|
28963
|
-
}), /* @__PURE__ */ jsx(KumoSidebar.GroupContent, { children: /* @__PURE__ */ jsx(KumoSidebar.Menu, { children: renderNavItems(visibleManage) }) })]
|
|
28964
|
-
}),
|
|
29337
|
+
visibleManage.length > 0 && /* @__PURE__ */ jsxs(KumoSidebar.Group, { children: [/* @__PURE__ */ jsx(KumoSidebar.GroupLabel, {
|
|
29338
|
+
className: "[&>span]:text-start [&_svg]:rtl:-scale-x-100 [&_svg]:rtl:-scale-y-100",
|
|
29339
|
+
children: _t({
|
|
29340
|
+
id: "wckWOP",
|
|
29341
|
+
message: "Manage"
|
|
29342
|
+
})
|
|
29343
|
+
}), /* @__PURE__ */ jsx(KumoSidebar.Menu, { children: renderNavItems(visibleManage) })] }),
|
|
28965
29344
|
/* @__PURE__ */ jsx(KumoSidebar.Separator, {}),
|
|
28966
|
-
visibleAdmin.length > 0 && /* @__PURE__ */ jsxs(KumoSidebar.Group, {
|
|
28967
|
-
|
|
28968
|
-
|
|
28969
|
-
|
|
28970
|
-
|
|
28971
|
-
|
|
28972
|
-
|
|
28973
|
-
|
|
28974
|
-
|
|
28975
|
-
|
|
28976
|
-
|
|
28977
|
-
|
|
28978
|
-
|
|
28979
|
-
|
|
28980
|
-
children: [/* @__PURE__ */ jsx(KumoSidebar.GroupLabel, {
|
|
28981
|
-
className: "[&>span]:text-start [&_svg]:rtl:-scale-x-100 [&_svg]:rtl:-scale-y-100",
|
|
28982
|
-
children: _t({
|
|
28983
|
-
id: "ohUJJM",
|
|
28984
|
-
message: "Plugins"
|
|
28985
|
-
})
|
|
28986
|
-
}), /* @__PURE__ */ jsx(KumoSidebar.GroupContent, { children: /* @__PURE__ */ jsx(KumoSidebar.Menu, { children: renderNavItems(visiblePlugins) }) })]
|
|
28987
|
-
})] })
|
|
29345
|
+
visibleAdmin.length > 0 && /* @__PURE__ */ jsxs(KumoSidebar.Group, { children: [/* @__PURE__ */ jsx(KumoSidebar.GroupLabel, {
|
|
29346
|
+
className: "[&>span]:text-start [&_svg]:rtl:-scale-x-100 [&_svg]:rtl:-scale-y-100",
|
|
29347
|
+
children: _t({
|
|
29348
|
+
id: "U3pytU",
|
|
29349
|
+
message: "Admin"
|
|
29350
|
+
})
|
|
29351
|
+
}), /* @__PURE__ */ jsx(KumoSidebar.Menu, { children: renderNavItems(visibleAdmin) })] }),
|
|
29352
|
+
visiblePlugins.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(KumoSidebar.Separator, {}), /* @__PURE__ */ jsxs(KumoSidebar.Group, { children: [/* @__PURE__ */ jsx(KumoSidebar.GroupLabel, {
|
|
29353
|
+
className: "[&>span]:text-start [&_svg]:rtl:-scale-x-100 [&_svg]:rtl:-scale-y-100",
|
|
29354
|
+
children: _t({
|
|
29355
|
+
id: "ohUJJM",
|
|
29356
|
+
message: "Plugins"
|
|
29357
|
+
})
|
|
29358
|
+
}), /* @__PURE__ */ jsx(KumoSidebar.Menu, { children: renderNavItems(visiblePlugins) })] })] })
|
|
28988
29359
|
] }),
|
|
28989
29360
|
/* @__PURE__ */ jsx(KumoSidebar.Footer, { children: /* @__PURE__ */ jsxs("p", {
|
|
28990
29361
|
className: "emdash-nav-label px-3 py-2 text-[11px] text-white/30",
|
|
@@ -30222,22 +30593,23 @@ function TermFormDialog({ open, onClose, taxonomyName, taxonomyDef, term, allTer
|
|
|
30222
30593
|
className: "flex items-start justify-between gap-4 mb-4",
|
|
30223
30594
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
30224
30595
|
className: "flex flex-col space-y-1.5",
|
|
30225
|
-
children: [/* @__PURE__ */
|
|
30596
|
+
children: [/* @__PURE__ */ jsx(Dialog.Title, {
|
|
30226
30597
|
className: "text-lg font-semibold leading-none tracking-tight",
|
|
30227
|
-
children:
|
|
30228
|
-
|
|
30229
|
-
|
|
30230
|
-
|
|
30231
|
-
}) : _t3({
|
|
30232
|
-
id: "m16xKo",
|
|
30233
|
-
message: "Add"
|
|
30234
|
-
}),
|
|
30235
|
-
" ",
|
|
30236
|
-
taxonomyDef.labelSingular || _t3({
|
|
30598
|
+
children: term ? _t3({
|
|
30599
|
+
id: "N6j2JH",
|
|
30600
|
+
message: "Edit {0}",
|
|
30601
|
+
values: { 0: taxonomyDef.labelSingular || _t3({
|
|
30237
30602
|
id: "k3xOSk",
|
|
30238
30603
|
message: "Term"
|
|
30239
|
-
})
|
|
30240
|
-
|
|
30604
|
+
}) }
|
|
30605
|
+
}) : _t3({
|
|
30606
|
+
id: "3nLdaX",
|
|
30607
|
+
message: "Add {0}",
|
|
30608
|
+
values: { 0: taxonomyDef.labelSingular || _t3({
|
|
30609
|
+
id: "k3xOSk",
|
|
30610
|
+
message: "Term"
|
|
30611
|
+
}) }
|
|
30612
|
+
})
|
|
30241
30613
|
}), /* @__PURE__ */ jsx(Dialog.Description, {
|
|
30242
30614
|
className: "text-sm text-kumo-subtle",
|
|
30243
30615
|
children: term ? _t3({
|
|
@@ -30762,20 +31134,17 @@ function TaxonomyManager({ taxonomyName }) {
|
|
|
30762
31134
|
message: "New Taxonomy"
|
|
30763
31135
|
})
|
|
30764
31136
|
}),
|
|
30765
|
-
/* @__PURE__ */
|
|
31137
|
+
/* @__PURE__ */ jsx(Button, {
|
|
30766
31138
|
icon: /* @__PURE__ */ jsx(Plus, {}),
|
|
30767
31139
|
onClick: () => setFormOpen(true),
|
|
30768
|
-
children:
|
|
30769
|
-
|
|
30770
|
-
|
|
30771
|
-
|
|
30772
|
-
}),
|
|
30773
|
-
" ",
|
|
30774
|
-
taxonomyDef.labelSingular || _t5({
|
|
31140
|
+
children: _t5({
|
|
31141
|
+
id: "3nLdaX",
|
|
31142
|
+
message: "Add {0}",
|
|
31143
|
+
values: { 0: taxonomyDef.labelSingular || _t5({
|
|
30775
31144
|
id: "k3xOSk",
|
|
30776
31145
|
message: "Term"
|
|
30777
|
-
})
|
|
30778
|
-
|
|
31146
|
+
}) }
|
|
31147
|
+
})
|
|
30779
31148
|
})
|
|
30780
31149
|
]
|
|
30781
31150
|
})]
|