@jini-ai/admin 0.4.11 → 0.4.13
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 +9 -0
- package/dist/agent-plugins/controllers/agent-plugin-files.controller.js +14 -9
- package/dist/agent-plugins/controllers/agent-plugins.controller.js +12 -9
- package/dist/agent-plugins/package-file-tree.js +54 -51
- package/dist/agent-plugins/react/components/AgentPluginRow.d.ts +1 -1
- package/dist/agent-plugins/react/components/AgentPluginRow.js +9 -2
- package/dist/agent-plugins/react/hooks/AgentPluginInspector.hooks.d.ts +5 -5
- package/dist/agent-plugins/react/hooks/AgentPluginInspector.hooks.js +14 -5
- package/dist/agent-plugins/react/hooks/AgentPluginPanel.hooks.d.ts +15 -13
- package/dist/agent-plugins/react/hooks/AgentPluginPanel.hooks.js +54 -40
- package/dist/agent-plugins/react/hooks/AgentPluginsPage.hooks.d.ts +13 -13
- package/dist/agent-plugins/react/hooks/AgentPluginsPage.hooks.js +23 -11
- package/dist/browser/navigation.js +44 -27
- package/dist/comments/adapters/memory.js +30 -12
- package/dist/comments/conformance/comments-api.conformance.js +55 -33
- package/dist/comments/controllers/comment-queue.controller.js +13 -4
- package/dist/comments/react/hooks/use-comment-queue.hooks.d.ts +17 -2
- package/dist/comments/react/hooks/use-comment-queue.hooks.js +79 -30
- package/dist/comments/react/hooks/use-comment-settings.hooks.js +9 -5
- package/dist/comments/react/index.d.ts +2 -1
- package/dist/comments/react/index.js +1 -1
- package/dist/consent/react/ConsentFields.d.ts +35 -0
- package/dist/consent/react/ConsentFields.js +74 -0
- package/dist/consent/react/ConsentPreviewFrame.d.ts +14 -0
- package/dist/consent/react/ConsentPreviewFrame.js +14 -0
- package/dist/consent/react/ConsentSettingsPanel.d.ts +27 -4
- package/dist/consent/react/ConsentSettingsPanel.js +6 -17
- package/dist/consent/react/index.d.ts +6 -1
- package/dist/consent/react/index.js +2 -0
- package/dist/consent/react/settings-panel.hooks.d.ts +28 -1
- package/dist/consent/react/settings-panel.hooks.js +26 -0
- package/dist/core/entities/rules.js +126 -81
- package/dist/core/manifest/rules.js +10 -10
- package/dist/core/module/create-admin.js +195 -117
- package/dist/core/ports/redirects.d.ts +8 -6
- package/dist/core/ports/redirects.js +8 -6
- package/dist/core/ports/seo.d.ts +20 -0
- package/dist/core/routing/rules.js +9 -4
- package/dist/core/transport/http.js +37 -20
- package/dist/forms/adapters/memory.js +22 -11
- package/dist/forms/conformance/forms-api.conformance.d.ts +6 -4
- package/dist/forms/conformance/forms-api.conformance.js +42 -46
- package/dist/forms/field-settings.js +150 -115
- package/dist/forms/html-rules.js +45 -28
- package/dist/forms/react/components/FieldSettingsRows.d.ts +22 -0
- package/dist/forms/react/components/FieldSettingsRows.js +29 -0
- package/dist/forms/react/components/FieldSettingsSection.d.ts +1 -1
- package/dist/forms/react/components/FieldSettingsSection.js +5 -6
- package/dist/forms/react/components/FormEditorFieldsBody.d.ts +1 -1
- package/dist/forms/react/components/FormEditorFieldsBody.js +4 -15
- package/dist/forms/react/components/FormEditorFieldsBodyParts.d.ts +18 -0
- package/dist/forms/react/components/FormEditorFieldsBodyParts.js +32 -0
- package/dist/forms/react/components/FormSubmissions.d.ts +1 -1
- package/dist/forms/react/components/FormSubmissions.js +4 -20
- package/dist/forms/react/components/FormSubmissionsTable.d.ts +19 -0
- package/dist/forms/react/components/FormSubmissionsTable.js +22 -0
- package/dist/media/adapters/memory.js +31 -26
- package/dist/media/controllers/edit-media.controller.js +2 -1
- package/dist/media/controllers/library.controller.js +48 -35
- package/dist/media/controllers/providers.controller.js +2 -1
- package/dist/media/html-attributes.js +29 -15
- package/dist/media/index.d.ts +1 -1
- package/dist/media/index.js +1 -1
- package/dist/media/react/components/MediaIcons.d.ts +0 -3
- package/dist/media/react/components/MediaIcons.js +0 -9
- package/dist/media/react/components/MediaLightbox.d.ts +2 -1
- package/dist/media/react/components/MediaLightbox.js +11 -5
- package/dist/media/react/hooks/LibraryTab.hooks.d.ts +55 -45
- package/dist/media/react/hooks/LibraryTab.hooks.js +107 -70
- package/dist/media/react/hooks/MediaCard.hooks.d.ts +27 -31
- package/dist/media/react/hooks/MediaCard.hooks.js +52 -26
- package/dist/media/react/hooks/MediaGrid.hooks.d.ts +1 -1
- package/dist/media/react/hooks/MediaLightbox.hooks.d.ts +10 -14
- package/dist/media/react/hooks/MediaLightbox.hooks.js +9 -56
- package/dist/media/react/hooks/MediaPage.hooks.d.ts +6 -0
- package/dist/media/react/hooks/MediaPage.hooks.js +29 -12
- package/dist/media/react/hooks/ProvidersTab.hooks.d.ts +33 -33
- package/dist/media/react/hooks/ProvidersTab.hooks.js +119 -79
- package/dist/media/react/hooks/UploadButton.hooks.d.ts +7 -5
- package/dist/media/react/hooks/UploadButton.hooks.js +36 -24
- package/dist/media/react/index.js +4 -0
- package/dist/media/react/tabs/LibraryTab.d.ts +1 -1
- package/dist/media/react/tabs/LibraryTab.js +2 -2
- package/dist/media/rules.d.ts +7 -0
- package/dist/media/rules.js +25 -2
- package/dist/react/components/ConfirmDialog/ConfirmDialog.js +5 -8
- package/dist/react/components/ConfirmDialog/confirm-dialog-attrs.d.ts +20 -0
- package/dist/react/components/ConfirmDialog/confirm-dialog-attrs.js +24 -0
- package/dist/react/components/InteractiveHtmlEditor/InteractiveHtmlEditor.js +16 -7
- package/dist/react/components/RowMenu/RowMenu.js +16 -10
- package/dist/react/components/Sidebar.js +32 -26
- package/dist/react/entities/EntityEdit.js +17 -14
- package/dist/react/entities/EntityField.d.ts +5 -2
- package/dist/react/entities/EntityField.js +38 -23
- package/dist/react/entities/EntityList.js +6 -5
- package/dist/react/entities/RelationTruncationNotice.d.ts +9 -0
- package/dist/react/entities/RelationTruncationNotice.js +9 -0
- package/dist/react/entities/use-entity-edit.d.ts +4 -2
- package/dist/react/entities/use-entity-edit.js +52 -33
- package/dist/react/shell/AdminShell.js +40 -23
- package/dist/react/shell/model.d.ts +4 -1
- package/dist/react/shell/model.js +3 -2
- package/dist/react/shell/use-admin-shell-session.js +25 -26
- package/dist/redirects/adapters/memory.js +9 -2
- package/dist/redirects/conformance/redirects-api.conformance.js +66 -36
- package/dist/redirects/react/hooks/use-import-redirects-form.hooks.d.ts +1 -1
- package/dist/redirects/react/hooks/use-import-redirects-form.hooks.js +10 -3
- package/dist/redirects/react/hooks/wired.hooks.d.ts +1 -1
- package/dist/security/adapters/http.js +25 -10
- package/dist/security/adapters/memory.js +19 -5
- package/dist/security/controllers/access-tokens.controller.js +27 -26
- package/dist/security/controllers/other-credentials.controller.js +4 -2
- package/dist/security/controllers/root-key.controller.js +17 -9
- package/dist/security/react/hooks/AccessTokensTab.hooks.d.ts +40 -38
- package/dist/security/react/hooks/AccessTokensTab.hooks.js +36 -9
- package/dist/security/react/hooks/RootKeyTab.hooks.d.ts +14 -14
- package/dist/security/react/hooks/RootKeyTab.hooks.js +12 -3
- package/dist/security/react/tabs/RootKeyTab.d.ts +1 -1
- package/dist/security/react/tabs/RootKeyTab.js +12 -3
- package/dist/security/rules.js +45 -25
- package/dist/seo/adapters/http.js +5 -0
- package/dist/seo/controllers/seo.controller.js +4 -2
- package/dist/seo/messages.en.d.ts +15 -10
- package/dist/seo/messages.en.js +15 -10
- package/dist/seo/models.d.ts +11 -0
- package/dist/seo/react/components/MediaRefField.d.ts +17 -5
- package/dist/seo/react/components/MediaRefField.js +31 -4
- package/dist/seo/react/hooks/MediaRefField.hooks.js +12 -2
- package/dist/seo/react/hooks/SeoPage.hooks.d.ts +1 -0
- package/dist/seo/react/hooks/SeoPage.hooks.js +2 -2
- package/dist/seo/react/hooks/SeoQuery.hooks.d.ts +1 -0
- package/dist/seo/react/hooks/SeoQuery.hooks.js +1 -0
- package/dist/seo/react/hooks/use-entry-picker.hooks.js +7 -2
- package/dist/seo/react/hooks/use-seo-entry-panel.hooks.js +13 -2
- package/dist/seo/react/hooks/use-share-image-warning.hooks.d.ts +4 -1
- package/dist/seo/react/hooks/use-share-image-warning.hooks.js +48 -33
- package/dist/seo/react/index.d.ts +45 -30
- package/dist/seo/react/pages/SeoPage.d.ts +6 -4
- package/dist/seo/react/pages/SeoPage.js +51 -29
- package/dist/seo/rules.d.ts +91 -0
- package/dist/seo/rules.js +161 -6
- package/dist/seo/seo.module.d.ts +15 -10
- package/dist/source-control/conformance/source-control-api.conformance.js +68 -38
- package/dist/source-control/controllers/source-control.controller.js +20 -8
- package/dist/source-control/react/hooks/ProvidersTab.hooks.d.ts +2 -1
- package/dist/source-control/react/hooks/ProvidersTab.hooks.js +18 -11
- package/dist/widgets/conformance/widgets-api.conformance.js +15 -1
- package/dist/widgets/controllers/instance-editor.controller.js +33 -24
- package/dist/widgets/controllers/library.controller.js +18 -12
- package/dist/widgets/controllers/region-editor.controller.js +6 -5
- package/dist/widgets/react/hooks/page-views.hooks.d.ts +1 -0
- package/dist/widgets/react/hooks/page-views.hooks.js +3 -2
- package/dist/widgets/react/hooks/use-widget-regions.hooks.d.ts +1 -1
- package/dist/widgets/react/hooks/use-widget-regions.hooks.js +9 -3
- package/dist/widgets/react/hooks/use-widgets-library.hooks.js +9 -2
- package/dist/widgets/react/pages/WidgetInstanceEditor.d.ts +1 -1
- package/dist/widgets/react/pages/WidgetInstanceEditor.js +7 -8
- package/dist/widgets/rules.d.ts +14 -2
- package/dist/widgets/rules.js +9 -6
- package/package.json +19 -10
- package/src/themes/default/admin.css +51 -0
- package/src/themes/default/chat.css +21 -0
- package/src/themes/default/manifest.json +29 -0
- package/dist/media/styles.d.ts +0 -4
- package/dist/media/styles.js +0 -5
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { createControllerStore } from '../../core/module/controller-store.js';
|
|
2
2
|
import { ROOT_KEY_PERMISSION, START_FRESH_CONFIRMATION, isRootKeyLocked } from '../rules.js';
|
|
3
|
+
/** Receivers named in a result need their new signing secret; the message tells the operator which ones. */
|
|
4
|
+
function resultMessage({ result, response }, _optional = {}) {
|
|
5
|
+
const hooks = response.affectedWebhooks;
|
|
6
|
+
return hooks?.length ? `${result} Update receivers: ${hooks.map(w => `${w.label} (${w.targetUrl})`).join(', ')}` : result;
|
|
7
|
+
}
|
|
3
8
|
export function createRootKeyController({ api, permissions = [] }, _optional = {}) {
|
|
4
9
|
const granted = permissions.includes(ROOT_KEY_PERMISSION);
|
|
5
10
|
const store = createControllerStore({ initial: { status: null, preview: null, token: '', confirm: '', loading: false, saving: false, error: null, result: null, confirming: false } });
|
|
6
11
|
let generation = 0;
|
|
7
12
|
const allowed = () => granted && !store.signal.aborted;
|
|
13
|
+
const canPreview = () => { const state = store.getSnapshot(); return allowed() && isRootKeyLocked({ status: state.status }) && !state.saving && !state.loading; };
|
|
8
14
|
async function load(_required, _optional = {}) {
|
|
9
15
|
if (!allowed())
|
|
10
16
|
return;
|
|
@@ -28,19 +34,20 @@ export function createRootKeyController({ api, permissions = [] }, _optional = {
|
|
|
28
34
|
if (!allowed() || store.getSnapshot().saving)
|
|
29
35
|
return false;
|
|
30
36
|
store.set({ patch: { saving: true, error: null } });
|
|
37
|
+
let ok = false;
|
|
31
38
|
try {
|
|
32
39
|
const response = await run();
|
|
33
|
-
store.set({ patch: { token: '', confirm: '', preview: null, confirming: false, result:
|
|
40
|
+
store.set({ patch: { token: '', confirm: '', preview: null, confirming: false, result: resultMessage({ result, response }) } });
|
|
34
41
|
await load({});
|
|
35
|
-
|
|
42
|
+
ok = !store.signal.aborted;
|
|
36
43
|
}
|
|
37
44
|
catch {
|
|
38
45
|
store.set({ patch: { error: 'Recovery refused. Check the host key configuration.' } });
|
|
39
|
-
return false;
|
|
40
46
|
}
|
|
41
47
|
finally {
|
|
42
48
|
store.set({ patch: { saving: false } });
|
|
43
49
|
}
|
|
50
|
+
return ok;
|
|
44
51
|
}
|
|
45
52
|
return {
|
|
46
53
|
getSnapshot: store.getSnapshot, subscribe: store.subscribe, load,
|
|
@@ -57,26 +64,27 @@ export function createRootKeyController({ api, permissions = [] }, _optional = {
|
|
|
57
64
|
},
|
|
58
65
|
importToken(_required, _optional = {}) { const token = store.getSnapshot().token.trim(); return token && isRootKeyLocked({ status: store.getSnapshot().status }) ? execute(() => api.importToken({ token }, { signal: store.signal }), 'Unlocked. Saved credentials work again.') : Promise.resolve(false); },
|
|
59
66
|
async preview(_required, _optional = {}) {
|
|
60
|
-
if (!
|
|
67
|
+
if (!canPreview())
|
|
61
68
|
return false;
|
|
62
69
|
const ticket = ++generation;
|
|
63
70
|
store.set({ patch: { loading: true, preview: null, confirm: '', error: null } });
|
|
71
|
+
let ok = false;
|
|
64
72
|
try {
|
|
65
73
|
const preview = await api.previewStartFresh({}, { signal: store.signal });
|
|
66
|
-
if (ticket
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
74
|
+
if (ticket === generation && allowed()) {
|
|
75
|
+
store.set({ patch: { preview, confirming: true } });
|
|
76
|
+
ok = true;
|
|
77
|
+
}
|
|
70
78
|
}
|
|
71
79
|
catch {
|
|
72
80
|
if (ticket === generation)
|
|
73
81
|
store.set({ patch: { error: 'Recovery preview unavailable' } });
|
|
74
|
-
return false;
|
|
75
82
|
}
|
|
76
83
|
finally {
|
|
77
84
|
if (ticket === generation)
|
|
78
85
|
store.set({ patch: { loading: false } });
|
|
79
86
|
}
|
|
87
|
+
return ok;
|
|
80
88
|
},
|
|
81
89
|
startFresh(_required, _optional = {}) { const state = store.getSnapshot(); if (!isRootKeyLocked({ status: state.status }) || !state.preview || !state.confirming || state.confirm !== START_FRESH_CONFIRMATION)
|
|
82
90
|
return Promise.resolve(false); return execute(() => api.startFresh({ confirm: state.confirm }, { signal: store.signal }), 'Done. Re-enter removed credentials.'); },
|
|
@@ -1,44 +1,11 @@
|
|
|
1
1
|
import type { TabViewProps } from '../../../core/react/bind-react.js';
|
|
2
2
|
import type { CredentialSummary, OtherCredentialSummary, CredentialCategory } from '../../models.js';
|
|
3
|
+
type Removal = {
|
|
4
|
+
row: CredentialSummary | OtherCredentialSummary;
|
|
5
|
+
type: 'access' | 'other';
|
|
6
|
+
};
|
|
3
7
|
export declare function useAccessTokensTab({ permissions }: TabViewProps, _optional?: {}): {
|
|
4
|
-
|
|
5
|
-
loading: boolean;
|
|
6
|
-
saving: boolean;
|
|
7
|
-
rows: {
|
|
8
|
-
row: CredentialSummary;
|
|
9
|
-
label: string;
|
|
10
|
-
canWrite: boolean;
|
|
11
|
-
edit(): void;
|
|
12
|
-
remove(): void;
|
|
13
|
-
makeDefault(): void;
|
|
14
|
-
}[];
|
|
15
|
-
otherRows: {
|
|
16
|
-
row: OtherCredentialSummary;
|
|
17
|
-
canWrite: boolean;
|
|
18
|
-
token: string;
|
|
19
|
-
onToken({ value }: {
|
|
20
|
-
value: string;
|
|
21
|
-
}): void;
|
|
22
|
-
replace(): void;
|
|
23
|
-
remove(): void;
|
|
24
|
-
}[];
|
|
25
|
-
providers: {
|
|
26
|
-
provider: import("../../models.js").CredentialProvider;
|
|
27
|
-
canWrite: boolean;
|
|
28
|
-
add(): void;
|
|
29
|
-
}[];
|
|
30
|
-
categories: readonly {
|
|
31
|
-
value: CredentialCategory | "all";
|
|
32
|
-
label: string;
|
|
33
|
-
}[];
|
|
34
|
-
query: string;
|
|
35
|
-
category: "all" | CredentialCategory;
|
|
36
|
-
total: number;
|
|
37
|
-
matched: number;
|
|
38
|
-
removal: {
|
|
39
|
-
row: CredentialSummary | OtherCredentialSummary;
|
|
40
|
-
type: "access" | "other";
|
|
41
|
-
} | null;
|
|
8
|
+
removal: Removal | null;
|
|
42
9
|
draft: import("../../models.js").CredentialDraft | null;
|
|
43
10
|
errors: string[];
|
|
44
11
|
canAddCustom: boolean;
|
|
@@ -90,5 +57,40 @@ export declare function useAccessTokensTab({ permissions }: TabViewProps, _optio
|
|
|
90
57
|
}, _optional?: {}): Promise<boolean>;
|
|
91
58
|
dispose(_required?: Record<string, never>, _optional?: {}): void;
|
|
92
59
|
} | null;
|
|
60
|
+
total: number;
|
|
61
|
+
matched: number;
|
|
62
|
+
rows: {
|
|
63
|
+
row: CredentialSummary;
|
|
64
|
+
label: string;
|
|
65
|
+
canWrite: boolean;
|
|
66
|
+
edit(): void;
|
|
67
|
+
remove(): void;
|
|
68
|
+
makeDefault(): void;
|
|
69
|
+
}[];
|
|
70
|
+
otherRows: {
|
|
71
|
+
row: OtherCredentialSummary;
|
|
72
|
+
canWrite: boolean;
|
|
73
|
+
token: string;
|
|
74
|
+
onToken({ value }: {
|
|
75
|
+
value: string;
|
|
76
|
+
}): void;
|
|
77
|
+
replace(): void;
|
|
78
|
+
remove(): void;
|
|
79
|
+
}[];
|
|
80
|
+
providers: {
|
|
81
|
+
provider: import("../../models.js").CredentialProvider;
|
|
82
|
+
canWrite: boolean;
|
|
83
|
+
add(): void;
|
|
84
|
+
}[];
|
|
85
|
+
categories: readonly {
|
|
86
|
+
value: CredentialCategory | "all";
|
|
87
|
+
label: string;
|
|
88
|
+
}[];
|
|
89
|
+
query: string;
|
|
90
|
+
category: "all" | CredentialCategory;
|
|
91
|
+
loading: boolean;
|
|
92
|
+
saving: boolean;
|
|
93
|
+
denied: boolean;
|
|
93
94
|
};
|
|
95
|
+
export {};
|
|
94
96
|
//# sourceMappingURL=AccessTokensTab.hooks.d.ts.map
|
|
@@ -4,6 +4,35 @@ import { createAccessTokensController } from '../../controllers/access-tokens.co
|
|
|
4
4
|
import { createOtherCredentialsController } from '../../controllers/other-credentials.controller.js';
|
|
5
5
|
import { CATEGORIES, credentialLabel, filterCredentials, filterOtherCredentials, otherWritePermission, writePermission } from '../../rules.js';
|
|
6
6
|
import { noOtherCredentials, useSecurityPorts } from './SecurityPorts.hooks.js';
|
|
7
|
+
function currentFilter({ snapshot: s }, _optional = {}) { return { query: s?.query ?? '', category: s?.category ?? 'all' }; }
|
|
8
|
+
function accessRows({ snapshot: s, controller, scope }, _optional = {}) {
|
|
9
|
+
if (!s)
|
|
10
|
+
return [];
|
|
11
|
+
const { permissions, query, category, setRemoval } = scope;
|
|
12
|
+
return filterCredentials({ rows: s.rows, providers: s.providers, query, category }).map(row => ({ row, label: credentialLabel({ row, providers: s.providers }), canWrite: permissions.includes(writePermission({ kind: row.kind })), edit() { controller?.begin({ row }); }, remove() { setRemoval({ row, type: 'access' }); }, makeDefault() { void controller?.makeDefault({ row }); } }));
|
|
13
|
+
}
|
|
14
|
+
function otherRowsView({ snapshot: o, controller, scope }, _optional = {}) {
|
|
15
|
+
if (!o)
|
|
16
|
+
return [];
|
|
17
|
+
const { permissions, query, category, setRemoval } = scope;
|
|
18
|
+
return filterOtherCredentials({ rows: o.rows, query, category }).map(row => ({ row, canWrite: permissions.includes(otherWritePermission({ store: row.store })), token: o.drafts[`${row.store}:${row.id}`] ?? '', onToken({ value }) { controller?.setDraft({ key: `${row.store}:${row.id}`, token: value }); }, replace() { void controller?.replace({ row }); }, remove() { setRemoval({ row, type: 'other' }); } }));
|
|
19
|
+
}
|
|
20
|
+
function providerOptions({ snapshot: s, controller, scope }, _optional = {}) {
|
|
21
|
+
const { permissions, query, category } = scope;
|
|
22
|
+
return (s?.providers ?? []).filter(p => (category === 'all' || p.category === category) && `${p.label} ${p.id}`.toLowerCase().includes(query.trim().toLowerCase())).map(provider => ({ provider, canWrite: permissions.includes(writePermission({ kind: provider.kind })), add() { controller?.begin({ kind: provider.kind, providerId: provider.id }); } }));
|
|
23
|
+
}
|
|
24
|
+
function tokenCounts({ access: s, other: o, query }, _optional = {}) {
|
|
25
|
+
// Count remains global across categories; the search count answers how many saved tokens this host holds.
|
|
26
|
+
const total = (s?.rows.length ?? 0) + (o?.rows.length ?? 0);
|
|
27
|
+
const matched = (s ? filterCredentials({ rows: s.rows, providers: s.providers, query, category: 'all' }).length : 0) + (o ? filterOtherCredentials({ rows: o.rows, query, category: 'all' }).length : 0);
|
|
28
|
+
return { total, matched };
|
|
29
|
+
}
|
|
30
|
+
function tabStatus({ access: s, other: o }, _optional = {}) {
|
|
31
|
+
return { loading: s?.loading ?? true, saving: (s?.saving ?? false) || (o?.saving ?? false) };
|
|
32
|
+
}
|
|
33
|
+
function errorList({ access: s, other: o }, _optional = {}) {
|
|
34
|
+
return [...(s?.error ? [s.error] : []), ...Object.values(s?.errors ?? {}), ...Object.values(o?.errors ?? {})];
|
|
35
|
+
}
|
|
7
36
|
export function useAccessTokensTab({ permissions = [] }, _optional = {}) {
|
|
8
37
|
const ports = useSecurityPorts({});
|
|
9
38
|
const key = permissions.join('\0');
|
|
@@ -12,15 +41,13 @@ export function useAccessTokensTab({ permissions = [] }, _optional = {}) {
|
|
|
12
41
|
const other = useController({ create: () => createOtherCredentialsController({ api: ports.otherCredentials ?? noOtherCredentials, permissions }), dependencies: [ports.otherCredentials, key] }, { start: ({ controller }) => { void controller.load({}); } });
|
|
13
42
|
const [removal, setRemoval] = useState(null);
|
|
14
43
|
const s = access.snapshot, o = other.snapshot;
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
return { denied: !canRead, loading: s?.loading ?? true, saving: (s?.saving ?? false) || (o?.saving ?? false), rows, otherRows, providers, categories: CATEGORIES, query, category, total, matched, removal,
|
|
23
|
-
draft: s?.draft ?? null, errors: [...(s?.error ? [s.error] : []), ...Object.values(s?.errors ?? {}), ...Object.values(o?.errors ?? {})],
|
|
44
|
+
const { query, category } = currentFilter({ snapshot: s });
|
|
45
|
+
const scope = { permissions, query, category, setRemoval };
|
|
46
|
+
const rows = accessRows({ snapshot: s, controller: access.controller, scope });
|
|
47
|
+
const otherRows = otherRowsView({ snapshot: o, controller: other.controller, scope });
|
|
48
|
+
const providers = providerOptions({ snapshot: s, controller: access.controller, scope });
|
|
49
|
+
return { denied: !canRead, ...tabStatus({ access: s, other: o }), rows, otherRows, providers, categories: CATEGORIES, query, category, ...tokenCounts({ access: s, other: o, query }), removal,
|
|
50
|
+
draft: s?.draft ?? null, errors: errorList({ access: s, other: o }),
|
|
24
51
|
canAddCustom: permissions.includes('custom-credentials.write'),
|
|
25
52
|
addCustom() { access.controller?.begin({ kind: 'custom' }); },
|
|
26
53
|
onQuery({ value }) { access.controller?.filter({ query: value }); },
|
|
@@ -1,19 +1,5 @@
|
|
|
1
1
|
import type { TabViewProps } from '../../../core/react/bind-react.js';
|
|
2
2
|
export declare function useRootKeyTab({ permissions, params }: TabViewProps, _optional?: {}): {
|
|
3
|
-
denied: boolean;
|
|
4
|
-
loading: boolean;
|
|
5
|
-
saving: boolean;
|
|
6
|
-
status: import("../../models.js").RootKeyStatus | null | undefined;
|
|
7
|
-
error: string | null | undefined;
|
|
8
|
-
result: string | null | undefined;
|
|
9
|
-
scopeNotice: string;
|
|
10
|
-
token: string;
|
|
11
|
-
confirm: string;
|
|
12
|
-
confirming: boolean;
|
|
13
|
-
preview: import("../../models.js").RootKeyPreview | null | undefined;
|
|
14
|
-
locked: boolean;
|
|
15
|
-
canGenerate: boolean;
|
|
16
|
-
canConfirm: boolean;
|
|
17
3
|
onToken({ value }: {
|
|
18
4
|
value: string;
|
|
19
5
|
}): void;
|
|
@@ -25,5 +11,19 @@ export declare function useRootKeyTab({ permissions, params }: TabViewProps, _op
|
|
|
25
11
|
previewFresh(): void;
|
|
26
12
|
cancelFresh(): void;
|
|
27
13
|
confirmFresh(): Promise<void>;
|
|
14
|
+
locked: boolean;
|
|
15
|
+
canGenerate: boolean;
|
|
16
|
+
canConfirm: boolean;
|
|
17
|
+
token: string;
|
|
18
|
+
confirm: string;
|
|
19
|
+
confirming: boolean;
|
|
20
|
+
preview: import("../../models.js").RootKeyPreview | null | undefined;
|
|
21
|
+
scopeNotice: string;
|
|
22
|
+
loading: boolean;
|
|
23
|
+
saving: boolean;
|
|
24
|
+
status: import("../../models.js").RootKeyStatus | null | undefined;
|
|
25
|
+
error: string | null | undefined;
|
|
26
|
+
result: string | null | undefined;
|
|
27
|
+
denied: boolean;
|
|
28
28
|
};
|
|
29
29
|
//# sourceMappingURL=RootKeyTab.hooks.d.ts.map
|
|
@@ -2,14 +2,23 @@ import { useController } from '../../../core/react/use-controller.js';
|
|
|
2
2
|
import { createRootKeyController } from '../../controllers/root-key.controller.js';
|
|
3
3
|
import { ROOT_KEY_PERMISSION, START_FRESH_CONFIRMATION, isRootKeyLocked } from '../../rules.js';
|
|
4
4
|
import { unavailableRootKey, useSecurityPorts } from './SecurityPorts.hooks.js';
|
|
5
|
+
/** Before the controller exists the tab reads as loading with nothing known yet. */
|
|
6
|
+
function rootKeyProgress({ snapshot: s }, _optional = {}) {
|
|
7
|
+
return { loading: s?.loading ?? true, saving: s?.saving ?? false, status: s?.status, error: s?.error, result: s?.result };
|
|
8
|
+
}
|
|
9
|
+
function rootKeyInputs({ snapshot: s }, _optional = {}) {
|
|
10
|
+
return { token: s?.token ?? '', confirm: s?.confirm ?? '', confirming: s?.confirming ?? false, preview: s?.preview };
|
|
11
|
+
}
|
|
12
|
+
function rootKeyFlags({ snapshot: s }, _optional = {}) {
|
|
13
|
+
return { locked: isRootKeyLocked({ status: s?.status ?? null }), canGenerate: s?.status?.state === 'missing' && !s.status.active, canConfirm: !!s?.preview && s.confirm === START_FRESH_CONFIRMATION };
|
|
14
|
+
}
|
|
5
15
|
export function useRootKeyTab({ permissions = [], params }, _optional = {}) {
|
|
6
16
|
const { rootKey } = useSecurityPorts({});
|
|
7
17
|
const key = permissions.join('\0');
|
|
8
18
|
const { controller, snapshot: s } = useController({ create: () => createRootKeyController({ api: rootKey ?? unavailableRootKey, permissions }), dependencies: [rootKey, key] }, { start: ({ controller }) => { void controller.load({}); } });
|
|
9
|
-
return { denied: !rootKey || !permissions.includes(ROOT_KEY_PERMISSION),
|
|
19
|
+
return { denied: !rootKey || !permissions.includes(ROOT_KEY_PERMISSION), ...rootKeyProgress({ snapshot: s }),
|
|
10
20
|
scopeNotice: typeof params.scopeNotice === 'string' ? params.scopeNotice : 'This key protects saved credentials. Keep its backup separate from the database.',
|
|
11
|
-
|
|
12
|
-
locked: isRootKeyLocked({ status: s?.status ?? null }), canGenerate: s?.status?.state === 'missing' && !s.status.active, canConfirm: !!s?.preview && s.confirm === START_FRESH_CONFIRMATION,
|
|
21
|
+
...rootKeyInputs({ snapshot: s }), ...rootKeyFlags({ snapshot: s }),
|
|
13
22
|
onToken({ value }) { controller?.setToken({ value }); }, onConfirmText({ value }) { controller?.setConfirm({ value }); },
|
|
14
23
|
generate() { void controller?.generate({}); }, unlock() { void controller?.importToken({}); }, previewFresh() { void controller?.preview({}); },
|
|
15
24
|
cancelFresh() { controller?.cancel({}); }, async confirmFresh() { if (!await controller?.startFresh({}))
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { TabViewProps } from '../../../core/react/bind-react.js';
|
|
2
|
-
export declare function RootKeyTab(props: TabViewProps
|
|
2
|
+
export declare function RootKeyTab(props: TabViewProps): import("react").JSX.Element;
|
|
3
3
|
export default RootKeyTab;
|
|
4
4
|
//# sourceMappingURL=RootKeyTab.d.ts.map
|
|
@@ -1,9 +1,18 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Button, TextField, Notice, ConfirmDialog, Spinner } from '@jini-ai/ui-kit/react';
|
|
3
3
|
import { useRootKeyTab } from '../hooks/RootKeyTab.hooks.js';
|
|
4
|
-
|
|
4
|
+
function RootKeyMessages({ vm }) {
|
|
5
|
+
return _jsxs(_Fragment, { children: [vm.loading && _jsx(Spinner, { label: "Loading root key status\u2026" }), vm.error && _jsx(Notice, { tone: "danger", children: vm.error }), vm.result && _jsx(Notice, { tone: "success", children: vm.result })] });
|
|
6
|
+
}
|
|
7
|
+
function LockedActions({ vm }) {
|
|
8
|
+
return _jsxs(_Fragment, { children: [_jsx(TextField, { label: "Old root key", type: "password", value: vm.token, onValueChange: vm.onToken, disabled: vm.saving, attrs: { 'data-agent-private': 'true' } }), _jsx(Button, { onPress: vm.unlock, pending: vm.saving, children: "Unlock saved credentials" }), _jsx(Button, { onPress: vm.previewFresh, disabled: vm.saving || vm.loading, children: "Start fresh" })] });
|
|
9
|
+
}
|
|
10
|
+
function StartFreshBody({ vm }) {
|
|
11
|
+
return _jsxs(_Fragment, { children: [_jsx("p", { children: vm.preview?.detail }), _jsxs("p", { children: [vm.preview?.removes, " credentials will be removed."] }), vm.preview?.affectedWebhooks.map(w => _jsxs("p", { children: [w.label, " \u00B7 ", w.targetUrl] }, `${w.label}:${w.targetUrl}`)), _jsx(TextField, { label: "Type START FRESH", value: vm.confirm, onValueChange: vm.onConfirmText, disabled: vm.saving })] });
|
|
12
|
+
}
|
|
13
|
+
export function RootKeyTab(props) {
|
|
5
14
|
const vm = useRootKeyTab(props);
|
|
6
|
-
return vm.denied ? _jsx(Notice, { tone: "danger", children: "Permission denied" }) : _jsxs("section", { "aria-label": "Root key", children: [_jsx(Notice, { children: vm.scopeNotice }),
|
|
15
|
+
return vm.denied ? _jsx(Notice, { tone: "danger", children: "Permission denied" }) : _jsxs("section", { "aria-label": "Root key", children: [_jsx(Notice, { children: vm.scopeNotice }), _jsx(RootKeyMessages, { vm: vm }), vm.status && _jsxs("p", { children: [vm.status.state, " \u00B7 ", vm.status.fingerprint, " \u00B7 ", vm.status.source] }), vm.canGenerate && _jsx(Button, { onPress: vm.generate, pending: vm.saving, children: "Create root key" }), vm.locked && _jsx(LockedActions, { vm: vm }), vm.confirming && _jsx(ConfirmDialog, { open: true, title: "Start fresh", body: _jsx(StartFreshBody, { vm: vm }), consequence: "Saved credentials will be removed. Update affected webhook receivers with new signing secrets.", confirmLabel: "Start fresh", onConfirm: vm.confirmFresh, onCancel: vm.cancelFresh, pending: vm.saving, agentMayConfirm: false, tone: "danger" })] });
|
|
7
16
|
}
|
|
8
17
|
export default RootKeyTab;
|
|
9
18
|
//# sourceMappingURL=RootKeyTab.js.map
|
package/dist/security/rules.js
CHANGED
|
@@ -9,8 +9,9 @@ export const OTHER_STORES = ['site-assistant', 'admin-byok', 'media-provider', '
|
|
|
9
9
|
export function writePermission({ kind }, _optional = {}) { return kind === 'custom' ? 'custom-credentials.write' : kind === 'publish' ? 'system.publish' : 'source-control.credentials.write'; }
|
|
10
10
|
export function otherWritePermission({ store }, _optional = {}) { return store === 'site-assistant' || store === 'admin-byok' ? 'admin.assistant.manage' : 'admin.integrations.manage'; }
|
|
11
11
|
export function credentialStore({ api, kind }, _optional = {}) { return kind === 'source-control' ? api.sourceControl : kind === 'publish' ? api.publish : api.custom; }
|
|
12
|
+
function defaultCategory({ kind }, _optional = {}) { return kind === 'publish' ? 'hosting' : kind === 'source-control' ? 'source-control' : 'general'; }
|
|
12
13
|
export function blankCredentialDraft({ kind, providerId = '' }, _optional = {}) {
|
|
13
|
-
return { kind, providerId, label: '', token: '', values: {}, category:
|
|
14
|
+
return { kind, providerId, label: '', token: '', values: {}, category: defaultCategory({ kind }), baseUrl: '', additionalHosts: '', username: '' };
|
|
14
15
|
}
|
|
15
16
|
/** Legacy sentinel labels are display-only: opening this page must never mutate saved data. */
|
|
16
17
|
export function credentialLabel({ row, providers }, _optional = {}) {
|
|
@@ -18,46 +19,65 @@ export function credentialLabel({ row, providers }, _optional = {}) {
|
|
|
18
19
|
return row.label;
|
|
19
20
|
return `${providers.find(p => p.kind === row.kind && p.id === row.providerId)?.label ?? row.providerId} token`;
|
|
20
21
|
}
|
|
21
|
-
export function credentialCategory({ row }, _optional = {}) { return row.category ?? (
|
|
22
|
+
export function credentialCategory({ row }, _optional = {}) { return row.category ?? defaultCategory({ kind: row.kind }); }
|
|
22
23
|
export function filterCredentials({ rows, query, category, providers }, _optional = {}) {
|
|
23
24
|
const q = query.trim().toLowerCase();
|
|
24
25
|
return rows.filter(row => (category === 'all' || credentialCategory({ row }) === category) && [credentialLabel({ row, providers }), row.providerId, row.baseUrl ?? '', row.accountLabel ?? ''].join(' ').toLowerCase().includes(q));
|
|
25
26
|
}
|
|
26
27
|
export function filterOtherCredentials({ rows, query, category }, _optional = {}) { return rows.filter(row => (category === 'all' || row.category === category) && `${row.label} ${row.store}`.toLowerCase().includes(query.trim().toLowerCase())); }
|
|
28
|
+
function findProvider({ draft, providers }, _optional = {}) { return providers.find(p => p.kind === draft.kind && p.id === draft.providerId); }
|
|
29
|
+
function isDuplicateLabel({ draft, rows }, _optional = {}) {
|
|
30
|
+
const label = draft.label.trim().toLowerCase();
|
|
31
|
+
return rows.some(r => r.kind === draft.kind && r.id !== draft.id && (draft.kind === 'custom' || r.providerId === draft.providerId) && r.label.trim().toLowerCase() === label);
|
|
32
|
+
}
|
|
33
|
+
function customDraftError({ draft }, _optional = {}) {
|
|
34
|
+
try {
|
|
35
|
+
if (!['http:', 'https:'].includes(new URL(draft.baseUrl).protocol))
|
|
36
|
+
return 'Enter an HTTP or HTTPS URL';
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return 'Enter an HTTP or HTTPS URL';
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
function providerDraftError({ draft, providers }, _optional = {}) {
|
|
44
|
+
if (draft.id && !draft.token.trim())
|
|
45
|
+
return null;
|
|
46
|
+
const provider = findProvider({ draft, providers });
|
|
47
|
+
if (!provider)
|
|
48
|
+
return 'Provider is unavailable';
|
|
49
|
+
if (provider.fields.some(f => f.name !== provider.tokenField && f.required && !draft.values[f.name]?.trim()))
|
|
50
|
+
return 'Complete all required fields';
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
27
53
|
/** A custom row is its own provider identity; its username can change without retyping its token. */
|
|
28
54
|
export function validateCredentialDraft({ draft, rows, providers }, _optional = {}) {
|
|
29
55
|
if (!draft.label.trim())
|
|
30
56
|
return 'Enter a name';
|
|
31
|
-
if (
|
|
57
|
+
if (isDuplicateLabel({ draft, rows }))
|
|
32
58
|
return 'That name is already in use';
|
|
33
59
|
if (!draft.id && !draft.token.trim())
|
|
34
60
|
return 'Enter an access token';
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (provider.fields.some(f => f.name !== provider.tokenField && f.required && !draft.values[f.name]?.trim()))
|
|
49
|
-
return 'Complete all required fields';
|
|
50
|
-
}
|
|
51
|
-
return null;
|
|
61
|
+
return draft.kind === 'custom' ? customDraftError({ draft }) : providerDraftError({ draft, providers });
|
|
62
|
+
}
|
|
63
|
+
function customConnection({ draft, token }, _optional = {}) {
|
|
64
|
+
const username = draft.username.trim();
|
|
65
|
+
return { token, ...(username ? { username } : {}) };
|
|
66
|
+
}
|
|
67
|
+
function providerConnection({ draft, token, providers }, _optional = {}) {
|
|
68
|
+
const provider = findProvider({ draft, providers });
|
|
69
|
+
const extras = provider ? provider.fields.filter(f => f.name !== provider.tokenField && draft.values[f.name]?.trim()).map(f => [f.name, draft.values[f.name].trim()]) : [];
|
|
70
|
+
return { providerId: draft.providerId, [provider?.tokenField ?? 'token']: token, ...Object.fromEntries(extras) };
|
|
71
|
+
}
|
|
72
|
+
function customPatchFields({ draft }, _optional = {}) {
|
|
73
|
+
return { category: draft.category, baseUrl: draft.baseUrl.trim(), additionalHosts: draft.additionalHosts.split(/[\n,]/).map(s => s.trim()).filter(Boolean), username: draft.username.trim() || null };
|
|
52
74
|
}
|
|
53
75
|
/** Omitting connection preserves the saved secret. Never send an empty or half-blank replacement. */
|
|
54
76
|
export function buildCredentialPatch({ draft, providers }, _optional = {}) {
|
|
55
|
-
const provider = providers.find(p => p.kind === draft.kind && p.id === draft.providerId);
|
|
56
77
|
const token = draft.token.trim();
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
return { label: draft.label.trim(), ...(connection ? { connection } : {}), ...(draft.kind === 'custom' ? { category: draft.category, baseUrl: draft.baseUrl.trim(), additionalHosts: draft.additionalHosts.split(/[\n,]/).map(s => s.trim()).filter(Boolean), username: draft.username.trim() || null } : {}) };
|
|
78
|
+
const custom = draft.kind === 'custom';
|
|
79
|
+
const connection = token ? (custom ? customConnection({ draft, token }) : providerConnection({ draft, token, providers })) : undefined;
|
|
80
|
+
return { label: draft.label.trim(), ...(connection ? { connection } : {}), ...(custom ? customPatchFields({ draft }) : {}) };
|
|
61
81
|
}
|
|
62
82
|
export function isRootKeyLocked({ status }, _optional = {}) { return status?.state === 'missing-with-data' || status?.state === 'mismatch'; }
|
|
63
83
|
//# sourceMappingURL=rules.js.map
|
|
@@ -20,6 +20,11 @@ export function createHttpSeoApi({ transport, basePath }, { sitemapTransport, im
|
|
|
20
20
|
analyzeSeoEntry: ({ entryId }) => data(`${entryPath(entryId)}/analyze`, 'GET'),
|
|
21
21
|
listSeoPosts: () => choices('posts'),
|
|
22
22
|
listSeoPages: () => choices('pages'),
|
|
23
|
+
// The admin media list route the picker already reads; projected so no media row type leaks in.
|
|
24
|
+
async listSeoMedia() {
|
|
25
|
+
const response = await transport.request({ path: `${base}/media`, method: 'GET' });
|
|
26
|
+
return response.media.map(({ id, slug, title, width, height, contentType }) => ({ id, title, ...(slug ? { slug } : {}), width: width ?? null, height: height ?? null, contentType: contentType ?? null }));
|
|
27
|
+
},
|
|
23
28
|
mediaOriginalUrl: ({ id }) => transport.url({ path: `${base}/media/${encodeURIComponent(id)}/original` }),
|
|
24
29
|
mediaPreviewUrl: ({ value }) => resolveMediaRefPreviewUrl({ value, mediaOriginalUrl: id => transport.url({ path: `${base}/media/${encodeURIComponent(id)}/original` }) }, { siteOrigin }),
|
|
25
30
|
...(imageTransport ? { async inspectShareImage({ url }, options = {}) {
|
|
@@ -27,18 +27,20 @@ export function createSeoController({ api }, { t = (key) => key } = {}) {
|
|
|
27
27
|
}
|
|
28
28
|
async function regenerateSitemap(_required = {}, _optional = {}) {
|
|
29
29
|
store.set({ patch: { saving: true, error: null, notice: null } });
|
|
30
|
+
// One exit after `finally`: returning from both try and catch left a fall-through nothing took.
|
|
31
|
+
let accepted = false;
|
|
30
32
|
try {
|
|
31
33
|
await api.regenerateSeoSitemap({});
|
|
32
34
|
store.set({ patch: { notice: t('Sitemap regeneration accepted.') } });
|
|
33
|
-
|
|
35
|
+
accepted = true;
|
|
34
36
|
}
|
|
35
37
|
catch (error) {
|
|
36
38
|
store.set({ patch: { error: describeSeoError({ error, fallback: t('failed to regenerate sitemap') }) } });
|
|
37
|
-
return false;
|
|
38
39
|
}
|
|
39
40
|
finally {
|
|
40
41
|
store.set({ patch: { saving: false } });
|
|
41
42
|
}
|
|
43
|
+
return accepted;
|
|
42
44
|
}
|
|
43
45
|
return {
|
|
44
46
|
...store, load, save, regenerateSitemap,
|
|
@@ -20,21 +20,24 @@ export declare const seoMessagesEn: {
|
|
|
20
20
|
readonly "Canonical URL": "Canonical URL";
|
|
21
21
|
readonly Noindex: "Noindex";
|
|
22
22
|
readonly Nofollow: "Nofollow";
|
|
23
|
-
readonly "
|
|
24
|
-
readonly "
|
|
25
|
-
readonly "
|
|
26
|
-
readonly "Twitter title": "Twitter title";
|
|
27
|
-
readonly "Twitter description": "Twitter description";
|
|
28
|
-
readonly "Twitter image": "Twitter image";
|
|
23
|
+
readonly "Preview title": "Preview title";
|
|
24
|
+
readonly "Preview description": "Preview description";
|
|
25
|
+
readonly "Preview image": "Preview image";
|
|
26
|
+
readonly "X/Twitter title": "X/Twitter title";
|
|
27
|
+
readonly "X/Twitter description": "X/Twitter description";
|
|
28
|
+
readonly "X/Twitter image": "X/Twitter image";
|
|
29
|
+
readonly "Customize for X/Twitter (optional)": "Customize for X/Twitter (optional)";
|
|
30
|
+
readonly "Same as the preview image": "Same as the preview image";
|
|
31
|
+
readonly "Used by Facebook, LinkedIn, Slack, Discord, iMessage, WhatsApp and X/Twitter": "Used by Facebook, LinkedIn, Slack, Discord, iMessage, WhatsApp and X/Twitter";
|
|
29
32
|
readonly "Saving\u2026": "Saving…";
|
|
30
33
|
readonly "Save overrides": "Save overrides";
|
|
31
34
|
readonly "Per-entry SEO": "Per-entry SEO";
|
|
32
35
|
readonly "Search appearance": "Search appearance";
|
|
33
36
|
readonly "Title template (must contain %s)": "Title template (must contain %s)";
|
|
34
37
|
readonly "Default meta description": "Default meta description";
|
|
35
|
-
readonly "Social
|
|
36
|
-
readonly "Default
|
|
37
|
-
readonly "Twitter @
|
|
38
|
+
readonly "Social preview": "Social preview";
|
|
39
|
+
readonly "Default preview image": "Default preview image";
|
|
40
|
+
readonly "X/Twitter @handle": "X/Twitter @handle";
|
|
38
41
|
readonly Crawling: "Crawling";
|
|
39
42
|
readonly "Default noindex": "Default noindex";
|
|
40
43
|
readonly "Default nofollow": "Default nofollow";
|
|
@@ -47,7 +50,7 @@ export declare const seoMessagesEn: {
|
|
|
47
50
|
readonly "View sitemap": "View sitemap";
|
|
48
51
|
readonly "Loading SEO settings\u2026": "Loading SEO settings…";
|
|
49
52
|
readonly Marketing: "Marketing";
|
|
50
|
-
readonly "Site-wide defaults for meta titles, descriptions,
|
|
53
|
+
readonly "Site-wide defaults for meta titles, descriptions, social previews, and robots directives.": "Site-wide defaults for meta titles, descriptions, social previews, and robots directives.";
|
|
51
54
|
readonly "Choose image": "Choose image";
|
|
52
55
|
readonly Remove: "Remove";
|
|
53
56
|
readonly "Sitemap \u00B7 {count} URLs": "Sitemap · {count} URLs";
|
|
@@ -75,5 +78,7 @@ export declare const seoMessagesEn: {
|
|
|
75
78
|
readonly "failed to load the sitemap": "failed to load the sitemap";
|
|
76
79
|
readonly "Could not load the sitemap (HTTP {status}).": "Could not load the sitemap (HTTP {status}).";
|
|
77
80
|
readonly "Selected image": "Selected image";
|
|
81
|
+
readonly "Using site default: {name}": "Using site default: {name}";
|
|
82
|
+
readonly "Using image from page content: {name}": "Using image from page content: {name}";
|
|
78
83
|
};
|
|
79
84
|
//# sourceMappingURL=messages.en.d.ts.map
|
package/dist/seo/messages.en.js
CHANGED
|
@@ -20,21 +20,24 @@ export const seoMessagesEn = {
|
|
|
20
20
|
"Canonical URL": "Canonical URL",
|
|
21
21
|
"Noindex": "Noindex",
|
|
22
22
|
"Nofollow": "Nofollow",
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"Twitter title": "Twitter title",
|
|
27
|
-
"Twitter description": "Twitter description",
|
|
28
|
-
"Twitter image": "Twitter image",
|
|
23
|
+
"Preview title": "Preview title",
|
|
24
|
+
"Preview description": "Preview description",
|
|
25
|
+
"Preview image": "Preview image",
|
|
26
|
+
"X/Twitter title": "X/Twitter title",
|
|
27
|
+
"X/Twitter description": "X/Twitter description",
|
|
28
|
+
"X/Twitter image": "X/Twitter image",
|
|
29
|
+
"Customize for X/Twitter (optional)": "Customize for X/Twitter (optional)",
|
|
30
|
+
"Same as the preview image": "Same as the preview image",
|
|
31
|
+
"Used by Facebook, LinkedIn, Slack, Discord, iMessage, WhatsApp and X/Twitter": "Used by Facebook, LinkedIn, Slack, Discord, iMessage, WhatsApp and X/Twitter",
|
|
29
32
|
"Saving…": "Saving…",
|
|
30
33
|
"Save overrides": "Save overrides",
|
|
31
34
|
"Per-entry SEO": "Per-entry SEO",
|
|
32
35
|
"Search appearance": "Search appearance",
|
|
33
36
|
"Title template (must contain %s)": "Title template (must contain %s)",
|
|
34
37
|
"Default meta description": "Default meta description",
|
|
35
|
-
"Social
|
|
36
|
-
"Default
|
|
37
|
-
"Twitter @
|
|
38
|
+
"Social preview": "Social preview",
|
|
39
|
+
"Default preview image": "Default preview image",
|
|
40
|
+
"X/Twitter @handle": "X/Twitter @handle",
|
|
38
41
|
"Crawling": "Crawling",
|
|
39
42
|
"Default noindex": "Default noindex",
|
|
40
43
|
"Default nofollow": "Default nofollow",
|
|
@@ -47,7 +50,7 @@ export const seoMessagesEn = {
|
|
|
47
50
|
"View sitemap": "View sitemap",
|
|
48
51
|
"Loading SEO settings…": "Loading SEO settings…",
|
|
49
52
|
"Marketing": "Marketing",
|
|
50
|
-
"Site-wide defaults for meta titles, descriptions,
|
|
53
|
+
"Site-wide defaults for meta titles, descriptions, social previews, and robots directives.": "Site-wide defaults for meta titles, descriptions, social previews, and robots directives.",
|
|
51
54
|
"Choose image": "Choose image",
|
|
52
55
|
"Remove": "Remove",
|
|
53
56
|
"Sitemap · {count} URLs": "Sitemap · {count} URLs",
|
|
@@ -75,5 +78,7 @@ export const seoMessagesEn = {
|
|
|
75
78
|
"failed to load the sitemap": "failed to load the sitemap",
|
|
76
79
|
"Could not load the sitemap (HTTP {status}).": "Could not load the sitemap (HTTP {status}).",
|
|
77
80
|
"Selected image": "Selected image",
|
|
81
|
+
"Using site default: {name}": "Using site default: {name}",
|
|
82
|
+
"Using image from page content: {name}": "Using image from page content: {name}",
|
|
78
83
|
};
|
|
79
84
|
//# sourceMappingURL=messages.en.js.map
|
package/dist/seo/models.d.ts
CHANGED
|
@@ -75,6 +75,8 @@ export interface SeoController {
|
|
|
75
75
|
}
|
|
76
76
|
export interface EntryPickerController {
|
|
77
77
|
entries: readonly AdminPost[] | null;
|
|
78
|
+
/** Searchable-picker rows (grouped Pages/Posts); null while entries load. */
|
|
79
|
+
options?: import('./rules.js').SeoEntryPickerOption[] | null;
|
|
78
80
|
error: string | null;
|
|
79
81
|
}
|
|
80
82
|
export interface SeoEntryPanelOptions {
|
|
@@ -98,6 +100,9 @@ export interface SeoEntryPanelController {
|
|
|
98
100
|
* `null` for each one they emptied. An untouched field is ABSENT, never `null`: "unchanged" must
|
|
99
101
|
* never become "clear". */
|
|
100
102
|
touched: SeoEntryOverridesPatch;
|
|
103
|
+
/** The social preview image comes from content or the site default (`ogImageInherited`, rules.ts). Optional
|
|
104
|
+
* for injected legacy views. */
|
|
105
|
+
ogImageInherited?: false | 'default' | 'content';
|
|
101
106
|
}
|
|
102
107
|
export interface SeoEntrySectionController {
|
|
103
108
|
entryId: string;
|
|
@@ -153,6 +158,12 @@ export interface MediaRefFieldController {
|
|
|
153
158
|
/** The field's current value resolved to a thumbnail `<img src>`, or `null` when the value is
|
|
154
159
|
* empty or unparseable — see `resolveMediaRefPreviewUrl`'s own doc. */
|
|
155
160
|
previewUrl: string | null;
|
|
161
|
+
/** The chosen file's display name and pixel size (`describeMediaRef`), or null when empty.
|
|
162
|
+
* Optional for injected legacy views. */
|
|
163
|
+
file?: {
|
|
164
|
+
name: string;
|
|
165
|
+
size: string | null;
|
|
166
|
+
} | null;
|
|
156
167
|
/** Shared diagnostics for defaults, OG and Twitter fields; optional for injected legacy views. */
|
|
157
168
|
warning?: string | null;
|
|
158
169
|
onPreviewLoad?: (event: {
|