@jini-ai/admin 0.4.3 → 0.4.4
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/CHANGELOG.md +4 -0
- package/dist/comments/comments.module.d.ts +4 -4
- package/dist/comments/messages.en.d.ts +4 -4
- package/dist/comments/messages.en.js +4 -4
- package/dist/comments/react/components/QueueSection.js +1 -1
- package/dist/comments/react/index.d.ts +16 -16
- package/dist/forms/forms.module.d.ts +3 -2
- package/dist/forms/html-rules.d.ts +14 -0
- package/dist/forms/html-rules.js +10 -0
- package/dist/forms/messages.en.d.ts +3 -2
- package/dist/forms/messages.en.js +4 -3
- package/dist/forms/react/components/FormEditorHeaderText.js +2 -2
- package/dist/forms/react/components/FormSubmissionDetail.d.ts +3 -1
- package/dist/forms/react/components/FormSubmissionDetail.js +3 -2
- package/dist/forms/react/components/FormSubmissions.js +1 -1
- package/dist/forms/react/hooks/forms-view.hooks.d.ts +1 -6
- package/dist/forms/react/hooks/forms-view.hooks.js +3 -4
- package/dist/forms/react/index.d.ts +9 -6
- package/dist/forms/react/pages/FormsList.js +2 -7
- package/dist/react/components/RowMenu/RowMenu.hooks.js +14 -3
- package/dist/redirects/messages.en.d.ts +9 -7
- package/dist/redirects/messages.en.js +14 -9
- package/dist/redirects/react/hooks/RedirectsPage.hooks.js +2 -2
- package/dist/redirects/react/index.d.ts +36 -28
- package/dist/redirects/react/pages/RedirectsPage.js +1 -1
- package/dist/redirects/redirects.module.d.ts +9 -7
- package/dist/seo/messages.en.d.ts +5 -4
- package/dist/seo/messages.en.js +6 -5
- package/dist/seo/react/components/MediaRefField.d.ts +4 -0
- package/dist/seo/react/components/MediaRefField.js +2 -2
- package/dist/seo/react/components/SitemapModal.js +4 -2
- package/dist/seo/react/hooks/MediaRefField.hooks.d.ts +4 -1
- package/dist/seo/react/hooks/MediaRefField.hooks.js +3 -3
- package/dist/seo/react/index.d.ts +15 -12
- package/dist/seo/react/options.d.ts +7 -0
- package/dist/seo/react/pages/SeoPage.js +23 -23
- package/dist/seo/rules.d.ts +6 -0
- package/dist/seo/rules.js +8 -0
- package/dist/seo/seo.module.d.ts +5 -4
- package/dist/widgets/react/pages/WidgetRegionEditor.js +1 -1
- package/package.json +5 -5
|
@@ -30,7 +30,7 @@ import { SitemapModal } from "../components/SitemapModal.js";
|
|
|
30
30
|
* routed through here.
|
|
31
31
|
*/
|
|
32
32
|
function SeoSection({ title, children }) {
|
|
33
|
-
return (_jsxs("section", { className: "seo-section", children: [_jsx("div", { className: "seo-section-head", children: _jsx("h2", { className: "seo-section-title", children: title }) }), _jsx("div", { className: "seo-section-body", children: children })] }));
|
|
33
|
+
return (_jsxs("section", { className: "jini-seo-section", children: [_jsx("div", { className: "jini-seo-section-head", children: _jsx("h2", { className: "jini-seo-section-title", children: title }) }), _jsx("div", { className: "jini-seo-section-body", children: children })] }));
|
|
34
34
|
}
|
|
35
35
|
/** Dropdown over every post + page, sourced from the already-existing `listPosts`/`listPages`
|
|
36
36
|
* routes — cheapest entry-selection UX available given what's already built (REQ-06). */
|
|
@@ -38,9 +38,9 @@ function EntryPicker({ locale, entryId, onChange, useEntryPickerHook = useWiredE
|
|
|
38
38
|
const t = useSeoCopy();
|
|
39
39
|
const { entries, error } = useEntryPickerHook();
|
|
40
40
|
if (error)
|
|
41
|
-
return _jsx("div", { className: "notice error", children: error });
|
|
41
|
+
return _jsx("div", { className: "jini-notice jini-notice-error", children: error });
|
|
42
42
|
if (!entries)
|
|
43
|
-
return _jsx("div", { className: "notice", children: t({ locale: locale, key: "Loading entries…" }) });
|
|
43
|
+
return _jsx("div", { className: "jini-notice", children: t({ locale: locale, key: "Loading entries…" }) });
|
|
44
44
|
/* The "Entry" caption is no longer painted (owner, 2026-09-06: "you can actually just get rid of
|
|
45
45
|
'Entry'") — the placeholder option already says "Choose an entry…", so the visible word was
|
|
46
46
|
saying it twice, and with no gap between the two it read as one smashed-together control.
|
|
@@ -50,7 +50,7 @@ function EntryPicker({ locale, entryId, onChange, useEntryPickerHook = useWiredE
|
|
|
50
50
|
`<select>` with no accessible name at all. `display: contents` drops the wrapper's own box, so
|
|
51
51
|
the select keeps the section body's sizing exactly as if the label were not there.
|
|
52
52
|
The i18n key is unchanged and still passed through `t`, so nothing dangles. */
|
|
53
|
-
return (_jsxs("label", { className: "a11y-label-wrap", children: [_jsx("span", { className: "visually-hidden", children: t({ locale: locale, key: "Entry" }) }), _jsxs("select", { value: entryId, onChange: (e) => onChange(e.target.value), ...agentHandle({ handle: "seo-entry-picker" }, {
|
|
53
|
+
return (_jsxs("label", { className: "jini-a11y-label-wrap", children: [_jsx("span", { className: "jini-visually-hidden", children: t({ locale: locale, key: "Entry" }) }), _jsxs("select", { className: "jini-select", value: entryId, onChange: (e) => onChange(e.target.value), ...agentHandle({ handle: "seo-entry-picker" }, {
|
|
54
54
|
role: "field",
|
|
55
55
|
label: "Choose which page or post to edit or analyze SEO for",
|
|
56
56
|
}), children: [_jsx("option", { value: "", children: t({ locale: locale, key: "Choose an entry…" }) }), entries.map((entry) => (_jsxs("option", { value: entry.id, children: [entry.title, " (", entry.status, ")"] }, entry.id)))] })] }));
|
|
@@ -62,7 +62,7 @@ function AnalyzePanel(props) {
|
|
|
62
62
|
const t = useSeoCopy();
|
|
63
63
|
const { locale } = props;
|
|
64
64
|
const { sortedIssues } = useAnalyzePanel({ analysis: props.analysis });
|
|
65
|
-
return (_jsxs("div", { className: "seo-analyze-panel", children: [_jsx("h3", { children: t({ locale: locale, key: "Analysis" }) }), _jsxs("p", { children: [t({ locale: locale, key: "Score:" }), " ", _jsx("strong", { children: props.analysis.score })] }), sortedIssues.length === 0 ? (_jsx("p", { className: "muted-cell", children: t({ locale: locale, key: "No issues." }) })) : (_jsx("ul", { children: sortedIssues.map((issue, i) => (_jsxs("li", { children: [_jsx("span", { className: `status status-${issue.severity === "error" ? "failure" : issue.severity === "warning" ? "unavailable" : "success"}`, children: issue.severity }), " ", _jsx("code", { children: issue.code }), issue.field ? _jsxs("span", { className: "muted-cell", children: [" (", issue.field, ")"] }) : null, " \u2014 ", issue.message] }, `${issue.code}-${i}`))) }))] }));
|
|
65
|
+
return (_jsxs("div", { className: "jini-seo-analyze-panel", children: [_jsx("h3", { children: t({ locale: locale, key: "Analysis" }) }), _jsxs("p", { children: [t({ locale: locale, key: "Score:" }), " ", _jsx("strong", { children: props.analysis.score })] }), sortedIssues.length === 0 ? (_jsx("p", { className: "jini-muted-cell", children: t({ locale: locale, key: "No issues." }) })) : (_jsx("ul", { children: sortedIssues.map((issue, i) => (_jsxs("li", { children: [_jsx("span", { className: `jini-status jini-status-${issue.severity === "error" ? "failure" : issue.severity === "warning" ? "unavailable" : "success"}`, children: issue.severity }), " ", _jsx("code", { children: issue.code }), issue.field ? _jsxs("span", { className: "jini-muted-cell", children: [" (", issue.field, ")"] }) : null, " \u2014 ", issue.message] }, `${issue.code}-${i}`))) }))] }));
|
|
66
66
|
}
|
|
67
67
|
/** Per-entry overrides edit form (REQ-06). Pre-fills from `getSeoEntry`'s resolved meta, but
|
|
68
68
|
* tracks which fields the user actually touched so `putSeoEntry` only ever sends a genuine
|
|
@@ -84,10 +84,10 @@ function SeoEntryPanel({ locale, entryId, useSeoEntryPanelHook = useWiredSeoEntr
|
|
|
84
84
|
const t = useSeoCopy();
|
|
85
85
|
const { resolved, analysis, loadError, saving, saveError, notice, fieldValue, setField, save, touched } = useSeoEntryPanelHook({ entryId });
|
|
86
86
|
if (loadError)
|
|
87
|
-
return _jsx("div", { className: "notice error", children: loadError });
|
|
87
|
+
return _jsx("div", { className: "jini-notice jini-notice-error", children: loadError });
|
|
88
88
|
if (!resolved)
|
|
89
|
-
return _jsx("div", { className: "notice", children: t({ locale: locale, key: "Loading entry SEO…" }) });
|
|
90
|
-
return (_jsxs("div", { className: "seo-entry-panel", children: [_jsx("h3", { children: t({ locale: locale, key: "Per-entry overrides" }) }), _jsxs("p", { className: "muted-cell", children: [t({ locale: locale, key: "Fields show the currently-effective value (author override, or site default, or derived from the entry). Only fields you change here are saved as overrides." }), " ", t({ locale: locale, key: "Empty a field and save to remove its override — the entry falls back to the site default." })] }), notice ? _jsx("div", { className: "notice", children: notice }) : null, saveError ? (_jsx("div", { className: "notice error", role: "alert", children: saveError })) : null, _jsxs("label", { children: [t({ locale: locale, key: "Title" }), _jsx("input", { value: fieldValue("title", resolved.title) ?? "", onChange: (e) => setField("title", e.target.value), ...agentHandle({ handle: "seo-entry-title" }, { role: "field", label: "This entry's SEO title override" }) })] }), _jsxs("label", { children: [t({ locale: locale, key: "Description" }), _jsx("textarea", { value: fieldValue("description", resolved.description ?? "") ?? "", onChange: (e) => setField("description", e.target.value), ...agentHandle({ handle: "seo-entry-description" }, { role: "field", label: "This entry's SEO description override" }) })] }), _jsxs("label", { children: [t({ locale: locale, key: "Canonical URL" }), _jsx("input", { value: fieldValue("canonical", resolved.canonical) ?? "", onChange: (e) => setField("canonical", e.target.value), ...agentHandle({ handle: "seo-entry-canonical" }, { role: "field", label: "This entry's canonical URL override" }) })] }), _jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: fieldValue("noindex", resolved.robots.noindex) ?? false, onChange: (e) => setField("noindex", e.target.checked), ...agentHandle({ handle: "seo-entry-noindex" }, { role: "checkbox", label: "Override this entry's robots noindex directive" }) }), t({ locale: locale, key: "Noindex" })] }), _jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: fieldValue("nofollow", resolved.robots.nofollow) ?? false, onChange: (e) => setField("nofollow", e.target.checked), ...agentHandle({ handle: "seo-entry-nofollow" }, { role: "checkbox", label: "Override this entry's robots nofollow directive" }) }), t({ locale: locale, key: "Nofollow" })] }), _jsxs("label", { children: [t({ locale: locale, key: "OG title" }), _jsx("input", { value: fieldValue("ogTitle", resolved.openGraph.title) ?? "", onChange: (e) => setField("ogTitle", e.target.value), ...agentHandle({ handle: "seo-entry-og-title" }, { role: "field", label: "This entry's Open Graph title override" }) })] }), _jsxs("label", { children: [t({ locale: locale, key: "OG description" }), _jsx("input", { value: fieldValue("ogDescription", resolved.openGraph.description ?? "") ?? "", onChange: (e) => setField("ogDescription", e.target.value), ...agentHandle({ handle: "seo-entry-og-description" }, { role: "field", label: "This entry's Open Graph description override" }) })] }), _jsx(MediaRefField, { locale: locale, id: "seo-entry-og-image", label: t({ locale: locale, key: "OG image
|
|
89
|
+
return _jsx("div", { className: "jini-notice", children: t({ locale: locale, key: "Loading entry SEO…" }) });
|
|
90
|
+
return (_jsxs("div", { className: "jini-seo-entry-panel", children: [_jsx("h3", { children: t({ locale: locale, key: "Per-entry overrides" }) }), _jsxs("p", { className: "jini-muted-cell", children: [t({ locale: locale, key: "Fields show the currently-effective value (author override, or site default, or derived from the entry). Only fields you change here are saved as overrides." }), " ", t({ locale: locale, key: "Empty a field and save to remove its override — the entry falls back to the site default." })] }), notice ? _jsx("div", { className: "jini-notice", children: notice }) : null, saveError ? (_jsx("div", { className: "jini-notice jini-notice-error", role: "alert", children: saveError })) : null, _jsxs("label", { children: [t({ locale: locale, key: "Title" }), _jsx("input", { className: "jini-input", value: fieldValue("title", resolved.title) ?? "", onChange: (e) => setField("title", e.target.value), ...agentHandle({ handle: "seo-entry-title" }, { role: "field", label: "This entry's SEO title override" }) })] }), _jsxs("label", { children: [t({ locale: locale, key: "Description" }), _jsx("textarea", { className: "jini-textarea", value: fieldValue("description", resolved.description ?? "") ?? "", onChange: (e) => setField("description", e.target.value), ...agentHandle({ handle: "seo-entry-description" }, { role: "field", label: "This entry's SEO description override" }) })] }), _jsxs("label", { children: [t({ locale: locale, key: "Canonical URL" }), _jsx("input", { className: "jini-input", value: fieldValue("canonical", resolved.canonical) ?? "", onChange: (e) => setField("canonical", e.target.value), ...agentHandle({ handle: "seo-entry-canonical" }, { role: "field", label: "This entry's canonical URL override" }) })] }), _jsxs("label", { children: [_jsx("input", { className: "jini-input", type: "checkbox", checked: fieldValue("noindex", resolved.robots.noindex) ?? false, onChange: (e) => setField("noindex", e.target.checked), ...agentHandle({ handle: "seo-entry-noindex" }, { role: "checkbox", label: "Override this entry's robots noindex directive" }) }), t({ locale: locale, key: "Noindex" })] }), _jsxs("label", { children: [_jsx("input", { className: "jini-input", type: "checkbox", checked: fieldValue("nofollow", resolved.robots.nofollow) ?? false, onChange: (e) => setField("nofollow", e.target.checked), ...agentHandle({ handle: "seo-entry-nofollow" }, { role: "checkbox", label: "Override this entry's robots nofollow directive" }) }), t({ locale: locale, key: "Nofollow" })] }), _jsxs("label", { children: [t({ locale: locale, key: "OG title" }), _jsx("input", { className: "jini-input", value: fieldValue("ogTitle", resolved.openGraph.title) ?? "", onChange: (e) => setField("ogTitle", e.target.value), ...agentHandle({ handle: "seo-entry-og-title" }, { role: "field", label: "This entry's Open Graph title override" }) })] }), _jsxs("label", { children: [t({ locale: locale, key: "OG description" }), _jsx("input", { className: "jini-input", value: fieldValue("ogDescription", resolved.openGraph.description ?? "") ?? "", onChange: (e) => setField("ogDescription", e.target.value), ...agentHandle({ handle: "seo-entry-og-description" }, { role: "field", label: "This entry's Open Graph description override" }) })] }), _jsx(MediaRefField, { locale: locale, id: "seo-entry-og-image", label: t({ locale: locale, key: "OG image" }), value: fieldValue("ogImage", resolved.openGraph.image ?? "") ?? "", onChange: (value) => setField("ogImage", value), agentHandle: "seo-entry-og-image" }), _jsxs("label", { children: [t({ locale: locale, key: "Twitter title" }), _jsx("input", { className: "jini-input", value: fieldValue("twitterTitle", resolved.twitter.title) ?? "", onChange: (e) => setField("twitterTitle", e.target.value), ...agentHandle({ handle: "seo-entry-twitter-title" }, { role: "field", label: "This entry's Twitter card title override" }) })] }), _jsxs("label", { children: [t({ locale: locale, key: "Twitter description" }), _jsx("input", { className: "jini-input", value: fieldValue("twitterDescription", resolved.twitter.description ?? "") ?? "", onChange: (e) => setField("twitterDescription", e.target.value), ...agentHandle({ handle: "seo-entry-twitter-description" }, { role: "field", label: "This entry's Twitter card description override" }) })] }), _jsx(MediaRefField, { locale: locale, id: "seo-entry-twitter-image", label: t({ locale: locale, key: "Twitter image" }), value: fieldValue("twitterImage", resolved.twitter.image ?? "") ?? "", onChange: (value) => setField("twitterImage", value), agentHandle: "seo-entry-twitter-image" }), _jsx("span", { className: "jini-editor-actions", children: _jsx("button", { type: "button", className: "jini-btn jini-btn-secondary", onClick: save, disabled: saving || Object.keys(touched).length === 0, ...agentHandle({ handle: "seo-entry-save-overrides" }, { role: "button", label: "Save this entry's SEO overrides" }), children: saving ? t({ locale: locale, key: "Saving…" }) : t({ locale: locale, key: "Save overrides" }) }) }), analysis ? _jsx(AnalyzePanel, { locale: locale, analysis: analysis }) : null] }));
|
|
91
91
|
}
|
|
92
92
|
/** Section wrapper (REQ-06/07) — entry picker over the per-entry edit + analyze panels. This IS the
|
|
93
93
|
* "Pages & posts" tab panel; it kept its name and its markup, because nothing about it changed
|
|
@@ -102,7 +102,7 @@ function SeoEntryPanel({ locale, entryId, useSeoEntryPanelHook = useWiredSeoEntr
|
|
|
102
102
|
export function SeoEntrySection({ locale, useSeoEntrySectionHook = useSeoEntrySection }, _optional = {}) {
|
|
103
103
|
const t = useSeoCopy();
|
|
104
104
|
const { entryId, setEntryId } = useSeoEntrySectionHook();
|
|
105
|
-
return (_jsx("div", { className: "seo-panel seo-panel--full seo-entry-section", ...agentHandle({ handle: "seo-per-entry" }, {
|
|
105
|
+
return (_jsx("div", { className: "jini-seo-panel jini-seo-panel--full jini-seo-entry-section", ...agentHandle({ handle: "seo-per-entry" }, {
|
|
106
106
|
role: "region",
|
|
107
107
|
label: "Per-entry SEO overrides — pick one entry and edit or analyze its metadata",
|
|
108
108
|
}), children: _jsxs(SeoSection, { title: t({ locale: locale, key: "Per-entry SEO" }), children: [_jsx(EntryPicker, { locale: locale, entryId: entryId, onChange: setEntryId }), entryId ? _jsx(SeoEntryPanel, { locale: locale, entryId: entryId }, entryId) : null] }) }));
|
|
@@ -130,28 +130,28 @@ export function SeoDefaultsTab({ controller }, _optional = {}) {
|
|
|
130
130
|
const t = useSeoCopy();
|
|
131
131
|
const { locale, settings, saving, defaultOgImage, setDefaultOgImage } = controller;
|
|
132
132
|
const { submit } = useSeoDefaults({ save: controller.save });
|
|
133
|
-
return (_jsxs("form", { className: "seo-panel seo-panel--full", ...agentHandle({ handle: "seo-defaults-form" }, {
|
|
133
|
+
return (_jsxs("form", { className: "jini-seo-panel jini-seo-panel--full", ...agentHandle({ handle: "seo-defaults-form" }, {
|
|
134
134
|
role: "form",
|
|
135
135
|
label: "Site-wide SEO defaults — title template, meta description, social image, robots",
|
|
136
|
-
}), onSubmit: submit, children: [_jsxs(SeoSection, { title: t({ locale: locale, key: "Search appearance" }), children: [_jsxs("div", { className: "field", children: [_jsx("label", { className: "field-label", htmlFor: "seo-title-template", children: t({ locale: locale, key: "Title template (must contain %s)" }) }), _jsx("input", { id: "seo-title-template", name: "titleTemplate", defaultValue: settings.titleTemplate, ...agentHandle({ handle: "seo-title-template" }, {
|
|
136
|
+
}), onSubmit: submit, children: [_jsxs(SeoSection, { title: t({ locale: locale, key: "Search appearance" }), children: [_jsxs("div", { className: "jini-field", children: [_jsx("label", { className: "jini-field-label", htmlFor: "seo-title-template", children: t({ locale: locale, key: "Title template (must contain %s)" }) }), _jsx("input", { className: "jini-input", id: "seo-title-template", name: "titleTemplate", defaultValue: settings.titleTemplate, ...agentHandle({ handle: "seo-title-template" }, {
|
|
137
137
|
role: "field",
|
|
138
138
|
label: "Site-wide title template; %s is replaced by the page's own title",
|
|
139
|
-
}) })] }), _jsxs("div", { className: "field", children: [_jsx("label", { className: "field-label", htmlFor: "seo-default-description", children: t({ locale: locale, key: "Default meta description" }) }), _jsx("textarea", { id: "seo-default-description", name: "defaultDescription", defaultValue: orEmpty({ value: settings.defaultDescription }), ...agentHandle({ handle: "seo-default-description" }, {
|
|
139
|
+
}) })] }), _jsxs("div", { className: "jini-field", children: [_jsx("label", { className: "jini-field-label", htmlFor: "seo-default-description", children: t({ locale: locale, key: "Default meta description" }) }), _jsx("textarea", { className: "jini-textarea", id: "seo-default-description", name: "defaultDescription", defaultValue: orEmpty({ value: settings.defaultDescription }), ...agentHandle({ handle: "seo-default-description" }, {
|
|
140
140
|
role: "field",
|
|
141
141
|
label: "Fallback meta description for pages that set none of their own",
|
|
142
|
-
}) })] })] }), _jsxs(SeoSection, { title: t({ locale: locale, key: "Social sharing" }), children: [_jsx(MediaRefField, { locale: locale, id: "seo-default-og-image", name: "defaultOgImage", label: t({ locale: locale, key: "Default Open Graph / Twitter image
|
|
142
|
+
}) })] })] }), _jsxs(SeoSection, { title: t({ locale: locale, key: "Social sharing" }), children: [_jsx(MediaRefField, { locale: locale, id: "seo-default-og-image", name: "defaultOgImage", label: t({ locale: locale, key: "Default Open Graph / Twitter image" }), value: defaultOgImage, onChange: setDefaultOgImage, agentHandle: "seo-default-og-image" }), _jsxs("div", { className: "jini-field", children: [_jsx("label", { className: "jini-field-label", htmlFor: "seo-twitter-site", children: t({ locale: locale, key: "Twitter @site handle" }) }), _jsx("input", { className: "jini-input", id: "seo-twitter-site", name: "twitterSite", defaultValue: orEmpty({ value: settings.twitterSite }), ...agentHandle({ handle: "seo-twitter-site" }, {
|
|
143
143
|
role: "field",
|
|
144
144
|
label: "The site's Twitter @handle, used in Twitter card metadata",
|
|
145
|
-
}) })] })] }), _jsxs(SeoSection, { title: t({ locale: locale, key: "Crawling" }), children: [_jsxs("label", { className: "form-checkbox-field", children: [_jsx("input", { type: "checkbox", name: "noindex", defaultChecked: settings.defaultRobots.noindex, ...agentHandle({ handle: "seo-default-noindex" }, {
|
|
145
|
+
}) })] })] }), _jsxs(SeoSection, { title: t({ locale: locale, key: "Crawling" }), children: [_jsxs("label", { className: "jini-form-checkbox-field", children: [_jsx("input", { className: "jini-input", type: "checkbox", name: "noindex", defaultChecked: settings.defaultRobots.noindex, ...agentHandle({ handle: "seo-default-noindex" }, {
|
|
146
146
|
role: "checkbox",
|
|
147
147
|
label: "Ask search engines not to index pages by default",
|
|
148
|
-
}) }), t({ locale: locale, key: "Default noindex" })] }), _jsxs("label", { className: "form-checkbox-field", children: [_jsx("input", { type: "checkbox", name: "nofollow", defaultChecked: settings.defaultRobots.nofollow, ...agentHandle({ handle: "seo-default-nofollow" }, {
|
|
148
|
+
}) }), t({ locale: locale, key: "Default noindex" })] }), _jsxs("label", { className: "jini-form-checkbox-field", children: [_jsx("input", { className: "jini-input", type: "checkbox", name: "nofollow", defaultChecked: settings.defaultRobots.nofollow, ...agentHandle({ handle: "seo-default-nofollow" }, {
|
|
149
149
|
role: "checkbox",
|
|
150
150
|
label: "Ask search engines not to follow links by default",
|
|
151
|
-
}) }), t({ locale: locale, key: "Default nofollow" })] }), _jsxs("label", { className: "form-checkbox-field", children: [_jsx("input", { type: "checkbox", name: "sitemapEnabled", defaultChecked: settings.sitemapEnabled, ...agentHandle({ handle: "seo-sitemap-enabled" }, {
|
|
151
|
+
}) }), t({ locale: locale, key: "Default nofollow" })] }), _jsxs("label", { className: "jini-form-checkbox-field", children: [_jsx("input", { className: "jini-input", type: "checkbox", name: "sitemapEnabled", defaultChecked: settings.sitemapEnabled, ...agentHandle({ handle: "seo-sitemap-enabled" }, {
|
|
152
152
|
role: "checkbox",
|
|
153
153
|
label: "Whether this site publishes a sitemap at all",
|
|
154
|
-
}) }), t({ locale: locale, key: "Sitemap enabled" })] })] }), _jsx("div", { className: "editor-actions form-actions seo-actions", children: _jsx("button", { type: "submit", disabled: saving, ...agentHandle({ handle: "seo-save-settings" }, {
|
|
154
|
+
}) }), t({ locale: locale, key: "Sitemap enabled" })] })] }), _jsx("div", { className: "jini-editor-actions jini-form-actions jini-seo-actions", children: _jsx("button", { className: "jini-btn", type: "submit", disabled: saving, ...agentHandle({ handle: "seo-save-settings" }, {
|
|
155
155
|
role: "button",
|
|
156
156
|
label: "Save the site-wide SEO defaults above",
|
|
157
157
|
}), children: actionLabel({ pending: saving, pendingLabel: t({ locale: locale, key: "Saving…" }), idleLabel: t({ locale: locale, key: "Save settings" }) }) }) })] }));
|
|
@@ -176,13 +176,13 @@ export function SeoDefaultsTab({ controller }, _optional = {}) {
|
|
|
176
176
|
export function SeoSitemapTab({ controller }, _optional = {}) {
|
|
177
177
|
const t = useSeoCopy();
|
|
178
178
|
const { locale, settings, saving, regenerateSitemap, openSitemapModal } = controller;
|
|
179
|
-
return (_jsx("div", { className: "seo-panel seo-panel--full", ...agentHandle({ handle: "seo-sitemap" }, {
|
|
179
|
+
return (_jsx("div", { className: "jini-seo-panel jini-seo-panel--full", ...agentHandle({ handle: "seo-sitemap" }, {
|
|
180
180
|
role: "region",
|
|
181
181
|
label: "Sitemap — force a rebuild of the cached sitemap",
|
|
182
|
-
}), children: _jsxs(SeoSection, { title: t({ locale: locale, key: "Cached sitemap" }), children: [_jsx("p", { className: "card-lead", children: sitemapStateLabel({ sitemapEnabled: settings.sitemapEnabled }, { t: key => t({ locale, key }) }) }), _jsx("p", { children: t({ locale: locale, key: "
|
|
182
|
+
}), children: _jsxs(SeoSection, { title: t({ locale: locale, key: "Cached sitemap" }), children: [_jsx("p", { className: "jini-card-lead", children: sitemapStateLabel({ sitemapEnabled: settings.sitemapEnabled }, { t: key => t({ locale, key }) }) }), _jsx("p", { children: t({ locale: locale, key: "Rebuild the sitemap now to include the latest published content." }) }), _jsxs("span", { className: "jini-editor-actions", children: [_jsx("button", { className: "jini-btn jini-btn-secondary", disabled: saving, onClick: regenerateSitemap, ...agentHandle({ handle: "seo-regenerate-sitemap" }, {
|
|
183
183
|
role: "button",
|
|
184
184
|
label: "Rebuild the cached sitemap now, bypassing the cache",
|
|
185
|
-
}), children: actionLabel({ pending: saving, pendingLabel: t({ locale: locale, key: "Working…" }), idleLabel: t({ locale: locale, key: "Regenerate sitemap" }) }) }), _jsx("button", { type: "button", className: "btn-secondary", onClick: openSitemapModal, ...agentHandle({ handle: "seo-view-sitemap" }, {
|
|
185
|
+
}), children: actionLabel({ pending: saving, pendingLabel: t({ locale: locale, key: "Working…" }), idleLabel: t({ locale: locale, key: "Regenerate sitemap" }) }) }), _jsx("button", { type: "button", className: "jini-btn jini-btn-secondary", onClick: openSitemapModal, ...agentHandle({ handle: "seo-view-sitemap" }, {
|
|
186
186
|
role: "button",
|
|
187
187
|
label: "Open a modal showing the sitemap's URLs, or its raw XML",
|
|
188
188
|
}), children: t({ locale: locale, key: "View sitemap" }) })] })] }) }));
|
|
@@ -195,9 +195,9 @@ export function Seo({ tabId, onTabChange, useSeoHook = useWiredSeo } = {}, _opti
|
|
|
195
195
|
// Locale is a context-backed hook; it must run on the loading/error renders too.
|
|
196
196
|
const { activeTabId, tabs } = useSeoTabs({ tabId });
|
|
197
197
|
if (error && !settings)
|
|
198
|
-
return _jsx("div", { className: "notice error", children: error });
|
|
198
|
+
return _jsx("div", { className: "jini-page", children: _jsx("div", { className: "jini-notice jini-notice-error", children: error }) });
|
|
199
199
|
if (!settings)
|
|
200
|
-
return _jsx("div", { className: "notice", children: t({ locale: locale, key: "Loading SEO settings…" }) });
|
|
200
|
+
return _jsx("div", { className: "jini-page", children: _jsx("div", { className: "jini-notice", children: t({ locale: locale, key: "Loading SEO settings…" }) }) });
|
|
201
201
|
const controller = {
|
|
202
202
|
locale,
|
|
203
203
|
settings,
|
|
@@ -208,7 +208,7 @@ export function Seo({ tabId, onTabChange, useSeoHook = useWiredSeo } = {}, _opti
|
|
|
208
208
|
regenerateSitemap,
|
|
209
209
|
openSitemapModal,
|
|
210
210
|
};
|
|
211
|
-
return (_jsxs("div", { className: "page seo-page", children: [_jsxs("div", { className: "page-header", children: [_jsxs("div", { className: "page-header-text", children: [_jsx("p", { className: "page-kicker", children: t({ locale: locale, key: "Marketing" }) }), _jsx("h1", { className: "page-title", children: "SEO" }), _jsx("p", { className: "page-description", children: t({ locale: locale, key: "Site-wide defaults for meta titles, descriptions, Open Graph/Twitter cards, and robots directives." }) })] }), _jsx("div", { className: "page-actions", children: headerActions })] }), error ? _jsx("div", { className: "notice error", children: error }) : null, notice ? _jsx("div", { className: "notice", children: notice }) : null, _jsx(TabBar, { ariaLabel: "SEO", tabs: tabs, activeId: activeTabId, onChange: changeTab, containerHandle: "seo-tab-bar" }), seoTabPanel({ activeTabId, controller, components: { SeoDefaultsTab, SeoSitemapTab, SeoEntrySection } }), sitemapModalOpen ? (_jsx(SitemapModal, { locale: locale, sitemapEnabled: settings.sitemapEnabled, regenerating: saving, onRegenerate: regenerateSitemap, onClose: closeSitemapModal })) : null] }));
|
|
211
|
+
return (_jsxs("div", { className: "jini-page jini-seo-page", children: [_jsxs("div", { className: "jini-page-header", children: [_jsxs("div", { className: "jini-page-header-text", children: [_jsx("p", { className: "jini-page-kicker", children: t({ locale: locale, key: "Marketing" }) }), _jsx("h1", { className: "jini-page-title", children: "SEO" }), _jsx("p", { className: "jini-page-description", children: t({ locale: locale, key: "Site-wide defaults for meta titles, descriptions, Open Graph/Twitter cards, and robots directives." }) })] }), _jsx("div", { className: "jini-page-actions", children: headerActions })] }), error ? _jsx("div", { className: "jini-notice jini-notice-error", children: error }) : null, notice ? _jsx("div", { className: "jini-notice", children: notice }) : null, _jsx(TabBar, { ariaLabel: "SEO", tabs: tabs, activeId: activeTabId, onChange: changeTab, containerHandle: "seo-tab-bar" }), seoTabPanel({ activeTabId, controller, components: { SeoDefaultsTab, SeoSitemapTab, SeoEntrySection } }), sitemapModalOpen ? (_jsx(SitemapModal, { locale: locale, sitemapEnabled: settings.sitemapEnabled, regenerating: saving, onRegenerate: regenerateSitemap, onClose: closeSitemapModal })) : null] }));
|
|
212
212
|
}
|
|
213
213
|
export { Seo as SeoPage };
|
|
214
214
|
export default Seo;
|
package/dist/seo/rules.d.ts
CHANGED
|
@@ -78,6 +78,12 @@ export declare function filterSitemapEntries({ entries, query }: {
|
|
|
78
78
|
entries: readonly SitemapUrlEntry[];
|
|
79
79
|
query: string;
|
|
80
80
|
}, _optional?: Record<string, never>): SitemapUrlEntry[];
|
|
81
|
+
/** A library selection is displayed as a preview while its persisted reference stays intact.
|
|
82
|
+
* URLs and incomplete input remain editable. @complexity O(n) in value length; no I/O.
|
|
83
|
+
*/
|
|
84
|
+
export declare function isStoredMediaRef({ value }: {
|
|
85
|
+
value: string;
|
|
86
|
+
}, _optional?: Record<string, never>): boolean;
|
|
81
87
|
/** Builds the `{slug}:public` reference `MediaRefField` writes into the field on selection —
|
|
82
88
|
* prefers the asset's readable slug over its id (readable-slugs S5b, 2026-09-23), matching
|
|
83
89
|
* `resolveSeoImageRef`'s own id-or-slug lookup server-side (`findMediaByIdOrSlug`,
|
package/dist/seo/rules.js
CHANGED
|
@@ -117,6 +117,14 @@ function parseMediaRefAssetId(ref) {
|
|
|
117
117
|
return null;
|
|
118
118
|
return ref.slice(0, separatorIndex);
|
|
119
119
|
}
|
|
120
|
+
/** A library selection is displayed as a preview while its persisted reference stays intact.
|
|
121
|
+
* URLs and incomplete input remain editable. @complexity O(n) in value length; no I/O.
|
|
122
|
+
*/
|
|
123
|
+
export function isStoredMediaRef({ value }, _optional = {}) {
|
|
124
|
+
const trimmed = typeof value === "string" ? value.trim() : "";
|
|
125
|
+
// A URL typed one character at a time reaches "https:/" before it reaches "https://".
|
|
126
|
+
return !trimmed.includes(":/") && parseMediaRefAssetId(trimmed) !== null;
|
|
127
|
+
}
|
|
120
128
|
/** Builds the `{slug}:public` reference `MediaRefField` writes into the field on selection —
|
|
121
129
|
* prefers the asset's readable slug over its id (readable-slugs S5b, 2026-09-23), matching
|
|
122
130
|
* `resolveSeoImageRef`'s own id-or-slug lookup server-side (`findMediaByIdOrSlug`,
|
package/dist/seo/seo.module.d.ts
CHANGED
|
@@ -31,10 +31,10 @@ export declare const seoModule: {
|
|
|
31
31
|
readonly Nofollow: "Nofollow";
|
|
32
32
|
readonly "OG title": "OG title";
|
|
33
33
|
readonly "OG description": "OG description";
|
|
34
|
-
readonly "OG image
|
|
34
|
+
readonly "OG image": "OG image";
|
|
35
35
|
readonly "Twitter title": "Twitter title";
|
|
36
36
|
readonly "Twitter description": "Twitter description";
|
|
37
|
-
readonly "Twitter image
|
|
37
|
+
readonly "Twitter image": "Twitter image";
|
|
38
38
|
readonly "Saving\u2026": "Saving…";
|
|
39
39
|
readonly "Save overrides": "Save overrides";
|
|
40
40
|
readonly "Per-entry SEO": "Per-entry SEO";
|
|
@@ -42,7 +42,7 @@ export declare const seoModule: {
|
|
|
42
42
|
readonly "Title template (must contain %s)": "Title template (must contain %s)";
|
|
43
43
|
readonly "Default meta description": "Default meta description";
|
|
44
44
|
readonly "Social sharing": "Social sharing";
|
|
45
|
-
readonly "Default Open Graph / Twitter image
|
|
45
|
+
readonly "Default Open Graph / Twitter image": "Default Open Graph / Twitter image";
|
|
46
46
|
readonly "Twitter @site handle": "Twitter @site handle";
|
|
47
47
|
readonly Crawling: "Crawling";
|
|
48
48
|
readonly "Default noindex": "Default noindex";
|
|
@@ -50,7 +50,7 @@ export declare const seoModule: {
|
|
|
50
50
|
readonly "Sitemap enabled": "Sitemap enabled";
|
|
51
51
|
readonly "Save settings": "Save settings";
|
|
52
52
|
readonly "Cached sitemap": "Cached sitemap";
|
|
53
|
-
readonly "
|
|
53
|
+
readonly "Rebuild the sitemap now to include the latest published content.": "Rebuild the sitemap now to include the latest published content.";
|
|
54
54
|
readonly "Working\u2026": "Working…";
|
|
55
55
|
readonly "Regenerate sitemap": "Regenerate sitemap";
|
|
56
56
|
readonly "View sitemap": "View sitemap";
|
|
@@ -83,6 +83,7 @@ export declare const seoModule: {
|
|
|
83
83
|
readonly "failed to regenerate sitemap": "failed to regenerate sitemap";
|
|
84
84
|
readonly "failed to load the sitemap": "failed to load the sitemap";
|
|
85
85
|
readonly "Could not load the sitemap (HTTP {status}).": "Could not load the sitemap (HTTP {status}).";
|
|
86
|
+
readonly "Selected image": "Selected image";
|
|
86
87
|
};
|
|
87
88
|
readonly pages: {
|
|
88
89
|
readonly settings: {
|
|
@@ -19,7 +19,7 @@ export function WidgetRegionEditor(props) {
|
|
|
19
19
|
return (_jsxs("div", { className: "page", children: [_jsxs("div", { className: "page-header page-header-split", ...agentHandle({ handle: "widget-region-editor-header" }, {
|
|
20
20
|
role: "region",
|
|
21
21
|
label: "Widget region editor header — the back link, the region's title, and the Save button",
|
|
22
|
-
}), children: [_jsx("div", { className: "page-header-lead", children: _jsxs("a", { className: "btn-secondary", href: "/admin/widgets/regions", "aria-label": `${t("Back")}: ${t("Regions")}`, ...agentHandle({ handle: "widget-region-editor-back" }, { role: "link", label: "Back to Widget Regions" }), children: ["\u2190 ", t("Back")] }) }), _jsxs("div", { className: "page-header-text", children: [_jsx("p", { className: "page-kicker", children: t("Content") }), _jsxs("h1", { className: "page-title", children: [t("Region:"), " ", regionKey] }), _jsx("p", { className: "page-description", children: t("Manage which widgets appear in this region and their order.") })] }), _jsx(WidgetRegionEditorHeaderActions, { message: message, error: error, saving: saving, onSave: save, t: t })] }), _jsxs("div", { className: "widget-region-placements", children: [placements.length === 0 ? (_jsx("div", { className: "card", children: _jsx("div", { className: "empty-state", children: _jsx("p", { children: t("No widgets placed in this region yet.") }) }) })) : (placements.map((placement, i) => (_jsx("div", { className: "menu-item-row", children: _jsxs("div", { className: "menu-item-fields", children: [placement.broken ? (_jsx("span", { className: "widget-embed-node--broken", children: t("⚠ Broken reference") })) : (_jsxs("span", { children: [_jsx("strong", { children: placement.widgetTitle }), placement.widgetType ? (_jsxs(_Fragment, { children: [" ", _jsxs("span", { className: "muted-cell", children: ["(", widgetTypeLabel({ widgetType: placement.widgetType, types: widgetTypes }, { t }), ")"] })] })) : null] })), _jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: placement.enabled, onChange: () => toggleEnabled(placement.placementId), ...agentHandle({ handle: `${placementHandles[i]}-enabled` }, { role: "field", label: `Whether "${placement.widgetTitle}" is enabled` }) }), t("Enabled")] }), _jsx("button", { className: "tb-btn", onClick: () => moveAt(i, -1), title: t("Move up"), "aria-label": t("Move up"), ...agentHandle({ handle: `${placementHandles[i]}-move-up` }, { role: "button", label: `Move "${placement.widgetTitle}" up` }), children: "\u2191" }), _jsx("button", { className: "tb-btn", onClick: () => moveAt(i, 1), title: t("Move down"), "aria-label": t("Move down"), ...agentHandle({ handle: `${placementHandles[i]}-move-down` }, { role: "button", label: `Move "${placement.widgetTitle}" down` }), children: "\u2193" }), _jsx("button", { className: "tb-btn", onClick: () => removeAt(placement.placementId), title: t("Remove"), "aria-label": t("Remove"), ...agentHandle({ handle: `${placementHandles[i]}-remove` }, { role: "button", label: `Remove "${placement.widgetTitle}" from this region` }), children: "\u2715" })] }) }, placement.placementId)))), _jsx(WidgetAddControl, { triggerLabel: t("+ Add widget"), onResolved: (widgetInstanceId) => addPlacement(widgetInstanceId), agentHandle: "widget-region-add" })] })] }));
|
|
22
|
+
}), children: [_jsx("div", { className: "page-header-lead", children: _jsxs("a", { className: "btn-secondary", href: "/admin/widgets/regions", "aria-label": `${t("Back")}: ${t("Regions")}`, ...agentHandle({ handle: "widget-region-editor-back" }, { role: "link", label: "Back to Widget Regions" }), children: ["\u2190 ", t("Back")] }) }), _jsxs("div", { className: "page-header-text", children: [_jsx("p", { className: "page-kicker", children: t("Content") }), _jsxs("h1", { className: "page-title", children: [t("Region:"), " ", regionKey] }), _jsx("p", { className: "page-description", children: t("Manage which widgets appear in this region and their order.") })] }), _jsx(WidgetRegionEditorHeaderActions, { message: message, error: error, saving: saving, onSave: save, t: t })] }), _jsxs("div", { className: "jini-widget-region-placements", children: [placements.length === 0 ? (_jsx("div", { className: "card", children: _jsx("div", { className: "empty-state", children: _jsx("p", { children: t("No widgets placed in this region yet.") }) }) })) : (placements.map((placement, i) => (_jsx("div", { className: "menu-item-row", children: _jsxs("div", { className: "menu-item-fields", children: [placement.broken ? (_jsx("span", { className: "widget-embed-node--broken", children: t("⚠ Broken reference") })) : (_jsxs("span", { children: [_jsx("strong", { children: placement.widgetTitle }), placement.widgetType ? (_jsxs(_Fragment, { children: [" ", _jsxs("span", { className: "muted-cell", children: ["(", widgetTypeLabel({ widgetType: placement.widgetType, types: widgetTypes }, { t }), ")"] })] })) : null] })), _jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: placement.enabled, onChange: () => toggleEnabled(placement.placementId), ...agentHandle({ handle: `${placementHandles[i]}-enabled` }, { role: "field", label: `Whether "${placement.widgetTitle}" is enabled` }) }), t("Enabled")] }), _jsx("button", { className: "tb-btn", onClick: () => moveAt(i, -1), title: t("Move up"), "aria-label": t("Move up"), ...agentHandle({ handle: `${placementHandles[i]}-move-up` }, { role: "button", label: `Move "${placement.widgetTitle}" up` }), children: "\u2191" }), _jsx("button", { className: "tb-btn", onClick: () => moveAt(i, 1), title: t("Move down"), "aria-label": t("Move down"), ...agentHandle({ handle: `${placementHandles[i]}-move-down` }, { role: "button", label: `Move "${placement.widgetTitle}" down` }), children: "\u2193" }), _jsx("button", { className: "tb-btn", onClick: () => removeAt(placement.placementId), title: t("Remove"), "aria-label": t("Remove"), ...agentHandle({ handle: `${placementHandles[i]}-remove` }, { role: "button", label: `Remove "${placement.widgetTitle}" from this region` }), children: "\u2715" })] }) }, placement.placementId)))), _jsx(WidgetAddControl, { triggerLabel: t("+ Add widget"), onResolved: (widgetInstanceId) => addPlacement(widgetInstanceId), agentHandle: "widget-region-add" })] })] }));
|
|
23
23
|
}
|
|
24
24
|
export default WidgetRegionEditor;
|
|
25
25
|
//# sourceMappingURL=WidgetRegionEditor.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jini-ai/admin",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "Composable admin surface for Jini-hosted products: framework-free contracts, panel registry, and route model in `/core`; React shell and panels in `/react`. Import only the layers you need.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -470,12 +470,12 @@
|
|
|
470
470
|
"react": "^19.2.0",
|
|
471
471
|
"react-dom": "^19.2.0",
|
|
472
472
|
"@testing-library/user-event": "^14.6.1",
|
|
473
|
-
"@jini-ai/ui": "^0.4.5",
|
|
474
473
|
"@jini-ai/agentic": "^0.4.3",
|
|
475
|
-
"@jini-ai/
|
|
474
|
+
"@jini-ai/ui": "^0.4.6",
|
|
476
475
|
"@jini-ai/ui-kit": "^0.1.2",
|
|
477
|
-
"@jini-ai/
|
|
478
|
-
"@jini-ai/
|
|
476
|
+
"@jini-ai/core": "^0.4.2",
|
|
477
|
+
"@jini-ai/cms": "^0.5.4",
|
|
478
|
+
"@jini-ai/agent-plugins": "^0.4.2"
|
|
479
479
|
},
|
|
480
480
|
"typesVersions": {
|
|
481
481
|
"*": {
|