@momentumcms/admin 0.5.3 → 0.5.5
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/fesm2022/{momentumcms-admin-array-field.component-ChjP5zK9.mjs → momentumcms-admin-array-field.component-Co0_3wwq.mjs} +2 -2
- package/fesm2022/{momentumcms-admin-array-field.component-ChjP5zK9.mjs.map → momentumcms-admin-array-field.component-Co0_3wwq.mjs.map} +1 -1
- package/fesm2022/{momentumcms-admin-blocks-field.component-Dl0FxGnf.mjs → momentumcms-admin-blocks-field.component-Dr0N35Nz.mjs} +2 -2
- package/fesm2022/{momentumcms-admin-blocks-field.component-Dl0FxGnf.mjs.map → momentumcms-admin-blocks-field.component-Dr0N35Nz.mjs.map} +1 -1
- package/fesm2022/{momentumcms-admin-collapsible-field.component-CSLOT0Dp.mjs → momentumcms-admin-collapsible-field.component-C1GobKo0.mjs} +2 -2
- package/fesm2022/{momentumcms-admin-collapsible-field.component-CSLOT0Dp.mjs.map → momentumcms-admin-collapsible-field.component-C1GobKo0.mjs.map} +1 -1
- package/fesm2022/{momentumcms-admin-global-edit.page-DHr7Icl6.mjs → momentumcms-admin-global-edit.page-C-e4_QKx.mjs} +2 -2
- package/fesm2022/{momentumcms-admin-global-edit.page-DHr7Icl6.mjs.map → momentumcms-admin-global-edit.page-C-e4_QKx.mjs.map} +1 -1
- package/fesm2022/{momentumcms-admin-group-field.component-Bofhumd5.mjs → momentumcms-admin-group-field.component-NdX_GSNQ.mjs} +2 -2
- package/fesm2022/{momentumcms-admin-group-field.component-Bofhumd5.mjs.map → momentumcms-admin-group-field.component-NdX_GSNQ.mjs.map} +1 -1
- package/fesm2022/{momentumcms-admin-momentumcms-admin-D8WvqCxe.mjs → momentumcms-admin-momentumcms-admin-D56RXzt0.mjs} +1132 -457
- package/fesm2022/momentumcms-admin-momentumcms-admin-D56RXzt0.mjs.map +1 -0
- package/fesm2022/{momentumcms-admin-relationship-field.component-DSZhc5MP.mjs → momentumcms-admin-relationship-field.component-Cy9BsTBY.mjs} +2 -2
- package/fesm2022/{momentumcms-admin-relationship-field.component-DSZhc5MP.mjs.map → momentumcms-admin-relationship-field.component-Cy9BsTBY.mjs.map} +1 -1
- package/fesm2022/{momentumcms-admin-rich-text-field.component-BIUu6NXa.mjs → momentumcms-admin-rich-text-field.component-s47cVU85.mjs} +2 -2
- package/fesm2022/{momentumcms-admin-rich-text-field.component-BIUu6NXa.mjs.map → momentumcms-admin-rich-text-field.component-s47cVU85.mjs.map} +1 -1
- package/fesm2022/{momentumcms-admin-row-field.component-DBzqzooT.mjs → momentumcms-admin-row-field.component-VJvSx9YM.mjs} +2 -2
- package/fesm2022/{momentumcms-admin-row-field.component-DBzqzooT.mjs.map → momentumcms-admin-row-field.component-VJvSx9YM.mjs.map} +1 -1
- package/fesm2022/{momentumcms-admin-tabs-field.component-BsnCWC5J.mjs → momentumcms-admin-tabs-field.component-C_iReaEt.mjs} +2 -2
- package/fesm2022/{momentumcms-admin-tabs-field.component-BsnCWC5J.mjs.map → momentumcms-admin-tabs-field.component-C_iReaEt.mjs.map} +1 -1
- package/fesm2022/momentumcms-admin.mjs +1 -1
- package/package.json +1 -1
- package/types/momentumcms-admin.d.ts +232 -8
- package/fesm2022/momentumcms-admin-momentumcms-admin-D8WvqCxe.mjs.map +0 -1
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, signal, computed, Injectable, PLATFORM_ID, InjectionToken, makeStateKey, TransferState, DestroyRef, effect, input, ChangeDetectionStrategy, Component, output, viewChild, Injector, untracked, runInInjectionContext, afterNextRender, model, forwardRef
|
|
2
|
+
import { inject, signal, computed, Injectable, PLATFORM_ID, InjectionToken, makeStateKey, TransferState, DestroyRef, effect, makeEnvironmentProviders, ENVIRONMENT_INITIALIZER, input, ChangeDetectionStrategy, Component, output, viewChild, Injector, untracked, runInInjectionContext, afterNextRender, model, forwardRef } from '@angular/core';
|
|
3
3
|
import { DOCUMENT, isPlatformBrowser, isPlatformServer, NgComponentOutlet, DatePipe } from '@angular/common';
|
|
4
4
|
import { Router, NavigationEnd, ActivatedRoute, RouterOutlet, RouterLink } from '@angular/router';
|
|
5
5
|
import { HttpClient, HttpContextToken, HttpResponse, HttpErrorResponse, HttpRequest, HttpEventType, HttpParams } from '@angular/common/http';
|
|
6
|
-
import { firstValueFrom, tap, catchError, throwError, Subject, finalize, Observable, of, filter, take } from 'rxjs';
|
|
6
|
+
import { firstValueFrom, tap, catchError, throwError, Subject, finalize, Observable, of, filter, take, combineLatest } from 'rxjs';
|
|
7
7
|
import { ToastService, ConfirmationService, DIALOG_DATA, Dialog, DialogHeader, DialogTitle, DialogContent, DialogFooter, DialogClose, Button, Badge, Skeleton, DialogService, Card, CardHeader, CardContent, Separator, Progress, CardFooter, Spinner, Alert, Breadcrumbs, BreadcrumbItem, BreadcrumbSeparator, FieldDisplay, Sidebar, SidebarNav, SidebarNavItem, SidebarSection, Avatar, AvatarFallback, DropdownMenu, DropdownMenuItem, DropdownSeparator, DropdownLabel, DropdownTrigger, SidebarService, SidebarTrigger, ToastContainer, Input, McmsFormField, DialogRef, DataTable, Label, Select, CardTitle, CardDescription, Textarea, Pagination, SearchInput, PopoverTrigger, PopoverContent, Command, CommandInput, CommandList, CommandGroup, CommandItem, CommandEmpty, Checkbox } from '@momentumcms/ui';
|
|
8
8
|
import { map } from 'rxjs/operators';
|
|
9
9
|
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
|
|
10
10
|
import * as i1 from '@angular/cdk/a11y';
|
|
11
11
|
import { LiveAnnouncer, A11yModule } from '@angular/cdk/a11y';
|
|
12
|
-
import { flattenDataFields, humanizeFieldName, isUploadCollection, getSoftDeleteField } from '@momentumcms/core';
|
|
12
|
+
import { flattenDataFields, humanizeFieldName, isUploadCollection, getSoftDeleteField, hasVersionDrafts } from '@momentumcms/core';
|
|
13
13
|
import { NgIcon, provideIcons } from '@ng-icons/core';
|
|
14
14
|
import { heroFilm, heroMusicalNote, heroDocumentText, heroArchiveBox, heroPhoto, heroDocument, heroCloudArrowUp, heroXMark, heroCursorArrowRays, heroMap, heroMagnifyingGlass, heroPuzzlePiece, heroCog6Tooth, heroChartBarSquare, heroChevronUpDown, heroBolt, heroFolder, heroUsers, heroNewspaper, heroSquares2x2, heroEye, heroPencilSquare, heroArrowDownTray, heroTrash, heroChevronRight, heroChevronDown, heroChevronUp, heroPlus } from '@ng-icons/heroicons/outline';
|
|
15
15
|
import { required, validate, applyEach, apply, email, min, max, minLength, maxLength, form, submit } from '@angular/forms/signals';
|
|
@@ -947,8 +947,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
947
947
|
|
|
948
948
|
/**
|
|
949
949
|
* Route guard that prompts the user before navigating away from a dirty form.
|
|
950
|
+
*
|
|
951
|
+
* Defensive: checks that the component implements HasUnsavedChanges before calling.
|
|
952
|
+
* This is necessary because AdminPageResolver wraps the actual page component.
|
|
950
953
|
*/
|
|
951
954
|
const unsavedChangesGuard = (component) => {
|
|
955
|
+
if (typeof component.hasUnsavedChanges !== 'function')
|
|
956
|
+
return true;
|
|
952
957
|
if (!component.hasUnsavedChanges())
|
|
953
958
|
return true;
|
|
954
959
|
const feedback = inject(FeedbackService);
|
|
@@ -995,6 +1000,7 @@ function momentumAdminRoutes(configOrOptions) {
|
|
|
995
1000
|
let branding;
|
|
996
1001
|
let includeAuthRoutes;
|
|
997
1002
|
let pluginRoutes;
|
|
1003
|
+
let adminComponents;
|
|
998
1004
|
let pluginDescriptors;
|
|
999
1005
|
// Distinguish MomentumConfig (has `db`) from MomentumAdminConfig (has `collections` but no `db` or `basePath`)
|
|
1000
1006
|
// from MomentumAdminOptions (has `basePath` as required string)
|
|
@@ -1010,6 +1016,7 @@ function momentumAdminRoutes(configOrOptions) {
|
|
|
1010
1016
|
collections = [...config.collections, ...uniquePluginCollections];
|
|
1011
1017
|
globals = config.globals;
|
|
1012
1018
|
branding = config.admin?.branding;
|
|
1019
|
+
adminComponents = config.admin?.components;
|
|
1013
1020
|
includeAuthRoutes = true;
|
|
1014
1021
|
pluginRoutes = pluginDescriptors.flatMap((p) => p.adminRoutes ?? []).map(toAdminPluginRoute);
|
|
1015
1022
|
}
|
|
@@ -1024,6 +1031,7 @@ function momentumAdminRoutes(configOrOptions) {
|
|
|
1024
1031
|
collections = [...configOrOptions.collections, ...uniquePluginCollections];
|
|
1025
1032
|
globals = configOrOptions.globals;
|
|
1026
1033
|
branding = configOrOptions.branding;
|
|
1034
|
+
adminComponents = undefined; // MomentumAdminOptions doesn't carry config-level components
|
|
1027
1035
|
includeAuthRoutes = configOrOptions.includeAuthRoutes ?? true;
|
|
1028
1036
|
// Merge explicit plugin routes with plugin-declared admin routes
|
|
1029
1037
|
const explicitRoutes = configOrOptions.pluginRoutes?.map(toAdminPluginRoute) ?? [];
|
|
@@ -1044,6 +1052,7 @@ function momentumAdminRoutes(configOrOptions) {
|
|
|
1044
1052
|
collections = [...adminConfig.collections, ...uniquePluginCollections];
|
|
1045
1053
|
globals = adminConfig.globals;
|
|
1046
1054
|
branding = adminConfig.admin?.branding;
|
|
1055
|
+
adminComponents = adminConfig.admin?.components;
|
|
1047
1056
|
includeAuthRoutes = true;
|
|
1048
1057
|
pluginRoutes = pluginDescriptors.flatMap((p) => p.adminRoutes ?? []).map(toAdminPluginRoute);
|
|
1049
1058
|
}
|
|
@@ -1058,6 +1067,9 @@ function momentumAdminRoutes(configOrOptions) {
|
|
|
1058
1067
|
globals,
|
|
1059
1068
|
branding,
|
|
1060
1069
|
pluginRoutes,
|
|
1070
|
+
adminComponents,
|
|
1071
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- AdminPluginDescriptor is a subset of MomentumPlugin
|
|
1072
|
+
plugins: pluginDescriptors,
|
|
1061
1073
|
};
|
|
1062
1074
|
const routes = [];
|
|
1063
1075
|
// Auth routes (login, setup, password reset) - outside the admin shell
|
|
@@ -1095,46 +1107,74 @@ function momentumAdminRoutes(configOrOptions) {
|
|
|
1095
1107
|
data: routeData,
|
|
1096
1108
|
canActivate: [authGuard],
|
|
1097
1109
|
children: [
|
|
1098
|
-
// Dashboard (default route)
|
|
1110
|
+
// Dashboard (default route) — swappable via AdminComponentRegistry
|
|
1099
1111
|
{
|
|
1100
1112
|
path: '',
|
|
1101
|
-
loadComponent: () => Promise.resolve().then(function () { return
|
|
1113
|
+
loadComponent: () => Promise.resolve().then(function () { return adminPageResolver_component; }).then((m) => m.AdminPageResolver),
|
|
1114
|
+
data: {
|
|
1115
|
+
adminPageKey: 'dashboard',
|
|
1116
|
+
adminPageFallback: () => Promise.resolve().then(function () { return dashboard_page; }).then((m) => m.DashboardPage),
|
|
1117
|
+
},
|
|
1102
1118
|
},
|
|
1103
|
-
// Media library
|
|
1119
|
+
// Media library — swappable
|
|
1104
1120
|
{
|
|
1105
1121
|
path: 'media',
|
|
1106
|
-
loadComponent: () => Promise.resolve().then(function () { return
|
|
1122
|
+
loadComponent: () => Promise.resolve().then(function () { return adminPageResolver_component; }).then((m) => m.AdminPageResolver),
|
|
1123
|
+
data: {
|
|
1124
|
+
adminPageKey: 'media',
|
|
1125
|
+
adminPageFallback: () => Promise.resolve().then(function () { return mediaLibrary_page; }).then((m) => m.MediaLibraryPage),
|
|
1126
|
+
},
|
|
1107
1127
|
},
|
|
1108
|
-
// Collection list
|
|
1128
|
+
// Collection list — swappable (per-collection + global)
|
|
1109
1129
|
{
|
|
1110
1130
|
path: 'collections/:slug',
|
|
1111
|
-
loadComponent: () => Promise.resolve().then(function () { return
|
|
1131
|
+
loadComponent: () => Promise.resolve().then(function () { return adminPageResolver_component; }).then((m) => m.AdminPageResolver),
|
|
1132
|
+
data: {
|
|
1133
|
+
adminPageKey: 'collection-list',
|
|
1134
|
+
adminPageFallback: () => Promise.resolve().then(function () { return collectionList_page; }).then((m) => m.CollectionListPage),
|
|
1135
|
+
},
|
|
1112
1136
|
canActivate: [collectionAccessGuard],
|
|
1113
1137
|
},
|
|
1114
|
-
// Create new document
|
|
1138
|
+
// Create new document — swappable (per-collection + global)
|
|
1115
1139
|
{
|
|
1116
1140
|
path: 'collections/:slug/new',
|
|
1117
|
-
loadComponent: () => Promise.resolve().then(function () { return
|
|
1141
|
+
loadComponent: () => Promise.resolve().then(function () { return adminPageResolver_component; }).then((m) => m.AdminPageResolver),
|
|
1142
|
+
data: {
|
|
1143
|
+
adminPageKey: 'collection-edit',
|
|
1144
|
+
adminPageFallback: () => Promise.resolve().then(function () { return collectionEdit_page; }).then((m) => m.CollectionEditPage),
|
|
1145
|
+
},
|
|
1118
1146
|
canActivate: [collectionAccessGuard],
|
|
1119
1147
|
canDeactivate: [unsavedChangesGuard],
|
|
1120
1148
|
},
|
|
1121
|
-
// View existing document
|
|
1149
|
+
// View existing document — swappable (per-collection + global)
|
|
1122
1150
|
{
|
|
1123
1151
|
path: 'collections/:slug/:id',
|
|
1124
|
-
loadComponent: () => Promise.resolve().then(function () { return
|
|
1152
|
+
loadComponent: () => Promise.resolve().then(function () { return adminPageResolver_component; }).then((m) => m.AdminPageResolver),
|
|
1153
|
+
data: {
|
|
1154
|
+
adminPageKey: 'collection-view',
|
|
1155
|
+
adminPageFallback: () => Promise.resolve().then(function () { return collectionView_page; }).then((m) => m.CollectionViewPage),
|
|
1156
|
+
},
|
|
1125
1157
|
canActivate: [collectionAccessGuard],
|
|
1126
1158
|
},
|
|
1127
|
-
// Edit existing document
|
|
1159
|
+
// Edit existing document — swappable (per-collection + global)
|
|
1128
1160
|
{
|
|
1129
1161
|
path: 'collections/:slug/:id/edit',
|
|
1130
|
-
loadComponent: () => Promise.resolve().then(function () { return
|
|
1162
|
+
loadComponent: () => Promise.resolve().then(function () { return adminPageResolver_component; }).then((m) => m.AdminPageResolver),
|
|
1163
|
+
data: {
|
|
1164
|
+
adminPageKey: 'collection-edit',
|
|
1165
|
+
adminPageFallback: () => Promise.resolve().then(function () { return collectionEdit_page; }).then((m) => m.CollectionEditPage),
|
|
1166
|
+
},
|
|
1131
1167
|
canActivate: [collectionAccessGuard],
|
|
1132
1168
|
canDeactivate: [unsavedChangesGuard],
|
|
1133
1169
|
},
|
|
1134
|
-
// Global edit
|
|
1170
|
+
// Global edit — swappable
|
|
1135
1171
|
{
|
|
1136
1172
|
path: 'globals/:slug',
|
|
1137
|
-
loadComponent: () =>
|
|
1173
|
+
loadComponent: () => Promise.resolve().then(function () { return adminPageResolver_component; }).then((m) => m.AdminPageResolver),
|
|
1174
|
+
data: {
|
|
1175
|
+
adminPageKey: 'global-edit',
|
|
1176
|
+
adminPageFallback: () => import('./momentumcms-admin-global-edit.page-C-e4_QKx.mjs').then((m) => m.GlobalEditPage),
|
|
1177
|
+
},
|
|
1138
1178
|
canDeactivate: [unsavedChangesGuard],
|
|
1139
1179
|
},
|
|
1140
1180
|
// Plugin-registered routes
|
|
@@ -2198,6 +2238,7 @@ class BrowserCollectionAPI {
|
|
|
2198
2238
|
if (options?.transfer !== false && this.transferState && !isPlatformServer(this.platformId)) {
|
|
2199
2239
|
const key = generateTransferKey(this.slug, 'findById', options, id);
|
|
2200
2240
|
if (this.transferState.hasKey(key)) {
|
|
2241
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- TransferState stores T
|
|
2201
2242
|
const cached = this.transferState.get(key, null);
|
|
2202
2243
|
this.transferState.remove(key);
|
|
2203
2244
|
return of(cached);
|
|
@@ -2213,7 +2254,7 @@ class BrowserCollectionAPI {
|
|
|
2213
2254
|
return (this.http
|
|
2214
2255
|
.get(`${this.endpoint}/${id}`, { params })
|
|
2215
2256
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- ApiResponse.doc is unknown
|
|
2216
|
-
.pipe(map((response) => response.doc
|
|
2257
|
+
.pipe(map((response) => response.doc)));
|
|
2217
2258
|
}
|
|
2218
2259
|
create$(data) {
|
|
2219
2260
|
return (this.http
|
|
@@ -3185,6 +3226,239 @@ function getPluginRoutesFromRouteData(data) {
|
|
|
3185
3226
|
return [];
|
|
3186
3227
|
}
|
|
3187
3228
|
|
|
3229
|
+
/**
|
|
3230
|
+
* Registry for swappable admin page components.
|
|
3231
|
+
*
|
|
3232
|
+
* Maps page keys (e.g., 'dashboard', 'collection-list') to lazy component loaders.
|
|
3233
|
+
* Per-collection overrides use the pattern: 'collections/{slug}/{type}'.
|
|
3234
|
+
*
|
|
3235
|
+
* Resolution chain: per-collection → global → undefined (use built-in default).
|
|
3236
|
+
*/
|
|
3237
|
+
class AdminComponentRegistry {
|
|
3238
|
+
components = new Map();
|
|
3239
|
+
/** Register a lazy loader for a page key. Later registrations override earlier ones. */
|
|
3240
|
+
register(key, loader) {
|
|
3241
|
+
this.components.set(key, loader);
|
|
3242
|
+
}
|
|
3243
|
+
/** Get the lazy loader for a page key. Returns undefined if not registered. */
|
|
3244
|
+
get(key) {
|
|
3245
|
+
return this.components.get(key);
|
|
3246
|
+
}
|
|
3247
|
+
/** Check if a page key has a registered component. */
|
|
3248
|
+
has(key) {
|
|
3249
|
+
return this.components.has(key);
|
|
3250
|
+
}
|
|
3251
|
+
/**
|
|
3252
|
+
* Resolve a component loader with per-collection fallback.
|
|
3253
|
+
*
|
|
3254
|
+
* For collection pages, tries `collections/{slug}/{type}` first,
|
|
3255
|
+
* then falls back to the global key (e.g., 'collection-list').
|
|
3256
|
+
*/
|
|
3257
|
+
resolve(key, slug) {
|
|
3258
|
+
if (slug) {
|
|
3259
|
+
const type = key.replace('collection-', '');
|
|
3260
|
+
const perCollectionKey = `collections/${slug}/${type}`;
|
|
3261
|
+
const perCollection = this.components.get(perCollectionKey);
|
|
3262
|
+
if (perCollection)
|
|
3263
|
+
return perCollection;
|
|
3264
|
+
}
|
|
3265
|
+
return this.components.get(key);
|
|
3266
|
+
}
|
|
3267
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AdminComponentRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
3268
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AdminComponentRegistry, providedIn: 'root' });
|
|
3269
|
+
}
|
|
3270
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AdminComponentRegistry, decorators: [{
|
|
3271
|
+
type: Injectable,
|
|
3272
|
+
args: [{ providedIn: 'root' }]
|
|
3273
|
+
}] });
|
|
3274
|
+
|
|
3275
|
+
/**
|
|
3276
|
+
* Registry for admin layout slot components.
|
|
3277
|
+
*
|
|
3278
|
+
* Slots are additive — multiple components can be registered for the same slot key.
|
|
3279
|
+
* Per-collection slots use the pattern: `{base-slot}:{collection-slug}`.
|
|
3280
|
+
*
|
|
3281
|
+
* Resolution merges global + per-collection loaders (global first).
|
|
3282
|
+
*
|
|
3283
|
+
* The registry is signal-aware: `resolve()` reads an internal version signal,
|
|
3284
|
+
* so Angular effects that call `resolve()` will re-run when new slots are registered.
|
|
3285
|
+
*/
|
|
3286
|
+
class AdminSlotRegistry {
|
|
3287
|
+
slots = new Map();
|
|
3288
|
+
/** Incremented on every register() so signal-based consumers re-evaluate. */
|
|
3289
|
+
_version = signal(0, ...(ngDevMode ? [{ debugName: "_version" }] : []));
|
|
3290
|
+
/** Register a lazy loader for a slot. Multiple loaders per slot are supported. Duplicate loaders are skipped. */
|
|
3291
|
+
register(slot, loader) {
|
|
3292
|
+
const existing = this.slots.get(slot) ?? [];
|
|
3293
|
+
if (existing.includes(loader))
|
|
3294
|
+
return;
|
|
3295
|
+
existing.push(loader);
|
|
3296
|
+
this.slots.set(slot, existing);
|
|
3297
|
+
this._version.update((v) => v + 1);
|
|
3298
|
+
}
|
|
3299
|
+
/** Get all loaders for a slot key. Returns empty array if none registered. */
|
|
3300
|
+
getAll(slot) {
|
|
3301
|
+
return this.slots.get(slot) ?? [];
|
|
3302
|
+
}
|
|
3303
|
+
/** Check if a slot has any registered loaders. */
|
|
3304
|
+
has(slot) {
|
|
3305
|
+
return this.slots.has(slot);
|
|
3306
|
+
}
|
|
3307
|
+
/**
|
|
3308
|
+
* Resolve slot loaders, merging global and per-collection entries.
|
|
3309
|
+
*
|
|
3310
|
+
* Reads the internal version signal so Angular effects tracking this call
|
|
3311
|
+
* will re-run when new slots are registered.
|
|
3312
|
+
*/
|
|
3313
|
+
resolve(slot, slug) {
|
|
3314
|
+
this._version();
|
|
3315
|
+
const global = this.getAll(slot);
|
|
3316
|
+
if (!slug)
|
|
3317
|
+
return global;
|
|
3318
|
+
const perCollection = this.getAll(`${slot}:${slug}`);
|
|
3319
|
+
return [...global, ...perCollection];
|
|
3320
|
+
}
|
|
3321
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AdminSlotRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
3322
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AdminSlotRegistry, providedIn: 'root' });
|
|
3323
|
+
}
|
|
3324
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AdminSlotRegistry, decorators: [{
|
|
3325
|
+
type: Injectable,
|
|
3326
|
+
args: [{ providedIn: 'root' }]
|
|
3327
|
+
}] });
|
|
3328
|
+
|
|
3329
|
+
/**
|
|
3330
|
+
* Register a custom admin page component override.
|
|
3331
|
+
*
|
|
3332
|
+
* ```typescript
|
|
3333
|
+
* export const appConfig: ApplicationConfig = {
|
|
3334
|
+
* providers: [
|
|
3335
|
+
* provideAdminComponent('dashboard', () =>
|
|
3336
|
+
* import('./custom-dashboard.component').then(m => m.CustomDashboard)
|
|
3337
|
+
* ),
|
|
3338
|
+
* ],
|
|
3339
|
+
* };
|
|
3340
|
+
* ```
|
|
3341
|
+
*/
|
|
3342
|
+
function provideAdminComponent(key, loader) {
|
|
3343
|
+
return makeEnvironmentProviders([
|
|
3344
|
+
{
|
|
3345
|
+
provide: ENVIRONMENT_INITIALIZER,
|
|
3346
|
+
multi: true,
|
|
3347
|
+
useFactory: () => {
|
|
3348
|
+
const registry = inject(AdminComponentRegistry);
|
|
3349
|
+
return () => {
|
|
3350
|
+
registry.register(key, loader);
|
|
3351
|
+
};
|
|
3352
|
+
},
|
|
3353
|
+
},
|
|
3354
|
+
]);
|
|
3355
|
+
}
|
|
3356
|
+
/**
|
|
3357
|
+
* Register a component into an admin layout slot.
|
|
3358
|
+
*
|
|
3359
|
+
* ```typescript
|
|
3360
|
+
* export const appConfig: ApplicationConfig = {
|
|
3361
|
+
* providers: [
|
|
3362
|
+
* provideAdminSlot('dashboard:before', () =>
|
|
3363
|
+
* import('./welcome-banner.component').then(m => m.WelcomeBanner)
|
|
3364
|
+
* ),
|
|
3365
|
+
* ],
|
|
3366
|
+
* };
|
|
3367
|
+
* ```
|
|
3368
|
+
*/
|
|
3369
|
+
function provideAdminSlot(slot, loader) {
|
|
3370
|
+
return makeEnvironmentProviders([
|
|
3371
|
+
{
|
|
3372
|
+
provide: ENVIRONMENT_INITIALIZER,
|
|
3373
|
+
multi: true,
|
|
3374
|
+
useFactory: () => {
|
|
3375
|
+
const registry = inject(AdminSlotRegistry);
|
|
3376
|
+
return () => {
|
|
3377
|
+
registry.register(slot, loader);
|
|
3378
|
+
};
|
|
3379
|
+
},
|
|
3380
|
+
},
|
|
3381
|
+
]);
|
|
3382
|
+
}
|
|
3383
|
+
/** Mapping from AdminComponentsConfig page keys to registry keys. */
|
|
3384
|
+
const PAGE_KEY_MAP = {
|
|
3385
|
+
dashboard: 'dashboard',
|
|
3386
|
+
login: 'login',
|
|
3387
|
+
media: 'media',
|
|
3388
|
+
};
|
|
3389
|
+
/** Mapping from AdminComponentsConfig slot keys to registry slot keys. */
|
|
3390
|
+
const GLOBAL_SLOT_MAP = {
|
|
3391
|
+
beforeNavigation: 'shell:nav-start',
|
|
3392
|
+
afterNavigation: 'shell:nav-end',
|
|
3393
|
+
header: 'shell:header',
|
|
3394
|
+
footer: 'shell:footer',
|
|
3395
|
+
beforeDashboard: 'dashboard:before',
|
|
3396
|
+
afterDashboard: 'dashboard:after',
|
|
3397
|
+
beforeLogin: 'login:before',
|
|
3398
|
+
afterLogin: 'login:after',
|
|
3399
|
+
};
|
|
3400
|
+
/** Mapping from CollectionAdminComponentsConfig page keys to page type suffixes. */
|
|
3401
|
+
const COLLECTION_PAGE_MAP = {
|
|
3402
|
+
list: 'list',
|
|
3403
|
+
edit: 'edit',
|
|
3404
|
+
view: 'view',
|
|
3405
|
+
};
|
|
3406
|
+
/** Mapping from CollectionAdminComponentsConfig slot keys to slot key patterns. */
|
|
3407
|
+
const COLLECTION_SLOT_MAP = {
|
|
3408
|
+
beforeList: 'collection-list:before',
|
|
3409
|
+
afterList: 'collection-list:after',
|
|
3410
|
+
beforeEdit: 'collection-edit:before',
|
|
3411
|
+
afterEdit: 'collection-edit:after',
|
|
3412
|
+
editSidebar: 'collection-edit:sidebar',
|
|
3413
|
+
beforeView: 'collection-view:before',
|
|
3414
|
+
afterView: 'collection-view:after',
|
|
3415
|
+
};
|
|
3416
|
+
/**
|
|
3417
|
+
* Read AdminComponentsConfig and CollectionAdminComponentsConfig from config
|
|
3418
|
+
* and register them into AdminComponentRegistry and AdminSlotRegistry.
|
|
3419
|
+
*
|
|
3420
|
+
* Called once in AdminShellComponent.ngOnInit() after route data is available.
|
|
3421
|
+
*/
|
|
3422
|
+
function registerConfigComponents(collections, adminComponents, componentRegistry, slotRegistry) {
|
|
3423
|
+
// Register global page overrides and slots from AdminComponentsConfig
|
|
3424
|
+
if (adminComponents) {
|
|
3425
|
+
for (const [configKey, registryKey] of Object.entries(PAGE_KEY_MAP)) {
|
|
3426
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Object.entries returns string keys
|
|
3427
|
+
const loader = adminComponents[configKey];
|
|
3428
|
+
if (loader) {
|
|
3429
|
+
componentRegistry.register(registryKey, loader);
|
|
3430
|
+
}
|
|
3431
|
+
}
|
|
3432
|
+
for (const [configKey, slotKey] of Object.entries(GLOBAL_SLOT_MAP)) {
|
|
3433
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Object.entries returns string keys
|
|
3434
|
+
const loader = adminComponents[configKey];
|
|
3435
|
+
if (loader) {
|
|
3436
|
+
slotRegistry.register(slotKey, loader);
|
|
3437
|
+
}
|
|
3438
|
+
}
|
|
3439
|
+
}
|
|
3440
|
+
// Register per-collection page overrides and slots
|
|
3441
|
+
for (const collection of collections) {
|
|
3442
|
+
const components = collection.admin?.components;
|
|
3443
|
+
if (!components)
|
|
3444
|
+
continue;
|
|
3445
|
+
for (const [configKey, typeSuffix] of Object.entries(COLLECTION_PAGE_MAP)) {
|
|
3446
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Object.entries returns string keys
|
|
3447
|
+
const loader = components[configKey];
|
|
3448
|
+
if (loader) {
|
|
3449
|
+
componentRegistry.register(`collections/${collection.slug}/${typeSuffix}`, loader);
|
|
3450
|
+
}
|
|
3451
|
+
}
|
|
3452
|
+
for (const [configKey, baseSlotKey] of Object.entries(COLLECTION_SLOT_MAP)) {
|
|
3453
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Object.entries returns string keys
|
|
3454
|
+
const loader = components[configKey];
|
|
3455
|
+
if (loader) {
|
|
3456
|
+
slotRegistry.register(`${baseSlotKey}:${collection.slug}`, loader);
|
|
3457
|
+
}
|
|
3458
|
+
}
|
|
3459
|
+
}
|
|
3460
|
+
}
|
|
3461
|
+
|
|
3188
3462
|
/**
|
|
3189
3463
|
* Entity Form Widget Types
|
|
3190
3464
|
*
|
|
@@ -4229,105 +4503,301 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
4229
4503
|
}], ctorParameters: () => [], propDecorators: { collection: [{ type: i0.Input, args: [{ isSignal: true, alias: "collection", required: true }] }], documentId: [{ type: i0.Input, args: [{ isSignal: true, alias: "documentId", required: true }] }], documentLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "documentLabel", required: false }] }], restored: [{ type: i0.Output, args: ["restored"] }] } });
|
|
4230
4504
|
|
|
4231
4505
|
/**
|
|
4232
|
-
*
|
|
4506
|
+
* Publish Controls Widget
|
|
4233
4507
|
*
|
|
4234
|
-
* Displays
|
|
4235
|
-
* - Images: Thumbnail preview
|
|
4236
|
-
* - Videos: Video icon with optional poster
|
|
4237
|
-
* - Audio: Audio icon
|
|
4238
|
-
* - Documents: Document icon
|
|
4239
|
-
* - Other: Generic file icon
|
|
4508
|
+
* Displays the current document status and provides publish/unpublish actions.
|
|
4240
4509
|
*
|
|
4241
4510
|
* @example
|
|
4242
4511
|
* ```html
|
|
4243
|
-
* <mcms-
|
|
4244
|
-
* [
|
|
4245
|
-
* [
|
|
4512
|
+
* <mcms-publish-controls
|
|
4513
|
+
* [collection]="'posts'"
|
|
4514
|
+
* [documentId]="'abc123'"
|
|
4515
|
+
* [documentLabel]="'Post'"
|
|
4516
|
+
* (statusChanged)="onStatusChanged($event)"
|
|
4246
4517
|
* />
|
|
4247
4518
|
* ```
|
|
4248
4519
|
*/
|
|
4249
|
-
class
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
/**
|
|
4253
|
-
|
|
4254
|
-
/**
|
|
4255
|
-
|
|
4256
|
-
/**
|
|
4257
|
-
|
|
4258
|
-
/**
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
/**
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
isAudio = computed(() => {
|
|
4295
|
-
const mimeType = this.media()?.mimeType ?? '';
|
|
4296
|
-
return mimeType.startsWith('audio/');
|
|
4297
|
-
}, ...(ngDevMode ? [{ debugName: "isAudio" }] : []));
|
|
4298
|
-
/** Image URL for preview */
|
|
4299
|
-
imageUrl = computed(() => {
|
|
4300
|
-
const media = this.media();
|
|
4301
|
-
if (!media)
|
|
4302
|
-
return '';
|
|
4303
|
-
return media.url ?? `/api/media/file/${media.path}`;
|
|
4304
|
-
}, ...(ngDevMode ? [{ debugName: "imageUrl" }] : []));
|
|
4305
|
-
/** Icon name based on media type */
|
|
4306
|
-
iconName = computed(() => {
|
|
4307
|
-
const mimeType = this.media()?.mimeType ?? '';
|
|
4308
|
-
if (mimeType.startsWith('video/')) {
|
|
4309
|
-
return heroFilm;
|
|
4310
|
-
}
|
|
4311
|
-
if (mimeType.startsWith('audio/')) {
|
|
4312
|
-
return heroMusicalNote;
|
|
4313
|
-
}
|
|
4314
|
-
if (mimeType === 'application/pdf') {
|
|
4315
|
-
return heroDocumentText;
|
|
4520
|
+
class PublishControlsWidget {
|
|
4521
|
+
versionService = inject(VersionService);
|
|
4522
|
+
feedback = inject(FeedbackService);
|
|
4523
|
+
/** Collection slug */
|
|
4524
|
+
collection = input.required(...(ngDevMode ? [{ debugName: "collection" }] : []));
|
|
4525
|
+
/** Document ID */
|
|
4526
|
+
documentId = input.required(...(ngDevMode ? [{ debugName: "documentId" }] : []));
|
|
4527
|
+
/** Document label for feedback messages */
|
|
4528
|
+
documentLabel = input('Document', ...(ngDevMode ? [{ debugName: "documentLabel" }] : []));
|
|
4529
|
+
/** Initial status (optional, will be fetched if not provided) */
|
|
4530
|
+
initialStatus = input(undefined, ...(ngDevMode ? [{ debugName: "initialStatus" }] : []));
|
|
4531
|
+
/** Emitted when the status changes */
|
|
4532
|
+
statusChanged = output();
|
|
4533
|
+
/** Current status */
|
|
4534
|
+
status = signal('draft', ...(ngDevMode ? [{ debugName: "status" }] : []));
|
|
4535
|
+
/** Whether a status update is in progress */
|
|
4536
|
+
isUpdating = signal(false, ...(ngDevMode ? [{ debugName: "isUpdating" }] : []));
|
|
4537
|
+
/** Whether status is loading */
|
|
4538
|
+
isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
|
|
4539
|
+
/** Badge variant based on status (derived from status signal) */
|
|
4540
|
+
statusVariant = computed(() => this.status() === 'published' ? 'default' : 'secondary', ...(ngDevMode ? [{ debugName: "statusVariant" }] : []));
|
|
4541
|
+
/** Status label (derived from status signal) */
|
|
4542
|
+
statusLabel = computed(() => (this.status() === 'published' ? 'Published' : 'Draft'), ...(ngDevMode ? [{ debugName: "statusLabel" }] : []));
|
|
4543
|
+
constructor() {
|
|
4544
|
+
// Load status when inputs change
|
|
4545
|
+
effect(() => {
|
|
4546
|
+
const collection = this.collection();
|
|
4547
|
+
const docId = this.documentId();
|
|
4548
|
+
const initial = this.initialStatus();
|
|
4549
|
+
if (initial !== undefined) {
|
|
4550
|
+
this.updateStatusDisplay(initial);
|
|
4551
|
+
}
|
|
4552
|
+
else if (collection && docId) {
|
|
4553
|
+
this.loadStatus(collection, docId);
|
|
4554
|
+
}
|
|
4555
|
+
});
|
|
4556
|
+
}
|
|
4557
|
+
/**
|
|
4558
|
+
* Load the current status from the API.
|
|
4559
|
+
*/
|
|
4560
|
+
async loadStatus(collection, docId) {
|
|
4561
|
+
this.isLoading.set(true);
|
|
4562
|
+
try {
|
|
4563
|
+
const status = await this.versionService.getStatus(collection, docId);
|
|
4564
|
+
this.updateStatusDisplay(status);
|
|
4316
4565
|
}
|
|
4317
|
-
|
|
4318
|
-
|
|
4566
|
+
catch {
|
|
4567
|
+
// Default to draft if we can't load status
|
|
4568
|
+
this.updateStatusDisplay('draft');
|
|
4319
4569
|
}
|
|
4320
|
-
|
|
4321
|
-
|
|
4570
|
+
finally {
|
|
4571
|
+
this.isLoading.set(false);
|
|
4322
4572
|
}
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4573
|
+
}
|
|
4574
|
+
/**
|
|
4575
|
+
* Update the status display.
|
|
4576
|
+
* statusVariant and statusLabel are computed signals that automatically update.
|
|
4577
|
+
*/
|
|
4578
|
+
updateStatusDisplay(status) {
|
|
4579
|
+
this.status.set(status);
|
|
4580
|
+
}
|
|
4581
|
+
/**
|
|
4582
|
+
* Publish the document.
|
|
4583
|
+
*/
|
|
4584
|
+
async onPublish() {
|
|
4585
|
+
this.isUpdating.set(true);
|
|
4586
|
+
try {
|
|
4587
|
+
await this.versionService.publish(this.collection(), this.documentId());
|
|
4588
|
+
this.updateStatusDisplay('published');
|
|
4589
|
+
this.statusChanged.emit('published');
|
|
4590
|
+
}
|
|
4591
|
+
catch {
|
|
4592
|
+
// Error handled by crudToastInterceptor
|
|
4593
|
+
}
|
|
4594
|
+
finally {
|
|
4595
|
+
this.isUpdating.set(false);
|
|
4596
|
+
}
|
|
4597
|
+
}
|
|
4598
|
+
/**
|
|
4599
|
+
* Unpublish the document.
|
|
4600
|
+
*/
|
|
4601
|
+
async onUnpublish() {
|
|
4602
|
+
const confirmed = await this.feedback.confirmUnpublish(this.documentLabel());
|
|
4603
|
+
if (!confirmed) {
|
|
4604
|
+
return;
|
|
4605
|
+
}
|
|
4606
|
+
this.isUpdating.set(true);
|
|
4607
|
+
try {
|
|
4608
|
+
await this.versionService.unpublish(this.collection(), this.documentId());
|
|
4609
|
+
this.updateStatusDisplay('draft');
|
|
4610
|
+
this.statusChanged.emit('draft');
|
|
4611
|
+
}
|
|
4612
|
+
catch {
|
|
4613
|
+
// Error handled by crudToastInterceptor
|
|
4614
|
+
}
|
|
4615
|
+
finally {
|
|
4616
|
+
this.isUpdating.set(false);
|
|
4617
|
+
}
|
|
4618
|
+
}
|
|
4619
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PublishControlsWidget, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4620
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: PublishControlsWidget, isStandalone: true, selector: "mcms-publish-controls", inputs: { collection: { classPropertyName: "collection", publicName: "collection", isSignal: true, isRequired: true, transformFunction: null }, documentId: { classPropertyName: "documentId", publicName: "documentId", isSignal: true, isRequired: true, transformFunction: null }, documentLabel: { classPropertyName: "documentLabel", publicName: "documentLabel", isSignal: true, isRequired: false, transformFunction: null }, initialStatus: { classPropertyName: "initialStatus", publicName: "initialStatus", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { statusChanged: "statusChanged" }, host: { classAttribute: "inline-flex items-center gap-3" }, ngImport: i0, template: `
|
|
4621
|
+
<mcms-badge [variant]="statusVariant()">
|
|
4622
|
+
{{ statusLabel() }}
|
|
4623
|
+
</mcms-badge>
|
|
4624
|
+
|
|
4625
|
+
@if (status() === 'draft') {
|
|
4626
|
+
<button
|
|
4627
|
+
mcms-button
|
|
4628
|
+
variant="primary"
|
|
4629
|
+
size="sm"
|
|
4630
|
+
[disabled]="isUpdating()"
|
|
4631
|
+
(click)="onPublish()"
|
|
4632
|
+
>
|
|
4633
|
+
@if (isUpdating()) {
|
|
4634
|
+
Publishing...
|
|
4635
|
+
} @else {
|
|
4636
|
+
Publish
|
|
4637
|
+
}
|
|
4638
|
+
</button>
|
|
4639
|
+
} @else {
|
|
4640
|
+
<button
|
|
4641
|
+
mcms-button
|
|
4642
|
+
variant="outline"
|
|
4643
|
+
size="sm"
|
|
4644
|
+
[disabled]="isUpdating()"
|
|
4645
|
+
(click)="onUnpublish()"
|
|
4646
|
+
>
|
|
4647
|
+
@if (isUpdating()) {
|
|
4648
|
+
Unpublishing...
|
|
4649
|
+
} @else {
|
|
4650
|
+
Unpublish
|
|
4651
|
+
}
|
|
4652
|
+
</button>
|
|
4653
|
+
}
|
|
4654
|
+
`, isInline: true, dependencies: [{ kind: "component", type: Badge, selector: "mcms-badge", inputs: ["variant", "class", "role", "ariaLabel"] }, { kind: "component", type: Button, selector: "button[mcms-button], a[mcms-button]", inputs: ["variant", "size", "disabled", "loading", "ariaLabel", "class"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4655
|
+
}
|
|
4656
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PublishControlsWidget, decorators: [{
|
|
4657
|
+
type: Component,
|
|
4658
|
+
args: [{
|
|
4659
|
+
selector: 'mcms-publish-controls',
|
|
4660
|
+
imports: [Badge, Button],
|
|
4661
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
4662
|
+
host: { class: 'inline-flex items-center gap-3' },
|
|
4663
|
+
template: `
|
|
4664
|
+
<mcms-badge [variant]="statusVariant()">
|
|
4665
|
+
{{ statusLabel() }}
|
|
4666
|
+
</mcms-badge>
|
|
4667
|
+
|
|
4668
|
+
@if (status() === 'draft') {
|
|
4669
|
+
<button
|
|
4670
|
+
mcms-button
|
|
4671
|
+
variant="primary"
|
|
4672
|
+
size="sm"
|
|
4673
|
+
[disabled]="isUpdating()"
|
|
4674
|
+
(click)="onPublish()"
|
|
4675
|
+
>
|
|
4676
|
+
@if (isUpdating()) {
|
|
4677
|
+
Publishing...
|
|
4678
|
+
} @else {
|
|
4679
|
+
Publish
|
|
4680
|
+
}
|
|
4681
|
+
</button>
|
|
4682
|
+
} @else {
|
|
4683
|
+
<button
|
|
4684
|
+
mcms-button
|
|
4685
|
+
variant="outline"
|
|
4686
|
+
size="sm"
|
|
4687
|
+
[disabled]="isUpdating()"
|
|
4688
|
+
(click)="onUnpublish()"
|
|
4689
|
+
>
|
|
4690
|
+
@if (isUpdating()) {
|
|
4691
|
+
Unpublishing...
|
|
4692
|
+
} @else {
|
|
4693
|
+
Unpublish
|
|
4694
|
+
}
|
|
4695
|
+
</button>
|
|
4696
|
+
}
|
|
4697
|
+
`,
|
|
4698
|
+
}]
|
|
4699
|
+
}], ctorParameters: () => [], propDecorators: { collection: [{ type: i0.Input, args: [{ isSignal: true, alias: "collection", required: true }] }], documentId: [{ type: i0.Input, args: [{ isSignal: true, alias: "documentId", required: true }] }], documentLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "documentLabel", required: false }] }], initialStatus: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialStatus", required: false }] }], statusChanged: [{ type: i0.Output, args: ["statusChanged"] }] } });
|
|
4700
|
+
|
|
4701
|
+
/**
|
|
4702
|
+
* Media Preview Component
|
|
4703
|
+
*
|
|
4704
|
+
* Displays a preview of media based on its type:
|
|
4705
|
+
* - Images: Thumbnail preview
|
|
4706
|
+
* - Videos: Video icon with optional poster
|
|
4707
|
+
* - Audio: Audio icon
|
|
4708
|
+
* - Documents: Document icon
|
|
4709
|
+
* - Other: Generic file icon
|
|
4710
|
+
*
|
|
4711
|
+
* @example
|
|
4712
|
+
* ```html
|
|
4713
|
+
* <mcms-media-preview
|
|
4714
|
+
* [media]="mediaDocument"
|
|
4715
|
+
* [size]="'md'"
|
|
4716
|
+
* />
|
|
4717
|
+
* ```
|
|
4718
|
+
*/
|
|
4719
|
+
class MediaPreviewComponent {
|
|
4720
|
+
/** Media data to preview */
|
|
4721
|
+
media = input(null, ...(ngDevMode ? [{ debugName: "media" }] : []));
|
|
4722
|
+
/** Size of the preview */
|
|
4723
|
+
size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : []));
|
|
4724
|
+
/** Custom class override */
|
|
4725
|
+
class = input('', ...(ngDevMode ? [{ debugName: "class" }] : []));
|
|
4726
|
+
/** Whether to show rounded corners */
|
|
4727
|
+
rounded = input(true, ...(ngDevMode ? [{ debugName: "rounded" }] : []));
|
|
4728
|
+
/** Host classes */
|
|
4729
|
+
hostClasses = computed(() => {
|
|
4730
|
+
const sizeClass = this.sizeClasses()[this.size()];
|
|
4731
|
+
const roundedClass = this.rounded() ? 'rounded-md overflow-hidden' : '';
|
|
4732
|
+
return `inline-block ${sizeClass} ${roundedClass} ${this.class()}`;
|
|
4733
|
+
}, ...(ngDevMode ? [{ debugName: "hostClasses" }] : []));
|
|
4734
|
+
/** Size classes map */
|
|
4735
|
+
sizeClasses = computed(() => ({
|
|
4736
|
+
xs: 'h-8 w-8',
|
|
4737
|
+
sm: 'h-12 w-12',
|
|
4738
|
+
md: 'h-20 w-20',
|
|
4739
|
+
lg: 'h-32 w-32',
|
|
4740
|
+
xl: 'h-48 w-48',
|
|
4741
|
+
}), ...(ngDevMode ? [{ debugName: "sizeClasses" }] : []));
|
|
4742
|
+
/** Icon size classes */
|
|
4743
|
+
iconClasses = computed(() => {
|
|
4744
|
+
const sizes = {
|
|
4745
|
+
xs: 'text-lg',
|
|
4746
|
+
sm: 'text-xl',
|
|
4747
|
+
md: 'text-3xl',
|
|
4748
|
+
lg: 'text-4xl',
|
|
4749
|
+
xl: 'text-6xl',
|
|
4750
|
+
};
|
|
4751
|
+
return `${sizes[this.size()]} text-mcms-muted-foreground`;
|
|
4752
|
+
}, ...(ngDevMode ? [{ debugName: "iconClasses" }] : []));
|
|
4753
|
+
/** Whether the media is an image */
|
|
4754
|
+
isImage = computed(() => {
|
|
4755
|
+
const mimeType = this.media()?.mimeType ?? '';
|
|
4756
|
+
return mimeType.startsWith('image/');
|
|
4757
|
+
}, ...(ngDevMode ? [{ debugName: "isImage" }] : []));
|
|
4758
|
+
/** Whether the media is a video */
|
|
4759
|
+
isVideo = computed(() => {
|
|
4760
|
+
const mimeType = this.media()?.mimeType ?? '';
|
|
4761
|
+
return mimeType.startsWith('video/');
|
|
4762
|
+
}, ...(ngDevMode ? [{ debugName: "isVideo" }] : []));
|
|
4763
|
+
/** Whether the media is audio */
|
|
4764
|
+
isAudio = computed(() => {
|
|
4765
|
+
const mimeType = this.media()?.mimeType ?? '';
|
|
4766
|
+
return mimeType.startsWith('audio/');
|
|
4767
|
+
}, ...(ngDevMode ? [{ debugName: "isAudio" }] : []));
|
|
4768
|
+
/** Image URL for preview */
|
|
4769
|
+
imageUrl = computed(() => {
|
|
4770
|
+
const media = this.media();
|
|
4771
|
+
if (!media)
|
|
4772
|
+
return '';
|
|
4773
|
+
return media.url ?? `/api/media/file/${media.path}`;
|
|
4774
|
+
}, ...(ngDevMode ? [{ debugName: "imageUrl" }] : []));
|
|
4775
|
+
/** Icon name based on media type */
|
|
4776
|
+
iconName = computed(() => {
|
|
4777
|
+
const mimeType = this.media()?.mimeType ?? '';
|
|
4778
|
+
if (mimeType.startsWith('video/')) {
|
|
4779
|
+
return heroFilm;
|
|
4780
|
+
}
|
|
4781
|
+
if (mimeType.startsWith('audio/')) {
|
|
4782
|
+
return heroMusicalNote;
|
|
4783
|
+
}
|
|
4784
|
+
if (mimeType === 'application/pdf') {
|
|
4785
|
+
return heroDocumentText;
|
|
4786
|
+
}
|
|
4787
|
+
if (mimeType.startsWith('application/zip') || mimeType.includes('compressed')) {
|
|
4788
|
+
return heroArchiveBox;
|
|
4789
|
+
}
|
|
4790
|
+
if (mimeType.startsWith('image/')) {
|
|
4791
|
+
return heroPhoto;
|
|
4792
|
+
}
|
|
4793
|
+
return heroDocument;
|
|
4794
|
+
}, ...(ngDevMode ? [{ debugName: "iconName" }] : []));
|
|
4795
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: MediaPreviewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4796
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: MediaPreviewComponent, isStandalone: true, selector: "mcms-media-preview", inputs: { media: { classPropertyName: "media", publicName: "media", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, rounded: { classPropertyName: "rounded", publicName: "rounded", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "hostClasses()" } }, ngImport: i0, template: `
|
|
4797
|
+
@if (isImage()) {
|
|
4798
|
+
<img
|
|
4799
|
+
[src]="imageUrl()"
|
|
4800
|
+
[alt]="media()?.alt ?? media()?.filename ?? 'Media preview'"
|
|
4331
4801
|
class="h-full w-full object-cover"
|
|
4332
4802
|
/>
|
|
4333
4803
|
} @else {
|
|
@@ -5482,6 +5952,13 @@ class EntityFormWidget {
|
|
|
5482
5952
|
}
|
|
5483
5953
|
return false;
|
|
5484
5954
|
}, ...(ngDevMode ? [{ debugName: "hasVersioning" }] : []));
|
|
5955
|
+
/** Current document status (from form model or default to 'draft') */
|
|
5956
|
+
documentStatus = computed(() => {
|
|
5957
|
+
const data = this.formModel();
|
|
5958
|
+
if (data['_status'] === 'published')
|
|
5959
|
+
return 'published';
|
|
5960
|
+
return 'draft';
|
|
5961
|
+
}, ...(ngDevMode ? [{ debugName: "documentStatus" }] : []));
|
|
5485
5962
|
/** Whether draft save is available (edit mode with existing entity) */
|
|
5486
5963
|
canSaveDraft = computed(() => {
|
|
5487
5964
|
return this.hasVersioning() && this.mode() === 'edit' && !!this.entityId();
|
|
@@ -5570,21 +6047,26 @@ class EntityFormWidget {
|
|
|
5570
6047
|
this.formError.set(null);
|
|
5571
6048
|
try {
|
|
5572
6049
|
const entity = await this.api.collection(slug).findById(id);
|
|
5573
|
-
if (entity) {
|
|
5574
|
-
this.originalData.set(entity);
|
|
5575
|
-
this.formModel.set({ ...entity });
|
|
5576
|
-
const ef = this.entityForm();
|
|
5577
|
-
if (ef)
|
|
5578
|
-
ef().reset();
|
|
5579
|
-
}
|
|
5580
|
-
else {
|
|
6050
|
+
if (!entity) {
|
|
5581
6051
|
this.formError.set(`${this.collectionLabelSingular()} not found`);
|
|
5582
6052
|
this.feedback.entityNotFound(this.collectionLabelSingular());
|
|
6053
|
+
return;
|
|
5583
6054
|
}
|
|
6055
|
+
this.originalData.set(entity);
|
|
6056
|
+
this.formModel.set({ ...entity });
|
|
6057
|
+
const ef = this.entityForm();
|
|
6058
|
+
if (ef)
|
|
6059
|
+
ef().reset();
|
|
5584
6060
|
}
|
|
5585
6061
|
catch (err) {
|
|
5586
|
-
|
|
5587
|
-
|
|
6062
|
+
if (err instanceof Error && err.name === 'DocumentNotFoundError') {
|
|
6063
|
+
this.formError.set(`${this.collectionLabelSingular()} not found`);
|
|
6064
|
+
this.feedback.entityNotFound(this.collectionLabelSingular());
|
|
6065
|
+
}
|
|
6066
|
+
else {
|
|
6067
|
+
this.formError.set('Failed to load data');
|
|
6068
|
+
this.saveError.emit(err instanceof Error ? err : new Error('Failed to load data'));
|
|
6069
|
+
}
|
|
5588
6070
|
}
|
|
5589
6071
|
finally {
|
|
5590
6072
|
this.isLoading.set(false);
|
|
@@ -5845,6 +6327,13 @@ class EntityFormWidget {
|
|
|
5845
6327
|
switchToEdit() {
|
|
5846
6328
|
this.modeChange.emit('edit');
|
|
5847
6329
|
}
|
|
6330
|
+
/**
|
|
6331
|
+
* Handle status change from publish controls.
|
|
6332
|
+
*/
|
|
6333
|
+
onStatusChanged(status) {
|
|
6334
|
+
const data = { ...this.formModel(), _status: status };
|
|
6335
|
+
this.formModel.set(data);
|
|
6336
|
+
}
|
|
5848
6337
|
/**
|
|
5849
6338
|
* Handle version restore — reload the entity data.
|
|
5850
6339
|
*/
|
|
@@ -5898,17 +6387,28 @@ class EntityFormWidget {
|
|
|
5898
6387
|
<div class="mb-8">
|
|
5899
6388
|
<div class="flex items-start justify-between gap-4">
|
|
5900
6389
|
<div>
|
|
5901
|
-
<
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
6390
|
+
<div class="flex items-center gap-3">
|
|
6391
|
+
<h1 class="text-2xl font-semibold tracking-tight">
|
|
6392
|
+
@if (isGlobal()) {
|
|
6393
|
+
{{ collectionLabelSingular() }}
|
|
6394
|
+
} @else if (mode() === 'create') {
|
|
6395
|
+
Create {{ collectionLabelSingular() }}
|
|
6396
|
+
} @else if (mode() === 'edit') {
|
|
6397
|
+
Edit {{ collectionLabelSingular() }}
|
|
6398
|
+
} @else {
|
|
6399
|
+
View {{ collectionLabelSingular() }}
|
|
6400
|
+
}
|
|
6401
|
+
</h1>
|
|
6402
|
+
@if (hasVersioning() && mode() === 'edit' && entityId()) {
|
|
6403
|
+
<mcms-publish-controls
|
|
6404
|
+
[collection]="collection().slug"
|
|
6405
|
+
[documentId]="entityId() ?? ''"
|
|
6406
|
+
[documentLabel]="collectionLabelSingular()"
|
|
6407
|
+
[initialStatus]="documentStatus()"
|
|
6408
|
+
(statusChanged)="onStatusChanged($event)"
|
|
6409
|
+
/>
|
|
5910
6410
|
}
|
|
5911
|
-
</
|
|
6411
|
+
</div>
|
|
5912
6412
|
<p class="mt-1 text-muted-foreground">
|
|
5913
6413
|
@if (isGlobal()) {
|
|
5914
6414
|
Manage {{ collectionLabelSingular().toLowerCase() }} settings.
|
|
@@ -6034,14 +6534,14 @@ class EntityFormWidget {
|
|
|
6034
6534
|
<div class="mt-8">
|
|
6035
6535
|
<mcms-version-history
|
|
6036
6536
|
[collection]="collection().slug"
|
|
6037
|
-
[documentId]="entityId()
|
|
6537
|
+
[documentId]="entityId() ?? ''"
|
|
6038
6538
|
[documentLabel]="collectionLabelSingular()"
|
|
6039
6539
|
(restored)="onVersionRestored()"
|
|
6040
6540
|
/>
|
|
6041
6541
|
</div>
|
|
6042
6542
|
}
|
|
6043
6543
|
</div>
|
|
6044
|
-
`, isInline: true, dependencies: [{ kind: "component", type: Card, selector: "mcms-card" }, { kind: "component", type: CardContent, selector: "mcms-card-content" }, { kind: "component", type: CardFooter, selector: "mcms-card-footer" }, { kind: "component", type: Button, selector: "button[mcms-button], a[mcms-button]", inputs: ["variant", "size", "disabled", "loading", "ariaLabel", "class"] }, { kind: "component", type: Spinner, selector: "mcms-spinner", inputs: ["size", "label", "class"] }, { kind: "component", type: Alert, selector: "mcms-alert", inputs: ["variant", "class"] }, { kind: "component", type: FieldRenderer, selector: "mcms-field-renderer", inputs: ["field", "formNode", "formTree", "formModel", "mode", "path"] }, { kind: "component", type: Breadcrumbs, selector: "mcms-breadcrumbs", inputs: ["class"] }, { kind: "component", type: BreadcrumbItem, selector: "mcms-breadcrumb-item", inputs: ["href", "current", "class"] }, { kind: "component", type: BreadcrumbSeparator, selector: "mcms-breadcrumb-separator", inputs: ["class"] }, { kind: "component", type: VersionHistoryWidget, selector: "mcms-version-history", inputs: ["collection", "documentId", "documentLabel"], outputs: ["restored"] }, { kind: "component", type: CollectionUploadZoneComponent, selector: "mcms-collection-upload-zone", inputs: ["uploadConfig", "disabled", "pendingFile", "isUploading", "uploadProgress", "error", "existingMedia"], outputs: ["fileSelected", "fileRemoved"] }, { kind: "component", type: FocalPointPickerComponent, selector: "mcms-focal-point-picker", inputs: ["imageUrl", "focalPoint", "alt", "naturalWidth", "naturalHeight", "imageSizes"], outputs: ["focalPointChange"] }, { kind: "component", type: ImageVariantsDisplay, selector: "mcms-image-variants-display", inputs: ["sizes"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6544
|
+
`, isInline: true, dependencies: [{ kind: "component", type: Card, selector: "mcms-card" }, { kind: "component", type: CardContent, selector: "mcms-card-content" }, { kind: "component", type: CardFooter, selector: "mcms-card-footer" }, { kind: "component", type: Button, selector: "button[mcms-button], a[mcms-button]", inputs: ["variant", "size", "disabled", "loading", "ariaLabel", "class"] }, { kind: "component", type: Spinner, selector: "mcms-spinner", inputs: ["size", "label", "class"] }, { kind: "component", type: Alert, selector: "mcms-alert", inputs: ["variant", "class"] }, { kind: "component", type: FieldRenderer, selector: "mcms-field-renderer", inputs: ["field", "formNode", "formTree", "formModel", "mode", "path"] }, { kind: "component", type: Breadcrumbs, selector: "mcms-breadcrumbs", inputs: ["class"] }, { kind: "component", type: BreadcrumbItem, selector: "mcms-breadcrumb-item", inputs: ["href", "current", "class"] }, { kind: "component", type: BreadcrumbSeparator, selector: "mcms-breadcrumb-separator", inputs: ["class"] }, { kind: "component", type: VersionHistoryWidget, selector: "mcms-version-history", inputs: ["collection", "documentId", "documentLabel"], outputs: ["restored"] }, { kind: "component", type: PublishControlsWidget, selector: "mcms-publish-controls", inputs: ["collection", "documentId", "documentLabel", "initialStatus"], outputs: ["statusChanged"] }, { kind: "component", type: CollectionUploadZoneComponent, selector: "mcms-collection-upload-zone", inputs: ["uploadConfig", "disabled", "pendingFile", "isUploading", "uploadProgress", "error", "existingMedia"], outputs: ["fileSelected", "fileRemoved"] }, { kind: "component", type: FocalPointPickerComponent, selector: "mcms-focal-point-picker", inputs: ["imageUrl", "focalPoint", "alt", "naturalWidth", "naturalHeight", "imageSizes"], outputs: ["focalPointChange"] }, { kind: "component", type: ImageVariantsDisplay, selector: "mcms-image-variants-display", inputs: ["sizes"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6045
6545
|
}
|
|
6046
6546
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: EntityFormWidget, decorators: [{
|
|
6047
6547
|
type: Component,
|
|
@@ -6059,6 +6559,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
6059
6559
|
BreadcrumbItem,
|
|
6060
6560
|
BreadcrumbSeparator,
|
|
6061
6561
|
VersionHistoryWidget,
|
|
6562
|
+
PublishControlsWidget,
|
|
6062
6563
|
CollectionUploadZoneComponent,
|
|
6063
6564
|
FocalPointPickerComponent,
|
|
6064
6565
|
ImageVariantsDisplay,
|
|
@@ -6083,17 +6584,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
6083
6584
|
<div class="mb-8">
|
|
6084
6585
|
<div class="flex items-start justify-between gap-4">
|
|
6085
6586
|
<div>
|
|
6086
|
-
<
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6587
|
+
<div class="flex items-center gap-3">
|
|
6588
|
+
<h1 class="text-2xl font-semibold tracking-tight">
|
|
6589
|
+
@if (isGlobal()) {
|
|
6590
|
+
{{ collectionLabelSingular() }}
|
|
6591
|
+
} @else if (mode() === 'create') {
|
|
6592
|
+
Create {{ collectionLabelSingular() }}
|
|
6593
|
+
} @else if (mode() === 'edit') {
|
|
6594
|
+
Edit {{ collectionLabelSingular() }}
|
|
6595
|
+
} @else {
|
|
6596
|
+
View {{ collectionLabelSingular() }}
|
|
6597
|
+
}
|
|
6598
|
+
</h1>
|
|
6599
|
+
@if (hasVersioning() && mode() === 'edit' && entityId()) {
|
|
6600
|
+
<mcms-publish-controls
|
|
6601
|
+
[collection]="collection().slug"
|
|
6602
|
+
[documentId]="entityId() ?? ''"
|
|
6603
|
+
[documentLabel]="collectionLabelSingular()"
|
|
6604
|
+
[initialStatus]="documentStatus()"
|
|
6605
|
+
(statusChanged)="onStatusChanged($event)"
|
|
6606
|
+
/>
|
|
6095
6607
|
}
|
|
6096
|
-
</
|
|
6608
|
+
</div>
|
|
6097
6609
|
<p class="mt-1 text-muted-foreground">
|
|
6098
6610
|
@if (isGlobal()) {
|
|
6099
6611
|
Manage {{ collectionLabelSingular().toLowerCase() }} settings.
|
|
@@ -6162,269 +6674,73 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
6162
6674
|
@for (field of visibleFields(); track field.name) {
|
|
6163
6675
|
<mcms-field-renderer
|
|
6164
6676
|
[field]="field"
|
|
6165
|
-
[formNode]="getFormNode(field.name)"
|
|
6166
|
-
[formTree]="entityForm()"
|
|
6167
|
-
[formModel]="formModel()"
|
|
6168
|
-
[mode]="mode()"
|
|
6169
|
-
[path]="field.name"
|
|
6170
|
-
/>
|
|
6171
|
-
}
|
|
6172
|
-
</div>
|
|
6173
|
-
}
|
|
6174
|
-
</mcms-card-content>
|
|
6175
|
-
|
|
6176
|
-
<mcms-card-footer class="flex justify-end gap-3 border-t bg-muted/50 px-6 py-4">
|
|
6177
|
-
@if (mode() !== 'view') {
|
|
6178
|
-
<button
|
|
6179
|
-
mcms-button
|
|
6180
|
-
variant="outline"
|
|
6181
|
-
[disabled]="isSubmitting() || isSavingDraft()"
|
|
6182
|
-
(click)="onCancel()"
|
|
6183
|
-
>
|
|
6184
|
-
Cancel
|
|
6185
|
-
</button>
|
|
6186
|
-
@if (canSaveDraft()) {
|
|
6187
|
-
<button
|
|
6188
|
-
mcms-button
|
|
6189
|
-
variant="outline"
|
|
6190
|
-
[disabled]="isSubmitting() || isSavingDraft()"
|
|
6191
|
-
(click)="onSaveDraft()"
|
|
6192
|
-
>
|
|
6193
|
-
@if (isSavingDraft()) {
|
|
6194
|
-
<mcms-spinner size="sm" class="mr-2" />
|
|
6195
|
-
}
|
|
6196
|
-
Save Draft
|
|
6197
|
-
</button>
|
|
6198
|
-
}
|
|
6199
|
-
<button
|
|
6200
|
-
mcms-button
|
|
6201
|
-
variant="primary"
|
|
6202
|
-
[disabled]="isSubmitting() || isSavingDraft()"
|
|
6203
|
-
(click)="onSubmit()"
|
|
6204
|
-
>
|
|
6205
|
-
@if (isSubmitting()) {
|
|
6206
|
-
<mcms-spinner size="sm" class="mr-2" />
|
|
6207
|
-
}
|
|
6208
|
-
{{ mode() === 'create' ? 'Create' : 'Save Changes' }}
|
|
6209
|
-
</button>
|
|
6210
|
-
} @else {
|
|
6211
|
-
@if (canEdit()) {
|
|
6212
|
-
<button mcms-button variant="primary" (click)="switchToEdit()">Edit</button>
|
|
6213
|
-
}
|
|
6214
|
-
}
|
|
6215
|
-
</mcms-card-footer>
|
|
6216
|
-
</mcms-card>
|
|
6217
|
-
|
|
6218
|
-
@if (hasVersioning() && mode() === 'edit' && entityId()) {
|
|
6219
|
-
<div class="mt-8">
|
|
6220
|
-
<mcms-version-history
|
|
6221
|
-
[collection]="collection().slug"
|
|
6222
|
-
[documentId]="entityId()!"
|
|
6223
|
-
[documentLabel]="collectionLabelSingular()"
|
|
6224
|
-
(restored)="onVersionRestored()"
|
|
6225
|
-
/>
|
|
6226
|
-
</div>
|
|
6227
|
-
}
|
|
6228
|
-
</div>
|
|
6229
|
-
`,
|
|
6230
|
-
}]
|
|
6231
|
-
}], ctorParameters: () => [], propDecorators: { collection: [{ type: i0.Input, args: [{ isSignal: true, alias: "collection", required: true }] }], entityId: [{ type: i0.Input, args: [{ isSignal: true, alias: "entityId", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], basePath: [{ type: i0.Input, args: [{ isSignal: true, alias: "basePath", required: false }] }], showBreadcrumbs: [{ type: i0.Input, args: [{ isSignal: true, alias: "showBreadcrumbs", required: false }] }], suppressNavigation: [{ type: i0.Input, args: [{ isSignal: true, alias: "suppressNavigation", required: false }] }], isGlobal: [{ type: i0.Input, args: [{ isSignal: true, alias: "isGlobal", required: false }] }], globalSlug: [{ type: i0.Input, args: [{ isSignal: true, alias: "globalSlug", required: false }] }], saved: [{ type: i0.Output, args: ["saved"] }], cancelled: [{ type: i0.Output, args: ["cancelled"] }], saveError: [{ type: i0.Output, args: ["saveError"] }], modeChange: [{ type: i0.Output, args: ["modeChange"] }], draftSaved: [{ type: i0.Output, args: ["draftSaved"] }] } });
|
|
6232
|
-
|
|
6233
|
-
/**
|
|
6234
|
-
* Publish Controls Widget
|
|
6235
|
-
*
|
|
6236
|
-
* Displays the current document status and provides publish/unpublish actions.
|
|
6237
|
-
*
|
|
6238
|
-
* @example
|
|
6239
|
-
* ```html
|
|
6240
|
-
* <mcms-publish-controls
|
|
6241
|
-
* [collection]="'posts'"
|
|
6242
|
-
* [documentId]="'abc123'"
|
|
6243
|
-
* [documentLabel]="'Post'"
|
|
6244
|
-
* (statusChanged)="onStatusChanged($event)"
|
|
6245
|
-
* />
|
|
6246
|
-
* ```
|
|
6247
|
-
*/
|
|
6248
|
-
class PublishControlsWidget {
|
|
6249
|
-
versionService = inject(VersionService);
|
|
6250
|
-
feedback = inject(FeedbackService);
|
|
6251
|
-
/** Collection slug */
|
|
6252
|
-
collection = input.required(...(ngDevMode ? [{ debugName: "collection" }] : []));
|
|
6253
|
-
/** Document ID */
|
|
6254
|
-
documentId = input.required(...(ngDevMode ? [{ debugName: "documentId" }] : []));
|
|
6255
|
-
/** Document label for feedback messages */
|
|
6256
|
-
documentLabel = input('Document', ...(ngDevMode ? [{ debugName: "documentLabel" }] : []));
|
|
6257
|
-
/** Initial status (optional, will be fetched if not provided) */
|
|
6258
|
-
initialStatus = input(undefined, ...(ngDevMode ? [{ debugName: "initialStatus" }] : []));
|
|
6259
|
-
/** Emitted when the status changes */
|
|
6260
|
-
statusChanged = output();
|
|
6261
|
-
/** Current status */
|
|
6262
|
-
status = signal('draft', ...(ngDevMode ? [{ debugName: "status" }] : []));
|
|
6263
|
-
/** Whether a status update is in progress */
|
|
6264
|
-
isUpdating = signal(false, ...(ngDevMode ? [{ debugName: "isUpdating" }] : []));
|
|
6265
|
-
/** Whether status is loading */
|
|
6266
|
-
isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
|
|
6267
|
-
/** Badge variant based on status (derived from status signal) */
|
|
6268
|
-
statusVariant = computed(() => this.status() === 'published' ? 'default' : 'secondary', ...(ngDevMode ? [{ debugName: "statusVariant" }] : []));
|
|
6269
|
-
/** Status label (derived from status signal) */
|
|
6270
|
-
statusLabel = computed(() => (this.status() === 'published' ? 'Published' : 'Draft'), ...(ngDevMode ? [{ debugName: "statusLabel" }] : []));
|
|
6271
|
-
constructor() {
|
|
6272
|
-
// Load status when inputs change
|
|
6273
|
-
effect(() => {
|
|
6274
|
-
const collection = this.collection();
|
|
6275
|
-
const docId = this.documentId();
|
|
6276
|
-
const initial = this.initialStatus();
|
|
6277
|
-
if (initial !== undefined) {
|
|
6278
|
-
this.updateStatusDisplay(initial);
|
|
6279
|
-
}
|
|
6280
|
-
else if (collection && docId) {
|
|
6281
|
-
this.loadStatus(collection, docId);
|
|
6282
|
-
}
|
|
6283
|
-
});
|
|
6284
|
-
}
|
|
6285
|
-
/**
|
|
6286
|
-
* Load the current status from the API.
|
|
6287
|
-
*/
|
|
6288
|
-
async loadStatus(collection, docId) {
|
|
6289
|
-
this.isLoading.set(true);
|
|
6290
|
-
try {
|
|
6291
|
-
const status = await this.versionService.getStatus(collection, docId);
|
|
6292
|
-
this.updateStatusDisplay(status);
|
|
6293
|
-
}
|
|
6294
|
-
catch {
|
|
6295
|
-
// Default to draft if we can't load status
|
|
6296
|
-
this.updateStatusDisplay('draft');
|
|
6297
|
-
}
|
|
6298
|
-
finally {
|
|
6299
|
-
this.isLoading.set(false);
|
|
6300
|
-
}
|
|
6301
|
-
}
|
|
6302
|
-
/**
|
|
6303
|
-
* Update the status display.
|
|
6304
|
-
* statusVariant and statusLabel are computed signals that automatically update.
|
|
6305
|
-
*/
|
|
6306
|
-
updateStatusDisplay(status) {
|
|
6307
|
-
this.status.set(status);
|
|
6308
|
-
}
|
|
6309
|
-
/**
|
|
6310
|
-
* Publish the document.
|
|
6311
|
-
*/
|
|
6312
|
-
async onPublish() {
|
|
6313
|
-
this.isUpdating.set(true);
|
|
6314
|
-
try {
|
|
6315
|
-
await this.versionService.publish(this.collection(), this.documentId());
|
|
6316
|
-
this.updateStatusDisplay('published');
|
|
6317
|
-
this.statusChanged.emit('published');
|
|
6318
|
-
}
|
|
6319
|
-
catch {
|
|
6320
|
-
// Error handled by crudToastInterceptor
|
|
6321
|
-
}
|
|
6322
|
-
finally {
|
|
6323
|
-
this.isUpdating.set(false);
|
|
6324
|
-
}
|
|
6325
|
-
}
|
|
6326
|
-
/**
|
|
6327
|
-
* Unpublish the document.
|
|
6328
|
-
*/
|
|
6329
|
-
async onUnpublish() {
|
|
6330
|
-
const confirmed = await this.feedback.confirmUnpublish(this.documentLabel());
|
|
6331
|
-
if (!confirmed) {
|
|
6332
|
-
return;
|
|
6333
|
-
}
|
|
6334
|
-
this.isUpdating.set(true);
|
|
6335
|
-
try {
|
|
6336
|
-
await this.versionService.unpublish(this.collection(), this.documentId());
|
|
6337
|
-
this.updateStatusDisplay('draft');
|
|
6338
|
-
this.statusChanged.emit('draft');
|
|
6339
|
-
}
|
|
6340
|
-
catch {
|
|
6341
|
-
// Error handled by crudToastInterceptor
|
|
6342
|
-
}
|
|
6343
|
-
finally {
|
|
6344
|
-
this.isUpdating.set(false);
|
|
6345
|
-
}
|
|
6346
|
-
}
|
|
6347
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PublishControlsWidget, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6348
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: PublishControlsWidget, isStandalone: true, selector: "mcms-publish-controls", inputs: { collection: { classPropertyName: "collection", publicName: "collection", isSignal: true, isRequired: true, transformFunction: null }, documentId: { classPropertyName: "documentId", publicName: "documentId", isSignal: true, isRequired: true, transformFunction: null }, documentLabel: { classPropertyName: "documentLabel", publicName: "documentLabel", isSignal: true, isRequired: false, transformFunction: null }, initialStatus: { classPropertyName: "initialStatus", publicName: "initialStatus", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { statusChanged: "statusChanged" }, host: { classAttribute: "inline-flex items-center gap-3" }, ngImport: i0, template: `
|
|
6349
|
-
<mcms-badge [variant]="statusVariant()">
|
|
6350
|
-
{{ statusLabel() }}
|
|
6351
|
-
</mcms-badge>
|
|
6352
|
-
|
|
6353
|
-
@if (status() === 'draft') {
|
|
6354
|
-
<button
|
|
6355
|
-
mcms-button
|
|
6356
|
-
variant="primary"
|
|
6357
|
-
size="sm"
|
|
6358
|
-
[disabled]="isUpdating()"
|
|
6359
|
-
(click)="onPublish()"
|
|
6360
|
-
>
|
|
6361
|
-
@if (isUpdating()) {
|
|
6362
|
-
Publishing...
|
|
6363
|
-
} @else {
|
|
6364
|
-
Publish
|
|
6365
|
-
}
|
|
6366
|
-
</button>
|
|
6367
|
-
} @else {
|
|
6368
|
-
<button
|
|
6369
|
-
mcms-button
|
|
6370
|
-
variant="outline"
|
|
6371
|
-
size="sm"
|
|
6372
|
-
[disabled]="isUpdating()"
|
|
6373
|
-
(click)="onUnpublish()"
|
|
6374
|
-
>
|
|
6375
|
-
@if (isUpdating()) {
|
|
6376
|
-
Unpublishing...
|
|
6377
|
-
} @else {
|
|
6378
|
-
Unpublish
|
|
6379
|
-
}
|
|
6380
|
-
</button>
|
|
6381
|
-
}
|
|
6382
|
-
`, isInline: true, dependencies: [{ kind: "component", type: Badge, selector: "mcms-badge", inputs: ["variant", "class", "role", "ariaLabel"] }, { kind: "component", type: Button, selector: "button[mcms-button], a[mcms-button]", inputs: ["variant", "size", "disabled", "loading", "ariaLabel", "class"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6383
|
-
}
|
|
6384
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PublishControlsWidget, decorators: [{
|
|
6385
|
-
type: Component,
|
|
6386
|
-
args: [{
|
|
6387
|
-
selector: 'mcms-publish-controls',
|
|
6388
|
-
imports: [Badge, Button],
|
|
6389
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
6390
|
-
host: { class: 'inline-flex items-center gap-3' },
|
|
6391
|
-
template: `
|
|
6392
|
-
<mcms-badge [variant]="statusVariant()">
|
|
6393
|
-
{{ statusLabel() }}
|
|
6394
|
-
</mcms-badge>
|
|
6677
|
+
[formNode]="getFormNode(field.name)"
|
|
6678
|
+
[formTree]="entityForm()"
|
|
6679
|
+
[formModel]="formModel()"
|
|
6680
|
+
[mode]="mode()"
|
|
6681
|
+
[path]="field.name"
|
|
6682
|
+
/>
|
|
6683
|
+
}
|
|
6684
|
+
</div>
|
|
6685
|
+
}
|
|
6686
|
+
</mcms-card-content>
|
|
6395
6687
|
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
|
|
6400
|
-
|
|
6401
|
-
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
6407
|
-
|
|
6408
|
-
|
|
6409
|
-
|
|
6410
|
-
|
|
6411
|
-
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
|
|
6424
|
-
|
|
6688
|
+
<mcms-card-footer class="flex justify-end gap-3 border-t bg-muted/50 px-6 py-4">
|
|
6689
|
+
@if (mode() !== 'view') {
|
|
6690
|
+
<button
|
|
6691
|
+
mcms-button
|
|
6692
|
+
variant="outline"
|
|
6693
|
+
[disabled]="isSubmitting() || isSavingDraft()"
|
|
6694
|
+
(click)="onCancel()"
|
|
6695
|
+
>
|
|
6696
|
+
Cancel
|
|
6697
|
+
</button>
|
|
6698
|
+
@if (canSaveDraft()) {
|
|
6699
|
+
<button
|
|
6700
|
+
mcms-button
|
|
6701
|
+
variant="outline"
|
|
6702
|
+
[disabled]="isSubmitting() || isSavingDraft()"
|
|
6703
|
+
(click)="onSaveDraft()"
|
|
6704
|
+
>
|
|
6705
|
+
@if (isSavingDraft()) {
|
|
6706
|
+
<mcms-spinner size="sm" class="mr-2" />
|
|
6707
|
+
}
|
|
6708
|
+
Save Draft
|
|
6709
|
+
</button>
|
|
6710
|
+
}
|
|
6711
|
+
<button
|
|
6712
|
+
mcms-button
|
|
6713
|
+
variant="primary"
|
|
6714
|
+
[disabled]="isSubmitting() || isSavingDraft()"
|
|
6715
|
+
(click)="onSubmit()"
|
|
6716
|
+
>
|
|
6717
|
+
@if (isSubmitting()) {
|
|
6718
|
+
<mcms-spinner size="sm" class="mr-2" />
|
|
6719
|
+
}
|
|
6720
|
+
{{ mode() === 'create' ? 'Create' : 'Save Changes' }}
|
|
6721
|
+
</button>
|
|
6722
|
+
} @else {
|
|
6723
|
+
@if (canEdit()) {
|
|
6724
|
+
<button mcms-button variant="primary" (click)="switchToEdit()">Edit</button>
|
|
6725
|
+
}
|
|
6726
|
+
}
|
|
6727
|
+
</mcms-card-footer>
|
|
6728
|
+
</mcms-card>
|
|
6729
|
+
|
|
6730
|
+
@if (hasVersioning() && mode() === 'edit' && entityId()) {
|
|
6731
|
+
<div class="mt-8">
|
|
6732
|
+
<mcms-version-history
|
|
6733
|
+
[collection]="collection().slug"
|
|
6734
|
+
[documentId]="entityId() ?? ''"
|
|
6735
|
+
[documentLabel]="collectionLabelSingular()"
|
|
6736
|
+
(restored)="onVersionRestored()"
|
|
6737
|
+
/>
|
|
6738
|
+
</div>
|
|
6739
|
+
}
|
|
6740
|
+
</div>
|
|
6425
6741
|
`,
|
|
6426
6742
|
}]
|
|
6427
|
-
}], ctorParameters: () => [], propDecorators: { collection: [{ type: i0.Input, args: [{ isSignal: true, alias: "collection", required: true }] }],
|
|
6743
|
+
}], ctorParameters: () => [], propDecorators: { collection: [{ type: i0.Input, args: [{ isSignal: true, alias: "collection", required: true }] }], entityId: [{ type: i0.Input, args: [{ isSignal: true, alias: "entityId", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], basePath: [{ type: i0.Input, args: [{ isSignal: true, alias: "basePath", required: false }] }], showBreadcrumbs: [{ type: i0.Input, args: [{ isSignal: true, alias: "showBreadcrumbs", required: false }] }], suppressNavigation: [{ type: i0.Input, args: [{ isSignal: true, alias: "suppressNavigation", required: false }] }], isGlobal: [{ type: i0.Input, args: [{ isSignal: true, alias: "isGlobal", required: false }] }], globalSlug: [{ type: i0.Input, args: [{ isSignal: true, alias: "globalSlug", required: false }] }], saved: [{ type: i0.Output, args: ["saved"] }], cancelled: [{ type: i0.Output, args: ["cancelled"] }], saveError: [{ type: i0.Output, args: ["saveError"] }], modeChange: [{ type: i0.Output, args: ["modeChange"] }], draftSaved: [{ type: i0.Output, args: ["draftSaved"] }] } });
|
|
6428
6744
|
|
|
6429
6745
|
/**
|
|
6430
6746
|
* Entity View Widget
|
|
@@ -6691,18 +7007,23 @@ class EntityViewWidget {
|
|
|
6691
7007
|
const entity = await this.api
|
|
6692
7008
|
.collection(slug)
|
|
6693
7009
|
.findById(id, { depth: 1, withDeleted: this.hasSoftDelete() });
|
|
6694
|
-
if (entity) {
|
|
6695
|
-
this.entity.set(entity);
|
|
6696
|
-
this.resolveRelationships(entity);
|
|
6697
|
-
}
|
|
6698
|
-
else {
|
|
7010
|
+
if (!entity) {
|
|
6699
7011
|
this.loadError.set(`${this.collectionLabelSingular()} not found`);
|
|
6700
7012
|
this.feedback.entityNotFound(this.collectionLabelSingular());
|
|
7013
|
+
return;
|
|
6701
7014
|
}
|
|
7015
|
+
this.entity.set(entity);
|
|
7016
|
+
this.resolveRelationships(entity);
|
|
6702
7017
|
}
|
|
6703
7018
|
catch (err) {
|
|
6704
|
-
|
|
6705
|
-
|
|
7019
|
+
if (err instanceof Error && err.name === 'DocumentNotFoundError') {
|
|
7020
|
+
this.loadError.set(`${this.collectionLabelSingular()} not found`);
|
|
7021
|
+
this.feedback.entityNotFound(this.collectionLabelSingular());
|
|
7022
|
+
}
|
|
7023
|
+
else {
|
|
7024
|
+
this.loadError.set('Failed to load data');
|
|
7025
|
+
this.feedback.operationFailed('Load failed', err instanceof Error ? err : undefined);
|
|
7026
|
+
}
|
|
6706
7027
|
}
|
|
6707
7028
|
finally {
|
|
6708
7029
|
this.isLoading.set(false);
|
|
@@ -7530,6 +7851,78 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
7530
7851
|
}]
|
|
7531
7852
|
}] });
|
|
7532
7853
|
|
|
7854
|
+
/**
|
|
7855
|
+
* Renders all components registered for a named admin layout slot.
|
|
7856
|
+
*
|
|
7857
|
+
* Usage:
|
|
7858
|
+
* ```html
|
|
7859
|
+
* <mcms-admin-slot slot="dashboard:before" />
|
|
7860
|
+
* <mcms-admin-slot slot="collection-list:before" [collectionSlug]="slug" />
|
|
7861
|
+
* ```
|
|
7862
|
+
*/
|
|
7863
|
+
class AdminSlotOutlet {
|
|
7864
|
+
registry = inject(AdminSlotRegistry);
|
|
7865
|
+
/** The slot key to render (e.g., 'dashboard:before'). */
|
|
7866
|
+
slot = input.required(...(ngDevMode ? [{ debugName: "slot" }] : []));
|
|
7867
|
+
/** Optional collection slug for per-collection slot resolution. */
|
|
7868
|
+
collectionSlug = input(...(ngDevMode ? [undefined, { debugName: "collectionSlug" }] : []));
|
|
7869
|
+
/** Optional context passed as inputs to slot components. */
|
|
7870
|
+
context = input({}, ...(ngDevMode ? [{ debugName: "context" }] : []));
|
|
7871
|
+
/** Resolved component types after lazy loading. */
|
|
7872
|
+
resolvedComponents = signal([], ...(ngDevMode ? [{ debugName: "resolvedComponents" }] : []));
|
|
7873
|
+
/** Inputs to pass to each slot component. */
|
|
7874
|
+
slotInputs = computed(() => ({
|
|
7875
|
+
...this.context(),
|
|
7876
|
+
}), ...(ngDevMode ? [{ debugName: "slotInputs" }] : []));
|
|
7877
|
+
/** Incremented on every effect run to detect stale promise resolutions. */
|
|
7878
|
+
loadGeneration = 0;
|
|
7879
|
+
constructor() {
|
|
7880
|
+
effect(() => {
|
|
7881
|
+
const slot = this.slot();
|
|
7882
|
+
const slug = this.collectionSlug();
|
|
7883
|
+
const loaders = this.registry.resolve(slot, slug);
|
|
7884
|
+
if (loaders.length === 0) {
|
|
7885
|
+
this.resolvedComponents.set([]);
|
|
7886
|
+
return;
|
|
7887
|
+
}
|
|
7888
|
+
const generation = ++this.loadGeneration;
|
|
7889
|
+
Promise.all(loaders.map((loader) => loader()))
|
|
7890
|
+
.then((components) => {
|
|
7891
|
+
if (generation !== this.loadGeneration)
|
|
7892
|
+
return;
|
|
7893
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- loaders resolve to unknown from registry, safe cast to Type[]
|
|
7894
|
+
this.resolvedComponents.set(components);
|
|
7895
|
+
})
|
|
7896
|
+
.catch((err) => {
|
|
7897
|
+
if (generation !== this.loadGeneration)
|
|
7898
|
+
return;
|
|
7899
|
+
console.error('[AdminSlotOutlet] Failed to load slot components:', err);
|
|
7900
|
+
this.resolvedComponents.set([]);
|
|
7901
|
+
});
|
|
7902
|
+
});
|
|
7903
|
+
}
|
|
7904
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AdminSlotOutlet, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
7905
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: AdminSlotOutlet, isStandalone: true, selector: "mcms-admin-slot", inputs: { slot: { classPropertyName: "slot", publicName: "slot", isSignal: true, isRequired: true, transformFunction: null }, collectionSlug: { classPropertyName: "collectionSlug", publicName: "collectionSlug", isSignal: true, isRequired: false, transformFunction: null }, context: { classPropertyName: "context", publicName: "context", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block" }, ngImport: i0, template: `
|
|
7906
|
+
@for (component of resolvedComponents(); track $index) {
|
|
7907
|
+
<ng-container *ngComponentOutlet="component; inputs: slotInputs()" />
|
|
7908
|
+
}
|
|
7909
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
7910
|
+
}
|
|
7911
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AdminSlotOutlet, decorators: [{
|
|
7912
|
+
type: Component,
|
|
7913
|
+
args: [{
|
|
7914
|
+
selector: 'mcms-admin-slot',
|
|
7915
|
+
imports: [NgComponentOutlet],
|
|
7916
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
7917
|
+
host: { class: 'block' },
|
|
7918
|
+
template: `
|
|
7919
|
+
@for (component of resolvedComponents(); track $index) {
|
|
7920
|
+
<ng-container *ngComponentOutlet="component; inputs: slotInputs()" />
|
|
7921
|
+
}
|
|
7922
|
+
`,
|
|
7923
|
+
}]
|
|
7924
|
+
}], ctorParameters: () => [], propDecorators: { slot: [{ type: i0.Input, args: [{ isSignal: true, alias: "slot", required: true }] }], collectionSlug: [{ type: i0.Input, args: [{ isSignal: true, alias: "collectionSlug", required: false }] }], context: [{ type: i0.Input, args: [{ isSignal: true, alias: "context", required: false }] }] } });
|
|
7925
|
+
|
|
7533
7926
|
const DEFAULT_GROUP = 'Collections';
|
|
7534
7927
|
/**
|
|
7535
7928
|
* Slugify a group name into a valid HTML id attribute value.
|
|
@@ -7772,6 +8165,8 @@ class AdminSidebarWidget {
|
|
|
7772
8165
|
[exact]="true"
|
|
7773
8166
|
/>
|
|
7774
8167
|
|
|
8168
|
+
<mcms-admin-slot slot="shell:nav-start" />
|
|
8169
|
+
|
|
7775
8170
|
<!-- Collection Sections (grouped by admin.group) -->
|
|
7776
8171
|
@for (group of collectionGroups(); track group.id) {
|
|
7777
8172
|
<mcms-sidebar-section [title]="group.name">
|
|
@@ -7816,6 +8211,8 @@ class AdminSidebarWidget {
|
|
|
7816
8211
|
}
|
|
7817
8212
|
</mcms-sidebar-section>
|
|
7818
8213
|
}
|
|
8214
|
+
|
|
8215
|
+
<mcms-admin-slot slot="shell:nav-end" />
|
|
7819
8216
|
</mcms-sidebar-nav>
|
|
7820
8217
|
</div>
|
|
7821
8218
|
|
|
@@ -7863,7 +8260,7 @@ class AdminSidebarWidget {
|
|
|
7863
8260
|
}
|
|
7864
8261
|
</div>
|
|
7865
8262
|
</mcms-sidebar>
|
|
7866
|
-
`, isInline: true, dependencies: [{ kind: "component", type: Sidebar, selector: "mcms-sidebar", inputs: ["width", "collapsedWidth", "collapsed", "class"] }, { kind: "component", type: SidebarNav, selector: "mcms-sidebar-nav", inputs: ["ariaLabel", "class"] }, { kind: "component", type: SidebarNavItem, selector: "mcms-sidebar-nav-item", inputs: ["label", "href", "icon", "badge", "active", "disabled", "exact", "class"], outputs: ["clicked"] }, { kind: "component", type: SidebarSection, selector: "mcms-sidebar-section", inputs: ["title", "collapsible", "expanded", "class"], outputs: ["expandedChange"] }, { kind: "component", type: Avatar, selector: "mcms-avatar", inputs: ["size", "class", "ariaLabel"] }, { kind: "component", type: AvatarFallback, selector: "mcms-avatar-fallback", inputs: ["delayMs", "class"] }, { kind: "component", type: DropdownMenu, selector: "mcms-dropdown-menu", inputs: ["disabled", "wrap", "typeaheadDelay", "class"], outputs: ["itemSelected"] }, { kind: "component", type: DropdownMenuItem, selector: "button[mcms-dropdown-item], a[mcms-dropdown-item]", inputs: ["value", "disabled", "shortcut", "class"], outputs: ["selected"] }, { kind: "component", type: DropdownSeparator, selector: "mcms-dropdown-separator" }, { kind: "component", type: DropdownLabel, selector: "mcms-dropdown-label" }, { kind: "directive", type: DropdownTrigger, selector: "[mcmsDropdownTrigger]", inputs: ["mcmsDropdownTrigger", "dropdownSide", "dropdownAlign", "dropdownOffset", "dropdownDisabled"], outputs: ["opened", "closed"], exportAs: ["mcmsDropdownTrigger"] }, { kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
8263
|
+
`, isInline: true, dependencies: [{ kind: "component", type: Sidebar, selector: "mcms-sidebar", inputs: ["width", "collapsedWidth", "collapsed", "class"] }, { kind: "component", type: SidebarNav, selector: "mcms-sidebar-nav", inputs: ["ariaLabel", "class"] }, { kind: "component", type: SidebarNavItem, selector: "mcms-sidebar-nav-item", inputs: ["label", "href", "icon", "badge", "active", "disabled", "exact", "class"], outputs: ["clicked"] }, { kind: "component", type: SidebarSection, selector: "mcms-sidebar-section", inputs: ["title", "collapsible", "expanded", "class"], outputs: ["expandedChange"] }, { kind: "component", type: Avatar, selector: "mcms-avatar", inputs: ["size", "class", "ariaLabel"] }, { kind: "component", type: AvatarFallback, selector: "mcms-avatar-fallback", inputs: ["delayMs", "class"] }, { kind: "component", type: DropdownMenu, selector: "mcms-dropdown-menu", inputs: ["disabled", "wrap", "typeaheadDelay", "class"], outputs: ["itemSelected"] }, { kind: "component", type: DropdownMenuItem, selector: "button[mcms-dropdown-item], a[mcms-dropdown-item]", inputs: ["value", "disabled", "shortcut", "class"], outputs: ["selected"] }, { kind: "component", type: DropdownSeparator, selector: "mcms-dropdown-separator" }, { kind: "component", type: DropdownLabel, selector: "mcms-dropdown-label" }, { kind: "directive", type: DropdownTrigger, selector: "[mcmsDropdownTrigger]", inputs: ["mcmsDropdownTrigger", "dropdownSide", "dropdownAlign", "dropdownOffset", "dropdownDisabled"], outputs: ["opened", "closed"], exportAs: ["mcmsDropdownTrigger"] }, { kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "component", type: AdminSlotOutlet, selector: "mcms-admin-slot", inputs: ["slot", "collectionSlug", "context"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
7867
8264
|
}
|
|
7868
8265
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AdminSidebarWidget, decorators: [{
|
|
7869
8266
|
type: Component,
|
|
@@ -7882,6 +8279,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
7882
8279
|
DropdownLabel,
|
|
7883
8280
|
DropdownTrigger,
|
|
7884
8281
|
NgIcon,
|
|
8282
|
+
AdminSlotOutlet,
|
|
7885
8283
|
],
|
|
7886
8284
|
providers: [
|
|
7887
8285
|
provideIcons({
|
|
@@ -7947,6 +8345,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
7947
8345
|
[exact]="true"
|
|
7948
8346
|
/>
|
|
7949
8347
|
|
|
8348
|
+
<mcms-admin-slot slot="shell:nav-start" />
|
|
8349
|
+
|
|
7950
8350
|
<!-- Collection Sections (grouped by admin.group) -->
|
|
7951
8351
|
@for (group of collectionGroups(); track group.id) {
|
|
7952
8352
|
<mcms-sidebar-section [title]="group.name">
|
|
@@ -7991,6 +8391,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
7991
8391
|
}
|
|
7992
8392
|
</mcms-sidebar-section>
|
|
7993
8393
|
}
|
|
8394
|
+
|
|
8395
|
+
<mcms-admin-slot slot="shell:nav-end" />
|
|
7994
8396
|
</mcms-sidebar-nav>
|
|
7995
8397
|
</div>
|
|
7996
8398
|
|
|
@@ -8061,6 +8463,8 @@ class AdminShellComponent {
|
|
|
8061
8463
|
auth = inject(MomentumAuthService);
|
|
8062
8464
|
collectionAccess = inject(CollectionAccessService);
|
|
8063
8465
|
sidebar = inject(SidebarService);
|
|
8466
|
+
componentRegistry = inject(AdminComponentRegistry);
|
|
8467
|
+
slotRegistry = inject(AdminSlotRegistry);
|
|
8064
8468
|
entitySheet = inject(EntitySheetService);
|
|
8065
8469
|
/** All collections from route data */
|
|
8066
8470
|
allCollections = computed(() => {
|
|
@@ -8110,6 +8514,23 @@ class AdminShellComponent {
|
|
|
8110
8514
|
};
|
|
8111
8515
|
}, ...(ngDevMode ? [{ debugName: "sidebarUser" }] : []));
|
|
8112
8516
|
ngOnInit() {
|
|
8517
|
+
// Register config-level component overrides and slot registrations.
|
|
8518
|
+
// This reads from route data (which has the full MomentumConfig-derived data)
|
|
8519
|
+
// and bridges config declarations into the runtime registries.
|
|
8520
|
+
const routeData = this.route.snapshot.data;
|
|
8521
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- route data is Record<string, unknown>
|
|
8522
|
+
const adminComponents = routeData['adminComponents'];
|
|
8523
|
+
registerConfigComponents(this.allCollections(), adminComponents, this.componentRegistry, this.slotRegistry);
|
|
8524
|
+
// Also register plugin-declared admin components
|
|
8525
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- route data is Record<string, unknown>
|
|
8526
|
+
const plugins = routeData['plugins'];
|
|
8527
|
+
if (plugins) {
|
|
8528
|
+
for (const plugin of plugins) {
|
|
8529
|
+
if (plugin.adminComponents) {
|
|
8530
|
+
registerConfigComponents([], plugin.adminComponents, this.componentRegistry, this.slotRegistry);
|
|
8531
|
+
}
|
|
8532
|
+
}
|
|
8533
|
+
}
|
|
8113
8534
|
// Keyboard shortcuts, auth, and sheet restoration only run in the browser.
|
|
8114
8535
|
// SSR user is provided via MOMENTUM_API_CONTEXT (used by injectUser above).
|
|
8115
8536
|
if (!isPlatformBrowser(this.platformId)) {
|
|
@@ -8185,9 +8606,11 @@ class AdminShellComponent {
|
|
|
8185
8606
|
|
|
8186
8607
|
<!-- Main Content (with top padding on mobile for header, normal padding at md+) -->
|
|
8187
8608
|
<main id="mcms-main-content" class="flex-1 p-8 overflow-y-auto overflow-x-hidden pt-20 md:pt-8">
|
|
8609
|
+
<mcms-admin-slot slot="shell:header" />
|
|
8188
8610
|
@defer (hydrate on immediate) {
|
|
8189
8611
|
<router-outlet />
|
|
8190
8612
|
}
|
|
8613
|
+
<mcms-admin-slot slot="shell:footer" />
|
|
8191
8614
|
</main>
|
|
8192
8615
|
|
|
8193
8616
|
<mcms-toast-container />
|
|
@@ -8222,7 +8645,7 @@ class AdminShellComponent {
|
|
|
8222
8645
|
</div>
|
|
8223
8646
|
</div>
|
|
8224
8647
|
}
|
|
8225
|
-
`, isInline: true, styles: ["@keyframes mcms-fade-in{0%{opacity:0}to{opacity:1}}@keyframes mcms-fade-out{0%{opacity:1}to{opacity:0}}@keyframes mcms-slide-in-right{0%{transform:translate(100%)}to{transform:translate(0)}}@keyframes mcms-slide-out-right{0%{transform:translate(0)}to{transform:translate(100%)}}.sheet-backdrop{animation:mcms-fade-in .15s ease-out}.sheet-backdrop-closing{animation:mcms-fade-out .15s ease-in forwards}.sheet-panel{animation:mcms-slide-in-right .2s ease-out}.sheet-panel-closing{animation:mcms-slide-out-right .2s ease-in forwards}\n"], dependencies: [{ kind: "component", type: AdminSidebarWidget, selector: "mcms-admin-sidebar", inputs: ["branding", "collections", "globals", "pluginRoutes", "user", "basePath", "collapsed", "width"], outputs: ["signOut"] }, { kind: "component", type: SidebarTrigger, selector: "mcms-sidebar-trigger", inputs: ["class"] }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: i1.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "component", type: EntitySheetContentComponent, selector: "mcms-entity-sheet-content" }, { kind: "component", type: ToastContainer, selector: "mcms-toast-container" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, deferBlockDependencies: [() => [RouterOutlet]] });
|
|
8648
|
+
`, isInline: true, styles: ["@keyframes mcms-fade-in{0%{opacity:0}to{opacity:1}}@keyframes mcms-fade-out{0%{opacity:1}to{opacity:0}}@keyframes mcms-slide-in-right{0%{transform:translate(100%)}to{transform:translate(0)}}@keyframes mcms-slide-out-right{0%{transform:translate(0)}to{transform:translate(100%)}}.sheet-backdrop{animation:mcms-fade-in .15s ease-out}.sheet-backdrop-closing{animation:mcms-fade-out .15s ease-in forwards}.sheet-panel{animation:mcms-slide-in-right .2s ease-out}.sheet-panel-closing{animation:mcms-slide-out-right .2s ease-in forwards}\n"], dependencies: [{ kind: "component", type: AdminSidebarWidget, selector: "mcms-admin-sidebar", inputs: ["branding", "collections", "globals", "pluginRoutes", "user", "basePath", "collapsed", "width"], outputs: ["signOut"] }, { kind: "component", type: SidebarTrigger, selector: "mcms-sidebar-trigger", inputs: ["class"] }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: i1.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "component", type: EntitySheetContentComponent, selector: "mcms-entity-sheet-content" }, { kind: "component", type: ToastContainer, selector: "mcms-toast-container" }, { kind: "component", type: AdminSlotOutlet, selector: "mcms-admin-slot", inputs: ["slot", "collectionSlug", "context"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, deferBlockDependencies: [() => [RouterOutlet]] });
|
|
8226
8649
|
}
|
|
8227
8650
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AdminShellComponent, decorators: [{
|
|
8228
8651
|
type: Component,
|
|
@@ -8233,6 +8656,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
8233
8656
|
A11yModule,
|
|
8234
8657
|
EntitySheetContentComponent,
|
|
8235
8658
|
ToastContainer,
|
|
8659
|
+
AdminSlotOutlet,
|
|
8236
8660
|
], changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
8237
8661
|
class: 'flex h-screen overflow-hidden bg-background',
|
|
8238
8662
|
'(document:keydown.escape)': 'onEscapeKey()',
|
|
@@ -8265,9 +8689,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
8265
8689
|
|
|
8266
8690
|
<!-- Main Content (with top padding on mobile for header, normal padding at md+) -->
|
|
8267
8691
|
<main id="mcms-main-content" class="flex-1 p-8 overflow-y-auto overflow-x-hidden pt-20 md:pt-8">
|
|
8692
|
+
<mcms-admin-slot slot="shell:header" />
|
|
8268
8693
|
@defer (hydrate on immediate) {
|
|
8269
8694
|
<router-outlet />
|
|
8270
8695
|
}
|
|
8696
|
+
<mcms-admin-slot slot="shell:footer" />
|
|
8271
8697
|
</main>
|
|
8272
8698
|
|
|
8273
8699
|
<mcms-toast-container />
|
|
@@ -9090,6 +9516,8 @@ class DashboardPage {
|
|
|
9090
9516
|
collectionGroups = computed(() => groupCollections(this.collections()), ...(ngDevMode ? [{ debugName: "collectionGroups" }] : []));
|
|
9091
9517
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DashboardPage, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
9092
9518
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: DashboardPage, isStandalone: true, selector: "mcms-dashboard", host: { classAttribute: "block max-w-6xl" }, ngImport: i0, template: `
|
|
9519
|
+
<mcms-admin-slot slot="dashboard:before" />
|
|
9520
|
+
|
|
9093
9521
|
<header class="mb-10">
|
|
9094
9522
|
<h1 class="text-4xl font-bold tracking-tight text-foreground">Dashboard</h1>
|
|
9095
9523
|
<p class="text-muted-foreground mt-3 text-lg">Manage your content and collections</p>
|
|
@@ -9139,16 +9567,20 @@ class DashboardPage {
|
|
|
9139
9567
|
</section>
|
|
9140
9568
|
}
|
|
9141
9569
|
}
|
|
9142
|
-
|
|
9570
|
+
|
|
9571
|
+
<mcms-admin-slot slot="dashboard:after" />
|
|
9572
|
+
`, isInline: true, dependencies: [{ kind: "component", type: CollectionCardWidget, selector: "mcms-collection-card", inputs: ["collection", "basePath", "showDocumentCount"], outputs: ["viewAll"] }, { kind: "component", type: AdminSlotOutlet, selector: "mcms-admin-slot", inputs: ["slot", "collectionSlug", "context"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
9143
9573
|
}
|
|
9144
9574
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DashboardPage, decorators: [{
|
|
9145
9575
|
type: Component,
|
|
9146
9576
|
args: [{
|
|
9147
9577
|
selector: 'mcms-dashboard',
|
|
9148
|
-
imports: [CollectionCardWidget],
|
|
9578
|
+
imports: [CollectionCardWidget, AdminSlotOutlet],
|
|
9149
9579
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
9150
9580
|
host: { class: 'block max-w-6xl' },
|
|
9151
9581
|
template: `
|
|
9582
|
+
<mcms-admin-slot slot="dashboard:before" />
|
|
9583
|
+
|
|
9152
9584
|
<header class="mb-10">
|
|
9153
9585
|
<h1 class="text-4xl font-bold tracking-tight text-foreground">Dashboard</h1>
|
|
9154
9586
|
<p class="text-muted-foreground mt-3 text-lg">Manage your content and collections</p>
|
|
@@ -9198,6 +9630,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
9198
9630
|
</section>
|
|
9199
9631
|
}
|
|
9200
9632
|
}
|
|
9633
|
+
|
|
9634
|
+
<mcms-admin-slot slot="dashboard:after" />
|
|
9201
9635
|
`,
|
|
9202
9636
|
}]
|
|
9203
9637
|
}] });
|
|
@@ -9344,6 +9778,8 @@ class EntityListWidget {
|
|
|
9344
9778
|
route = inject(ActivatedRoute);
|
|
9345
9779
|
/** Template ref for complex cell rendering (group, array, json). */
|
|
9346
9780
|
complexCellTemplate = viewChild('complexCell', ...(ngDevMode ? [{ debugName: "complexCellTemplate" }] : []));
|
|
9781
|
+
/** Template ref for badge cell rendering (_status column). */
|
|
9782
|
+
badgeCellTemplate = viewChild('badgeCell', ...(ngDevMode ? [{ debugName: "badgeCellTemplate" }] : []));
|
|
9347
9783
|
/** The collection configuration */
|
|
9348
9784
|
collection = input.required(...(ngDevMode ? [{ debugName: "collection" }] : []));
|
|
9349
9785
|
/** Base path for entity routes */
|
|
@@ -9395,8 +9831,17 @@ class EntityListWidget {
|
|
|
9395
9831
|
selectedEntities = signal([], ...(ngDevMode ? [{ debugName: "selectedEntities" }] : []));
|
|
9396
9832
|
searchQuery = model('', ...(ngDevMode ? [{ debugName: "searchQuery" }] : []));
|
|
9397
9833
|
viewingTrash = signal(false, ...(ngDevMode ? [{ debugName: "viewingTrash" }] : []));
|
|
9834
|
+
statusFilter = signal(null, ...(ngDevMode ? [{ debugName: "statusFilter" }] : []));
|
|
9835
|
+
/** Status filter options for versioned collections */
|
|
9836
|
+
statusFilterOptions = [
|
|
9837
|
+
{ label: 'All', value: null },
|
|
9838
|
+
{ label: 'Draft', value: 'draft' },
|
|
9839
|
+
{ label: 'Published', value: 'published' },
|
|
9840
|
+
];
|
|
9398
9841
|
/** Whether the collection has soft delete enabled */
|
|
9399
9842
|
hasSoftDelete = computed(() => getSoftDeleteField(this.collection()) !== null, ...(ngDevMode ? [{ debugName: "hasSoftDelete" }] : []));
|
|
9843
|
+
/** Whether the collection has versioning with drafts enabled */
|
|
9844
|
+
hasVersioning = computed(() => hasVersionDrafts(this.collection()), ...(ngDevMode ? [{ debugName: "hasVersioning" }] : []));
|
|
9400
9845
|
/** Computed collection label */
|
|
9401
9846
|
collectionLabel = computed(() => {
|
|
9402
9847
|
const col = this.collection();
|
|
@@ -9414,8 +9859,9 @@ class EntityListWidget {
|
|
|
9414
9859
|
}, ...(ngDevMode ? [{ debugName: "dashboardPath" }] : []));
|
|
9415
9860
|
/** Auto-derive columns from collection fields if not provided */
|
|
9416
9861
|
tableColumns = computed(() => {
|
|
9417
|
-
// Read template
|
|
9862
|
+
// Read template signals at top level so the computed re-runs when viewChild resolves
|
|
9418
9863
|
const complexTemplate = this.complexCellTemplate();
|
|
9864
|
+
const badgeTemplate = this.badgeCellTemplate();
|
|
9419
9865
|
const customColumns = this.columns();
|
|
9420
9866
|
if (customColumns.length > 0) {
|
|
9421
9867
|
return customColumns;
|
|
@@ -9433,6 +9879,22 @@ class EntityListWidget {
|
|
|
9433
9879
|
if (columns.length >= 5)
|
|
9434
9880
|
break;
|
|
9435
9881
|
}
|
|
9882
|
+
// Add _status badge column for versioned collections with drafts
|
|
9883
|
+
if (hasVersionDrafts(col)) {
|
|
9884
|
+
columns.push({
|
|
9885
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
9886
|
+
field: '_status',
|
|
9887
|
+
header: 'Status',
|
|
9888
|
+
sortable: true,
|
|
9889
|
+
type: 'badge',
|
|
9890
|
+
width: '110px',
|
|
9891
|
+
badgeMap: {
|
|
9892
|
+
draft: { label: 'Draft', variant: 'secondary' },
|
|
9893
|
+
published: { label: 'Published', variant: 'default' },
|
|
9894
|
+
},
|
|
9895
|
+
...(badgeTemplate ? { template: badgeTemplate } : {}),
|
|
9896
|
+
});
|
|
9897
|
+
}
|
|
9436
9898
|
// Add deletedAt column when viewing trash
|
|
9437
9899
|
if (this.viewingTrash() && this.hasSoftDelete()) {
|
|
9438
9900
|
const softDeleteField = getSoftDeleteField(col) ?? 'deletedAt';
|
|
@@ -9486,6 +9948,11 @@ class EntityListWidget {
|
|
|
9486
9948
|
if (typeof initialSearch === 'string') {
|
|
9487
9949
|
this.searchQuery.set(initialSearch);
|
|
9488
9950
|
}
|
|
9951
|
+
// Initialize status filter from URL
|
|
9952
|
+
const initialStatus = queryParams['status'];
|
|
9953
|
+
if (initialStatus === 'draft' || initialStatus === 'published') {
|
|
9954
|
+
this.statusFilter.set(initialStatus);
|
|
9955
|
+
}
|
|
9489
9956
|
// Initialize sort from URL (format: "field" for asc, "-field" for desc)
|
|
9490
9957
|
const initialSort = queryParams['sort'];
|
|
9491
9958
|
if (typeof initialSort === 'string' && initialSort) {
|
|
@@ -9497,11 +9964,12 @@ class EntityListWidget {
|
|
|
9497
9964
|
direction: isDesc ? 'desc' : 'asc',
|
|
9498
9965
|
});
|
|
9499
9966
|
}
|
|
9500
|
-
// Load data when collection, pagination,
|
|
9967
|
+
// Load data when collection, pagination, trash view, or status filter changes
|
|
9501
9968
|
effect(() => {
|
|
9502
9969
|
const col = this.collection();
|
|
9503
9970
|
const page = this.currentPage();
|
|
9504
9971
|
const trash = this.viewingTrash();
|
|
9972
|
+
const status = this.statusFilter();
|
|
9505
9973
|
let sortState = this.sort();
|
|
9506
9974
|
let search = this.searchQuery();
|
|
9507
9975
|
if (col) {
|
|
@@ -9511,24 +9979,25 @@ class EntityListWidget {
|
|
|
9511
9979
|
this.sort.set(undefined);
|
|
9512
9980
|
this.currentPage.set(1);
|
|
9513
9981
|
this.viewingTrash.set(false);
|
|
9982
|
+
this.statusFilter.set(null);
|
|
9514
9983
|
search = '';
|
|
9515
9984
|
sortState = undefined;
|
|
9516
9985
|
// Clear URL params
|
|
9517
9986
|
this.router.navigate([], {
|
|
9518
|
-
queryParams: { search: null, sort: null },
|
|
9987
|
+
queryParams: { search: null, sort: null, status: null },
|
|
9519
9988
|
queryParamsHandling: 'merge',
|
|
9520
9989
|
replaceUrl: true,
|
|
9521
9990
|
});
|
|
9522
9991
|
}
|
|
9523
9992
|
this.previousCollectionSlug = col.slug;
|
|
9524
|
-
this.loadData(col.slug, page, sortState, search, trash);
|
|
9993
|
+
this.loadData(col.slug, page, sortState, search, trash, status);
|
|
9525
9994
|
}
|
|
9526
9995
|
});
|
|
9527
9996
|
}
|
|
9528
9997
|
/**
|
|
9529
9998
|
* Load data from API.
|
|
9530
9999
|
*/
|
|
9531
|
-
async loadData(slug, page, sortState, search, onlyDeleted) {
|
|
10000
|
+
async loadData(slug, page, sortState, search, onlyDeleted, statusFilter) {
|
|
9532
10001
|
this.loading.set(true);
|
|
9533
10002
|
this.error.set(null);
|
|
9534
10003
|
try {
|
|
@@ -9544,17 +10013,25 @@ class EntityListWidget {
|
|
|
9544
10013
|
options['sort'] =
|
|
9545
10014
|
sortState.direction === 'desc' ? `-${String(sortState.field)}` : String(sortState.field);
|
|
9546
10015
|
}
|
|
10016
|
+
// Build where clause combining search and status filter
|
|
10017
|
+
const whereConditions = [];
|
|
9547
10018
|
if (search) {
|
|
9548
|
-
// Build where clause for search
|
|
9549
10019
|
const searchFields = this.searchFields().length > 0 ? this.searchFields() : this.getDefaultSearchFields();
|
|
9550
10020
|
if (searchFields.length > 0) {
|
|
9551
|
-
|
|
9552
|
-
|
|
9553
|
-
|
|
9554
|
-
})),
|
|
9555
|
-
};
|
|
10021
|
+
whereConditions.push(...searchFields.map((field) => ({
|
|
10022
|
+
[field]: { contains: search },
|
|
10023
|
+
})));
|
|
9556
10024
|
}
|
|
9557
10025
|
}
|
|
10026
|
+
if (statusFilter) {
|
|
10027
|
+
options['where'] = {
|
|
10028
|
+
...(whereConditions.length > 0 ? { or: whereConditions } : {}),
|
|
10029
|
+
_status: { equals: statusFilter },
|
|
10030
|
+
};
|
|
10031
|
+
}
|
|
10032
|
+
else if (whereConditions.length > 0) {
|
|
10033
|
+
options['where'] = { or: whereConditions };
|
|
10034
|
+
}
|
|
9558
10035
|
const result = await this.api.collection(slug).find(options);
|
|
9559
10036
|
this.entities.set(result.docs);
|
|
9560
10037
|
this.totalItems.set(result.totalDocs);
|
|
@@ -9732,6 +10209,15 @@ class EntityListWidget {
|
|
|
9732
10209
|
}
|
|
9733
10210
|
}
|
|
9734
10211
|
}
|
|
10212
|
+
/**
|
|
10213
|
+
* Look up a badge configuration from a column's badgeMap.
|
|
10214
|
+
*/
|
|
10215
|
+
getBadgeConfig(value, column) {
|
|
10216
|
+
if (!column.badgeMap || value === null || value === undefined)
|
|
10217
|
+
return null;
|
|
10218
|
+
const key = String(value);
|
|
10219
|
+
return column.badgeMap[key] ?? null;
|
|
10220
|
+
}
|
|
9735
10221
|
/**
|
|
9736
10222
|
* Generate a brief summary string for complex field values (group, array, json).
|
|
9737
10223
|
*/
|
|
@@ -9864,7 +10350,7 @@ class EntityListWidget {
|
|
|
9864
10350
|
reload() {
|
|
9865
10351
|
const col = this.collection();
|
|
9866
10352
|
if (col) {
|
|
9867
|
-
this.loadData(col.slug, this.currentPage(), this.sort(), this.searchQuery(), this.viewingTrash());
|
|
10353
|
+
this.loadData(col.slug, this.currentPage(), this.sort(), this.searchQuery(), this.viewingTrash(), this.statusFilter());
|
|
9868
10354
|
}
|
|
9869
10355
|
}
|
|
9870
10356
|
/**
|
|
@@ -9875,6 +10361,18 @@ class EntityListWidget {
|
|
|
9875
10361
|
this.currentPage.set(1);
|
|
9876
10362
|
this.selectedEntities.set([]);
|
|
9877
10363
|
}
|
|
10364
|
+
/**
|
|
10365
|
+
* Set the status filter for versioned collections.
|
|
10366
|
+
*/
|
|
10367
|
+
setStatusFilter(status) {
|
|
10368
|
+
this.statusFilter.set(status);
|
|
10369
|
+
this.currentPage.set(1);
|
|
10370
|
+
this.router.navigate([], {
|
|
10371
|
+
queryParams: { status: status ?? null },
|
|
10372
|
+
queryParamsHandling: 'merge',
|
|
10373
|
+
replaceUrl: true,
|
|
10374
|
+
});
|
|
10375
|
+
}
|
|
9878
10376
|
/**
|
|
9879
10377
|
* Handle create button click.
|
|
9880
10378
|
*/
|
|
@@ -9883,7 +10381,7 @@ class EntityListWidget {
|
|
|
9883
10381
|
this.router.navigate([path]);
|
|
9884
10382
|
}
|
|
9885
10383
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: EntityListWidget, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
9886
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: EntityListWidget, isStandalone: true, selector: "mcms-entity-list", inputs: { collection: { classPropertyName: "collection", publicName: "collection", isSignal: true, isRequired: true, transformFunction: null }, basePath: { classPropertyName: "basePath", publicName: "basePath", isSignal: true, isRequired: false, transformFunction: null }, showHeader: { classPropertyName: "showHeader", publicName: "showHeader", isSignal: true, isRequired: false, transformFunction: null }, showBreadcrumbs: { classPropertyName: "showBreadcrumbs", publicName: "showBreadcrumbs", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, rowActions: { classPropertyName: "rowActions", publicName: "rowActions", isSignal: true, isRequired: false, transformFunction: null }, bulkActions: { classPropertyName: "bulkActions", publicName: "bulkActions", isSignal: true, isRequired: false, transformFunction: null }, headerActions: { classPropertyName: "headerActions", publicName: "headerActions", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, searchFields: { classPropertyName: "searchFields", publicName: "searchFields", isSignal: true, isRequired: false, transformFunction: null }, sortable: { classPropertyName: "sortable", publicName: "sortable", isSignal: true, isRequired: false, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, paginated: { classPropertyName: "paginated", publicName: "paginated", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, emptyTitle: { classPropertyName: "emptyTitle", publicName: "emptyTitle", isSignal: true, isRequired: false, transformFunction: null }, emptyDescription: { classPropertyName: "emptyDescription", publicName: "emptyDescription", isSignal: true, isRequired: false, transformFunction: null }, searchQuery: { classPropertyName: "searchQuery", publicName: "searchQuery", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { headerActionClick: "headerActionClick", entityClick: "entityClick", entityAction: "entityAction", bulkAction: "bulkAction", dataLoaded: "dataLoaded", searchQuery: "searchQueryChange" }, host: { classAttribute: "block" }, providers: [provideIcons({ heroEye })], viewQueries: [{ propertyName: "complexCellTemplate", first: true, predicate: ["complexCell"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
10384
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: EntityListWidget, isStandalone: true, selector: "mcms-entity-list", inputs: { collection: { classPropertyName: "collection", publicName: "collection", isSignal: true, isRequired: true, transformFunction: null }, basePath: { classPropertyName: "basePath", publicName: "basePath", isSignal: true, isRequired: false, transformFunction: null }, showHeader: { classPropertyName: "showHeader", publicName: "showHeader", isSignal: true, isRequired: false, transformFunction: null }, showBreadcrumbs: { classPropertyName: "showBreadcrumbs", publicName: "showBreadcrumbs", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, rowActions: { classPropertyName: "rowActions", publicName: "rowActions", isSignal: true, isRequired: false, transformFunction: null }, bulkActions: { classPropertyName: "bulkActions", publicName: "bulkActions", isSignal: true, isRequired: false, transformFunction: null }, headerActions: { classPropertyName: "headerActions", publicName: "headerActions", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, searchFields: { classPropertyName: "searchFields", publicName: "searchFields", isSignal: true, isRequired: false, transformFunction: null }, sortable: { classPropertyName: "sortable", publicName: "sortable", isSignal: true, isRequired: false, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, paginated: { classPropertyName: "paginated", publicName: "paginated", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, emptyTitle: { classPropertyName: "emptyTitle", publicName: "emptyTitle", isSignal: true, isRequired: false, transformFunction: null }, emptyDescription: { classPropertyName: "emptyDescription", publicName: "emptyDescription", isSignal: true, isRequired: false, transformFunction: null }, searchQuery: { classPropertyName: "searchQuery", publicName: "searchQuery", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { headerActionClick: "headerActionClick", entityClick: "entityClick", entityAction: "entityAction", bulkAction: "bulkAction", dataLoaded: "dataLoaded", searchQuery: "searchQueryChange" }, host: { classAttribute: "block" }, providers: [provideIcons({ heroEye })], viewQueries: [{ propertyName: "complexCellTemplate", first: true, predicate: ["complexCell"], descendants: true, isSignal: true }, { propertyName: "badgeCellTemplate", first: true, predicate: ["badgeCell"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
9887
10385
|
@if (showHeader()) {
|
|
9888
10386
|
@if (showBreadcrumbs()) {
|
|
9889
10387
|
<mcms-breadcrumbs class="mb-6">
|
|
@@ -9906,6 +10404,22 @@ class EntityListWidget {
|
|
|
9906
10404
|
}
|
|
9907
10405
|
</div>
|
|
9908
10406
|
<div class="flex items-center gap-2">
|
|
10407
|
+
@if (hasVersioning()) {
|
|
10408
|
+
<div class="flex items-center gap-1" role="group" aria-label="Filter by status">
|
|
10409
|
+
@for (option of statusFilterOptions; track option.value) {
|
|
10410
|
+
<button
|
|
10411
|
+
mcms-button
|
|
10412
|
+
[variant]="statusFilter() === option.value ? 'outline' : 'ghost'"
|
|
10413
|
+
size="sm"
|
|
10414
|
+
[attr.data-testid]="'status-filter-' + (option.value ?? 'all')"
|
|
10415
|
+
[attr.aria-pressed]="statusFilter() === option.value"
|
|
10416
|
+
(click)="setStatusFilter(option.value)"
|
|
10417
|
+
>
|
|
10418
|
+
{{ option.label }}
|
|
10419
|
+
</button>
|
|
10420
|
+
}
|
|
10421
|
+
</div>
|
|
10422
|
+
}
|
|
9909
10423
|
@if (hasSoftDelete()) {
|
|
9910
10424
|
<button
|
|
9911
10425
|
mcms-button
|
|
@@ -9979,6 +10493,15 @@ class EntityListWidget {
|
|
|
9979
10493
|
}
|
|
9980
10494
|
</mcms-data-table>
|
|
9981
10495
|
|
|
10496
|
+
<!-- Template for badge cells (_status column) -->
|
|
10497
|
+
<ng-template #badgeCell let-value let-column="column">
|
|
10498
|
+
@if (getBadgeConfig(value, column); as badge) {
|
|
10499
|
+
<mcms-badge [variant]="badge.variant">{{ badge.label }}</mcms-badge>
|
|
10500
|
+
} @else {
|
|
10501
|
+
{{ value ?? '-' }}
|
|
10502
|
+
}
|
|
10503
|
+
</ng-template>
|
|
10504
|
+
|
|
9982
10505
|
<!-- Template for complex field cells (group, array, json) -->
|
|
9983
10506
|
<ng-template #complexCell let-value let-column="column">
|
|
9984
10507
|
<div class="flex items-center gap-1.5">
|
|
@@ -10030,6 +10553,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
10030
10553
|
}
|
|
10031
10554
|
</div>
|
|
10032
10555
|
<div class="flex items-center gap-2">
|
|
10556
|
+
@if (hasVersioning()) {
|
|
10557
|
+
<div class="flex items-center gap-1" role="group" aria-label="Filter by status">
|
|
10558
|
+
@for (option of statusFilterOptions; track option.value) {
|
|
10559
|
+
<button
|
|
10560
|
+
mcms-button
|
|
10561
|
+
[variant]="statusFilter() === option.value ? 'outline' : 'ghost'"
|
|
10562
|
+
size="sm"
|
|
10563
|
+
[attr.data-testid]="'status-filter-' + (option.value ?? 'all')"
|
|
10564
|
+
[attr.aria-pressed]="statusFilter() === option.value"
|
|
10565
|
+
(click)="setStatusFilter(option.value)"
|
|
10566
|
+
>
|
|
10567
|
+
{{ option.label }}
|
|
10568
|
+
</button>
|
|
10569
|
+
}
|
|
10570
|
+
</div>
|
|
10571
|
+
}
|
|
10033
10572
|
@if (hasSoftDelete()) {
|
|
10034
10573
|
<button
|
|
10035
10574
|
mcms-button
|
|
@@ -10103,6 +10642,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
10103
10642
|
}
|
|
10104
10643
|
</mcms-data-table>
|
|
10105
10644
|
|
|
10645
|
+
<!-- Template for badge cells (_status column) -->
|
|
10646
|
+
<ng-template #badgeCell let-value let-column="column">
|
|
10647
|
+
@if (getBadgeConfig(value, column); as badge) {
|
|
10648
|
+
<mcms-badge [variant]="badge.variant">{{ badge.label }}</mcms-badge>
|
|
10649
|
+
} @else {
|
|
10650
|
+
{{ value ?? '-' }}
|
|
10651
|
+
}
|
|
10652
|
+
</ng-template>
|
|
10653
|
+
|
|
10106
10654
|
<!-- Template for complex field cells (group, array, json) -->
|
|
10107
10655
|
<ng-template #complexCell let-value let-column="column">
|
|
10108
10656
|
<div class="flex items-center gap-1.5">
|
|
@@ -10123,7 +10671,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
10123
10671
|
</ng-template>
|
|
10124
10672
|
`,
|
|
10125
10673
|
}]
|
|
10126
|
-
}], ctorParameters: () => [], propDecorators: { complexCellTemplate: [{ type: i0.ViewChild, args: ['complexCell', { isSignal: true }] }], collection: [{ type: i0.Input, args: [{ isSignal: true, alias: "collection", required: true }] }], basePath: [{ type: i0.Input, args: [{ isSignal: true, alias: "basePath", required: false }] }], showHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "showHeader", required: false }] }], showBreadcrumbs: [{ type: i0.Input, args: [{ isSignal: true, alias: "showBreadcrumbs", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], rowActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowActions", required: false }] }], bulkActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "bulkActions", required: false }] }], headerActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerActions", required: false }] }], headerActionClick: [{ type: i0.Output, args: ["headerActionClick"] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], searchFields: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchFields", required: false }] }], sortable: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortable", required: false }] }], selectable: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectable", required: false }] }], paginated: [{ type: i0.Input, args: [{ isSignal: true, alias: "paginated", required: false }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }], emptyTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyTitle", required: false }] }], emptyDescription: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyDescription", required: false }] }], entityClick: [{ type: i0.Output, args: ["entityClick"] }], entityAction: [{ type: i0.Output, args: ["entityAction"] }], bulkAction: [{ type: i0.Output, args: ["bulkAction"] }], dataLoaded: [{ type: i0.Output, args: ["dataLoaded"] }], searchQuery: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchQuery", required: false }] }, { type: i0.Output, args: ["searchQueryChange"] }] } });
|
|
10674
|
+
}], ctorParameters: () => [], propDecorators: { complexCellTemplate: [{ type: i0.ViewChild, args: ['complexCell', { isSignal: true }] }], badgeCellTemplate: [{ type: i0.ViewChild, args: ['badgeCell', { isSignal: true }] }], collection: [{ type: i0.Input, args: [{ isSignal: true, alias: "collection", required: true }] }], basePath: [{ type: i0.Input, args: [{ isSignal: true, alias: "basePath", required: false }] }], showHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "showHeader", required: false }] }], showBreadcrumbs: [{ type: i0.Input, args: [{ isSignal: true, alias: "showBreadcrumbs", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], rowActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "rowActions", required: false }] }], bulkActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "bulkActions", required: false }] }], headerActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "headerActions", required: false }] }], headerActionClick: [{ type: i0.Output, args: ["headerActionClick"] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], searchFields: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchFields", required: false }] }], sortable: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortable", required: false }] }], selectable: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectable", required: false }] }], paginated: [{ type: i0.Input, args: [{ isSignal: true, alias: "paginated", required: false }] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }], emptyTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyTitle", required: false }] }], emptyDescription: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyDescription", required: false }] }], entityClick: [{ type: i0.Output, args: ["entityClick"] }], entityAction: [{ type: i0.Output, args: ["entityAction"] }], bulkAction: [{ type: i0.Output, args: ["bulkAction"] }], dataLoaded: [{ type: i0.Output, args: ["dataLoaded"] }], searchQuery: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchQuery", required: false }] }, { type: i0.Output, args: ["searchQueryChange"] }] } });
|
|
10127
10675
|
|
|
10128
10676
|
/** Role hierarchy: lower index = higher privilege.
|
|
10129
10677
|
* Keep in sync with AUTH_ROLES in @momentumcms/auth/collections */
|
|
@@ -10510,6 +11058,7 @@ class CollectionListPage {
|
|
|
10510
11058
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: CollectionListPage, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
10511
11059
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: CollectionListPage, isStandalone: true, selector: "mcms-collection-list", host: { classAttribute: "block" }, viewQueries: [{ propertyName: "entityList", first: true, predicate: ["entityList"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
10512
11060
|
@if (collection(); as col) {
|
|
11061
|
+
<mcms-admin-slot slot="collection-list:before" [collectionSlug]="col.slug" />
|
|
10513
11062
|
<mcms-entity-list
|
|
10514
11063
|
#entityList
|
|
10515
11064
|
[collection]="col"
|
|
@@ -10521,20 +11070,22 @@ class CollectionListPage {
|
|
|
10521
11070
|
(bulkAction)="onBulkAction($event)"
|
|
10522
11071
|
(headerActionClick)="onHeaderAction($event)"
|
|
10523
11072
|
/>
|
|
11073
|
+
<mcms-admin-slot slot="collection-list:after" [collectionSlug]="col.slug" />
|
|
10524
11074
|
} @else {
|
|
10525
11075
|
<div class="p-12 text-center text-muted-foreground">Collection not found</div>
|
|
10526
11076
|
}
|
|
10527
|
-
`, isInline: true, dependencies: [{ kind: "component", type: EntityListWidget, selector: "mcms-entity-list", inputs: ["collection", "basePath", "showHeader", "showBreadcrumbs", "columns", "rowActions", "bulkActions", "headerActions", "searchable", "searchPlaceholder", "searchFields", "sortable", "selectable", "paginated", "pageSize", "emptyTitle", "emptyDescription", "searchQuery"], outputs: ["headerActionClick", "entityClick", "entityAction", "bulkAction", "dataLoaded", "searchQueryChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
11077
|
+
`, isInline: true, dependencies: [{ kind: "component", type: EntityListWidget, selector: "mcms-entity-list", inputs: ["collection", "basePath", "showHeader", "showBreadcrumbs", "columns", "rowActions", "bulkActions", "headerActions", "searchable", "searchPlaceholder", "searchFields", "sortable", "selectable", "paginated", "pageSize", "emptyTitle", "emptyDescription", "searchQuery"], outputs: ["headerActionClick", "entityClick", "entityAction", "bulkAction", "dataLoaded", "searchQueryChange"] }, { kind: "component", type: AdminSlotOutlet, selector: "mcms-admin-slot", inputs: ["slot", "collectionSlug", "context"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
10528
11078
|
}
|
|
10529
11079
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: CollectionListPage, decorators: [{
|
|
10530
11080
|
type: Component,
|
|
10531
11081
|
args: [{
|
|
10532
11082
|
selector: 'mcms-collection-list',
|
|
10533
|
-
imports: [EntityListWidget],
|
|
11083
|
+
imports: [EntityListWidget, AdminSlotOutlet],
|
|
10534
11084
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
10535
11085
|
host: { class: 'block' },
|
|
10536
11086
|
template: `
|
|
10537
11087
|
@if (collection(); as col) {
|
|
11088
|
+
<mcms-admin-slot slot="collection-list:before" [collectionSlug]="col.slug" />
|
|
10538
11089
|
<mcms-entity-list
|
|
10539
11090
|
#entityList
|
|
10540
11091
|
[collection]="col"
|
|
@@ -10546,6 +11097,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
10546
11097
|
(bulkAction)="onBulkAction($event)"
|
|
10547
11098
|
(headerActionClick)="onHeaderAction($event)"
|
|
10548
11099
|
/>
|
|
11100
|
+
<mcms-admin-slot slot="collection-list:after" [collectionSlug]="col.slug" />
|
|
10549
11101
|
} @else {
|
|
10550
11102
|
<div class="p-12 text-center text-muted-foreground">Collection not found</div>
|
|
10551
11103
|
}
|
|
@@ -10595,6 +11147,7 @@ class CollectionViewPage {
|
|
|
10595
11147
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: CollectionViewPage, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
10596
11148
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: CollectionViewPage, isStandalone: true, selector: "mcms-collection-view", host: { classAttribute: "block" }, ngImport: i0, template: `
|
|
10597
11149
|
@if (collection(); as col) {
|
|
11150
|
+
<mcms-admin-slot slot="collection-view:before" [collectionSlug]="col.slug" />
|
|
10598
11151
|
@if (entityId(); as id) {
|
|
10599
11152
|
<mcms-entity-view
|
|
10600
11153
|
[collection]="col"
|
|
@@ -10606,20 +11159,22 @@ class CollectionViewPage {
|
|
|
10606
11159
|
} @else {
|
|
10607
11160
|
<div class="p-12 text-center text-muted-foreground">Entity ID not provided</div>
|
|
10608
11161
|
}
|
|
11162
|
+
<mcms-admin-slot slot="collection-view:after" [collectionSlug]="col.slug" />
|
|
10609
11163
|
} @else {
|
|
10610
11164
|
<div class="p-12 text-center text-muted-foreground">Collection not found</div>
|
|
10611
11165
|
}
|
|
10612
|
-
`, isInline: true, dependencies: [{ kind: "component", type: EntityViewWidget, selector: "mcms-entity-view", inputs: ["collection", "entityId", "basePath", "showBreadcrumbs", "fieldConfigs", "actions", "showVersionHistory", "suppressNavigation"], outputs: ["edit", "statusChanged", "delete_", "actionClick"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
11166
|
+
`, isInline: true, dependencies: [{ kind: "component", type: EntityViewWidget, selector: "mcms-entity-view", inputs: ["collection", "entityId", "basePath", "showBreadcrumbs", "fieldConfigs", "actions", "showVersionHistory", "suppressNavigation"], outputs: ["edit", "statusChanged", "delete_", "actionClick"] }, { kind: "component", type: AdminSlotOutlet, selector: "mcms-admin-slot", inputs: ["slot", "collectionSlug", "context"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
10613
11167
|
}
|
|
10614
11168
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: CollectionViewPage, decorators: [{
|
|
10615
11169
|
type: Component,
|
|
10616
11170
|
args: [{
|
|
10617
11171
|
selector: 'mcms-collection-view',
|
|
10618
|
-
imports: [EntityViewWidget],
|
|
11172
|
+
imports: [EntityViewWidget, AdminSlotOutlet],
|
|
10619
11173
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
10620
11174
|
host: { class: 'block' },
|
|
10621
11175
|
template: `
|
|
10622
11176
|
@if (collection(); as col) {
|
|
11177
|
+
<mcms-admin-slot slot="collection-view:before" [collectionSlug]="col.slug" />
|
|
10623
11178
|
@if (entityId(); as id) {
|
|
10624
11179
|
<mcms-entity-view
|
|
10625
11180
|
[collection]="col"
|
|
@@ -10631,6 +11186,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
10631
11186
|
} @else {
|
|
10632
11187
|
<div class="p-12 text-center text-muted-foreground">Entity ID not provided</div>
|
|
10633
11188
|
}
|
|
11189
|
+
<mcms-admin-slot slot="collection-view:after" [collectionSlug]="col.slug" />
|
|
10634
11190
|
} @else {
|
|
10635
11191
|
<div class="p-12 text-center text-muted-foreground">Collection not found</div>
|
|
10636
11192
|
}
|
|
@@ -11245,6 +11801,7 @@ class CollectionEditPage {
|
|
|
11245
11801
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: CollectionEditPage, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
11246
11802
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: CollectionEditPage, isStandalone: true, selector: "mcms-collection-edit", host: { classAttribute: "block" }, viewQueries: [{ propertyName: "entityFormRef", first: true, predicate: ["entityForm"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
11247
11803
|
@if (collection(); as col) {
|
|
11804
|
+
<mcms-admin-slot slot="collection-edit:before" [collectionSlug]="col.slug" />
|
|
11248
11805
|
@if (previewConfig(); as preview) {
|
|
11249
11806
|
@if (showPreview()) {
|
|
11250
11807
|
<!-- Split layout: form + preview -->
|
|
@@ -11279,6 +11836,7 @@ class CollectionEditPage {
|
|
|
11279
11836
|
(editBlockRequest)="onEditBlockRequest($event)"
|
|
11280
11837
|
/>
|
|
11281
11838
|
</div>
|
|
11839
|
+
<mcms-admin-slot slot="collection-edit:sidebar" [collectionSlug]="col.slug" />
|
|
11282
11840
|
</div>
|
|
11283
11841
|
} @else {
|
|
11284
11842
|
<!-- Full-width form (preview hidden) -->
|
|
@@ -11303,29 +11861,36 @@ class CollectionEditPage {
|
|
|
11303
11861
|
</mcms-entity-form>
|
|
11304
11862
|
}
|
|
11305
11863
|
} @else {
|
|
11306
|
-
<!-- Standard layout: form
|
|
11307
|
-
<
|
|
11308
|
-
|
|
11309
|
-
|
|
11310
|
-
|
|
11311
|
-
|
|
11312
|
-
|
|
11313
|
-
|
|
11864
|
+
<!-- Standard layout: form + optional sidebar -->
|
|
11865
|
+
<div class="flex gap-6">
|
|
11866
|
+
<div class="min-w-0 flex-1">
|
|
11867
|
+
<mcms-entity-form
|
|
11868
|
+
#entityForm
|
|
11869
|
+
[collection]="col"
|
|
11870
|
+
[entityId]="entityId()"
|
|
11871
|
+
[mode]="mode()"
|
|
11872
|
+
[basePath]="basePath"
|
|
11873
|
+
/>
|
|
11874
|
+
</div>
|
|
11875
|
+
<mcms-admin-slot slot="collection-edit:sidebar" [collectionSlug]="col.slug" />
|
|
11876
|
+
</div>
|
|
11314
11877
|
}
|
|
11878
|
+
<mcms-admin-slot slot="collection-edit:after" [collectionSlug]="col.slug" />
|
|
11315
11879
|
} @else {
|
|
11316
11880
|
<div class="p-12 text-center text-muted-foreground">Collection not found</div>
|
|
11317
11881
|
}
|
|
11318
|
-
`, isInline: true, dependencies: [{ kind: "component", type: EntityFormWidget, selector: "mcms-entity-form", inputs: ["collection", "entityId", "mode", "basePath", "showBreadcrumbs", "suppressNavigation", "isGlobal", "globalSlug"], outputs: ["saved", "cancelled", "saveError", "modeChange", "draftSaved"] }, { kind: "component", type: LivePreviewComponent, selector: "mcms-live-preview", inputs: ["preview", "documentData", "collectionSlug", "entityId"], outputs: ["editBlockRequest"] }, { kind: "component", type: Button, selector: "button[mcms-button], a[mcms-button]", inputs: ["variant", "size", "disabled", "loading", "ariaLabel", "class"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
11882
|
+
`, isInline: true, dependencies: [{ kind: "component", type: EntityFormWidget, selector: "mcms-entity-form", inputs: ["collection", "entityId", "mode", "basePath", "showBreadcrumbs", "suppressNavigation", "isGlobal", "globalSlug"], outputs: ["saved", "cancelled", "saveError", "modeChange", "draftSaved"] }, { kind: "component", type: LivePreviewComponent, selector: "mcms-live-preview", inputs: ["preview", "documentData", "collectionSlug", "entityId"], outputs: ["editBlockRequest"] }, { kind: "component", type: Button, selector: "button[mcms-button], a[mcms-button]", inputs: ["variant", "size", "disabled", "loading", "ariaLabel", "class"] }, { kind: "component", type: AdminSlotOutlet, selector: "mcms-admin-slot", inputs: ["slot", "collectionSlug", "context"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
11319
11883
|
}
|
|
11320
11884
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: CollectionEditPage, decorators: [{
|
|
11321
11885
|
type: Component,
|
|
11322
11886
|
args: [{
|
|
11323
11887
|
selector: 'mcms-collection-edit',
|
|
11324
|
-
imports: [EntityFormWidget, LivePreviewComponent, Button],
|
|
11888
|
+
imports: [EntityFormWidget, LivePreviewComponent, Button, AdminSlotOutlet],
|
|
11325
11889
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
11326
11890
|
host: { class: 'block' },
|
|
11327
11891
|
template: `
|
|
11328
11892
|
@if (collection(); as col) {
|
|
11893
|
+
<mcms-admin-slot slot="collection-edit:before" [collectionSlug]="col.slug" />
|
|
11329
11894
|
@if (previewConfig(); as preview) {
|
|
11330
11895
|
@if (showPreview()) {
|
|
11331
11896
|
<!-- Split layout: form + preview -->
|
|
@@ -11360,6 +11925,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
11360
11925
|
(editBlockRequest)="onEditBlockRequest($event)"
|
|
11361
11926
|
/>
|
|
11362
11927
|
</div>
|
|
11928
|
+
<mcms-admin-slot slot="collection-edit:sidebar" [collectionSlug]="col.slug" />
|
|
11363
11929
|
</div>
|
|
11364
11930
|
} @else {
|
|
11365
11931
|
<!-- Full-width form (preview hidden) -->
|
|
@@ -11384,15 +11950,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
11384
11950
|
</mcms-entity-form>
|
|
11385
11951
|
}
|
|
11386
11952
|
} @else {
|
|
11387
|
-
<!-- Standard layout: form
|
|
11388
|
-
<
|
|
11389
|
-
|
|
11390
|
-
|
|
11391
|
-
|
|
11392
|
-
|
|
11393
|
-
|
|
11394
|
-
|
|
11953
|
+
<!-- Standard layout: form + optional sidebar -->
|
|
11954
|
+
<div class="flex gap-6">
|
|
11955
|
+
<div class="min-w-0 flex-1">
|
|
11956
|
+
<mcms-entity-form
|
|
11957
|
+
#entityForm
|
|
11958
|
+
[collection]="col"
|
|
11959
|
+
[entityId]="entityId()"
|
|
11960
|
+
[mode]="mode()"
|
|
11961
|
+
[basePath]="basePath"
|
|
11962
|
+
/>
|
|
11963
|
+
</div>
|
|
11964
|
+
<mcms-admin-slot slot="collection-edit:sidebar" [collectionSlug]="col.slug" />
|
|
11965
|
+
</div>
|
|
11395
11966
|
}
|
|
11967
|
+
<mcms-admin-slot slot="collection-edit:after" [collectionSlug]="col.slug" />
|
|
11396
11968
|
} @else {
|
|
11397
11969
|
<div class="p-12 text-center text-muted-foreground">Collection not found</div>
|
|
11398
11970
|
}
|
|
@@ -11485,6 +12057,7 @@ class LoginPage {
|
|
|
11485
12057
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: LoginPage, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
11486
12058
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: LoginPage, isStandalone: true, selector: "mcms-login-page", host: { classAttribute: "flex min-h-screen items-center justify-center bg-background p-4" }, ngImport: i0, template: `
|
|
11487
12059
|
<main>
|
|
12060
|
+
<mcms-admin-slot slot="login:before" />
|
|
11488
12061
|
<mcms-card class="w-full max-w-md">
|
|
11489
12062
|
<mcms-card-header class="text-center">
|
|
11490
12063
|
<mcms-card-title>Sign In</mcms-card-title>
|
|
@@ -11622,8 +12195,9 @@ class LoginPage {
|
|
|
11622
12195
|
<p class="text-sm text-muted-foreground">Momentum CMS</p>
|
|
11623
12196
|
</mcms-card-footer>
|
|
11624
12197
|
</mcms-card>
|
|
12198
|
+
<mcms-admin-slot slot="login:after" />
|
|
11625
12199
|
</main>
|
|
11626
|
-
`, isInline: true, dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: Input, selector: "mcms-input", inputs: ["value", "disabled", "errors", "touched", "invalid", "readonly", "required", "type", "id", "name", "placeholder", "autocomplete", "ariaLabel", "describedBy", "min", "max", "step"], outputs: ["valueChange", "blurred"] }, { kind: "component", type: Button, selector: "button[mcms-button], a[mcms-button]", inputs: ["variant", "size", "disabled", "loading", "ariaLabel", "class"] }, { kind: "component", type: McmsFormField, selector: "mcms-form-field", inputs: ["id", "required", "disabled", "errors", "hint", "hasLabel"] }, { kind: "component", type: Card, selector: "mcms-card" }, { kind: "component", type: CardHeader, selector: "mcms-card-header" }, { kind: "component", type: CardTitle, selector: "mcms-card-title", inputs: ["level"] }, { kind: "component", type: CardDescription, selector: "mcms-card-description" }, { kind: "component", type: CardContent, selector: "mcms-card-content" }, { kind: "component", type: CardFooter, selector: "mcms-card-footer" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
12200
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: Input, selector: "mcms-input", inputs: ["value", "disabled", "errors", "touched", "invalid", "readonly", "required", "type", "id", "name", "placeholder", "autocomplete", "ariaLabel", "describedBy", "min", "max", "step"], outputs: ["valueChange", "blurred"] }, { kind: "component", type: Button, selector: "button[mcms-button], a[mcms-button]", inputs: ["variant", "size", "disabled", "loading", "ariaLabel", "class"] }, { kind: "component", type: McmsFormField, selector: "mcms-form-field", inputs: ["id", "required", "disabled", "errors", "hint", "hasLabel"] }, { kind: "component", type: Card, selector: "mcms-card" }, { kind: "component", type: CardHeader, selector: "mcms-card-header" }, { kind: "component", type: CardTitle, selector: "mcms-card-title", inputs: ["level"] }, { kind: "component", type: CardDescription, selector: "mcms-card-description" }, { kind: "component", type: CardContent, selector: "mcms-card-content" }, { kind: "component", type: CardFooter, selector: "mcms-card-footer" }, { kind: "component", type: AdminSlotOutlet, selector: "mcms-admin-slot", inputs: ["slot", "collectionSlug", "context"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
11627
12201
|
}
|
|
11628
12202
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: LoginPage, decorators: [{
|
|
11629
12203
|
type: Component,
|
|
@@ -11640,6 +12214,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
11640
12214
|
CardDescription,
|
|
11641
12215
|
CardContent,
|
|
11642
12216
|
CardFooter,
|
|
12217
|
+
AdminSlotOutlet,
|
|
11643
12218
|
],
|
|
11644
12219
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
11645
12220
|
host: {
|
|
@@ -11647,6 +12222,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
11647
12222
|
},
|
|
11648
12223
|
template: `
|
|
11649
12224
|
<main>
|
|
12225
|
+
<mcms-admin-slot slot="login:before" />
|
|
11650
12226
|
<mcms-card class="w-full max-w-md">
|
|
11651
12227
|
<mcms-card-header class="text-center">
|
|
11652
12228
|
<mcms-card-title>Sign In</mcms-card-title>
|
|
@@ -11784,6 +12360,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
11784
12360
|
<p class="text-sm text-muted-foreground">Momentum CMS</p>
|
|
11785
12361
|
</mcms-card-footer>
|
|
11786
12362
|
</mcms-card>
|
|
12363
|
+
<mcms-admin-slot slot="login:after" />
|
|
11787
12364
|
</main>
|
|
11788
12365
|
`,
|
|
11789
12366
|
}]
|
|
@@ -14222,18 +14799,18 @@ function provideMomentumFieldRenderers() {
|
|
|
14222
14799
|
registry.register('checkbox', () => Promise.resolve().then(function () { return checkboxField_component; }).then((m) => m.CheckboxFieldRenderer));
|
|
14223
14800
|
registry.register('date', () => Promise.resolve().then(function () { return dateField_component; }).then((m) => m.DateFieldRenderer));
|
|
14224
14801
|
registry.register('upload', () => Promise.resolve().then(function () { return uploadField_component; }).then((m) => m.UploadFieldRenderer));
|
|
14225
|
-
registry.register('richText', () => import('./momentumcms-admin-rich-text-field.component-
|
|
14802
|
+
registry.register('richText', () => import('./momentumcms-admin-rich-text-field.component-s47cVU85.mjs').then((m) => m.RichTextFieldRenderer));
|
|
14226
14803
|
// Layout field renderers (support nested field rendering)
|
|
14227
|
-
registry.register('group', () => import('./momentumcms-admin-group-field.component-
|
|
14228
|
-
registry.register('array', () => import('./momentumcms-admin-array-field.component-
|
|
14229
|
-
registry.register('blocks', () => import('./momentumcms-admin-blocks-field.component-
|
|
14804
|
+
registry.register('group', () => import('./momentumcms-admin-group-field.component-NdX_GSNQ.mjs').then((m) => m.GroupFieldRenderer));
|
|
14805
|
+
registry.register('array', () => import('./momentumcms-admin-array-field.component-Co0_3wwq.mjs').then((m) => m.ArrayFieldRenderer));
|
|
14806
|
+
registry.register('blocks', () => import('./momentumcms-admin-blocks-field.component-Dr0N35Nz.mjs').then((m) => m.BlocksFieldRenderer));
|
|
14230
14807
|
// Visual block editor variant (blocks field with admin.editor === 'visual')
|
|
14231
14808
|
registry.register('blocks-visual', () => Promise.resolve().then(function () { return visualBlockEditor_component; }).then((m) => m.VisualBlockEditorComponent));
|
|
14232
|
-
registry.register('relationship', () => import('./momentumcms-admin-relationship-field.component-
|
|
14809
|
+
registry.register('relationship', () => import('./momentumcms-admin-relationship-field.component-Cy9BsTBY.mjs').then((m) => m.RelationshipFieldRenderer));
|
|
14233
14810
|
// Layout-only renderers (tabs, collapsible, row)
|
|
14234
|
-
registry.register('tabs', () => import('./momentumcms-admin-tabs-field.component-
|
|
14235
|
-
registry.register('collapsible', () => import('./momentumcms-admin-collapsible-field.component-
|
|
14236
|
-
registry.register('row', () => import('./momentumcms-admin-row-field.component-
|
|
14811
|
+
registry.register('tabs', () => import('./momentumcms-admin-tabs-field.component-C_iReaEt.mjs').then((m) => m.TabsFieldRenderer));
|
|
14812
|
+
registry.register('collapsible', () => import('./momentumcms-admin-collapsible-field.component-C1GobKo0.mjs').then((m) => m.CollapsibleFieldRenderer));
|
|
14813
|
+
registry.register('row', () => import('./momentumcms-admin-row-field.component-VJvSx9YM.mjs').then((m) => m.RowFieldRenderer));
|
|
14237
14814
|
};
|
|
14238
14815
|
},
|
|
14239
14816
|
},
|
|
@@ -14268,6 +14845,104 @@ function provideFieldRenderer(type, loader) {
|
|
|
14268
14845
|
]);
|
|
14269
14846
|
}
|
|
14270
14847
|
|
|
14848
|
+
/**
|
|
14849
|
+
* Registry-aware page resolver that delegates to either a registered
|
|
14850
|
+
* custom component or the built-in fallback.
|
|
14851
|
+
*
|
|
14852
|
+
* Used as the `loadComponent` for every admin route. Route `data` provides:
|
|
14853
|
+
* - `adminPageKey` — the registry key (e.g., 'dashboard', 'collection-list')
|
|
14854
|
+
* - `adminPageFallback` — lazy loader for the built-in default
|
|
14855
|
+
*
|
|
14856
|
+
* For collection pages, reads `:slug` from route params to check per-collection overrides.
|
|
14857
|
+
*
|
|
14858
|
+
* Subscribes to route observables so the component re-resolves when Angular
|
|
14859
|
+
* reuses the instance for sibling routes (e.g., navigating between collections).
|
|
14860
|
+
*
|
|
14861
|
+
* Implements HasUnsavedChanges to delegate to the resolved component for
|
|
14862
|
+
* the unsaved changes guard.
|
|
14863
|
+
*/
|
|
14864
|
+
class AdminPageResolver {
|
|
14865
|
+
registry = inject(AdminComponentRegistry);
|
|
14866
|
+
route = inject(ActivatedRoute);
|
|
14867
|
+
destroyRef = inject(DestroyRef);
|
|
14868
|
+
outlet = viewChild(NgComponentOutlet, ...(ngDevMode ? [{ debugName: "outlet" }] : []));
|
|
14869
|
+
resolvedComponent = signal(null, ...(ngDevMode ? [{ debugName: "resolvedComponent" }] : []));
|
|
14870
|
+
/** Incremented on every load to detect stale promise resolutions. */
|
|
14871
|
+
loadGeneration = 0;
|
|
14872
|
+
ngOnInit() {
|
|
14873
|
+
combineLatest([this.route.data, this.route.params])
|
|
14874
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
14875
|
+
.subscribe(([data, params]) => {
|
|
14876
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- route data is Record<string, unknown>
|
|
14877
|
+
const pageKey = data['adminPageKey'];
|
|
14878
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- route data is Record<string, unknown>
|
|
14879
|
+
const fallback = data['adminPageFallback'];
|
|
14880
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- route params are Record<string, string>
|
|
14881
|
+
const slug = params['slug'];
|
|
14882
|
+
const override = this.registry.resolve(pageKey, slug);
|
|
14883
|
+
const loader = override ?? fallback;
|
|
14884
|
+
if (loader) {
|
|
14885
|
+
const generation = ++this.loadGeneration;
|
|
14886
|
+
loader()
|
|
14887
|
+
.then((component) => {
|
|
14888
|
+
if (generation !== this.loadGeneration)
|
|
14889
|
+
return;
|
|
14890
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- loader resolves to unknown from registry, safe cast to Type
|
|
14891
|
+
this.resolvedComponent.set(component);
|
|
14892
|
+
})
|
|
14893
|
+
.catch((err) => {
|
|
14894
|
+
if (generation !== this.loadGeneration)
|
|
14895
|
+
return;
|
|
14896
|
+
console.error('[AdminPageResolver] Failed to load component:', err);
|
|
14897
|
+
});
|
|
14898
|
+
}
|
|
14899
|
+
});
|
|
14900
|
+
}
|
|
14901
|
+
hasUnsavedChanges() {
|
|
14902
|
+
const instance = this.outlet()?.componentInstance;
|
|
14903
|
+
if (instance == null)
|
|
14904
|
+
return false;
|
|
14905
|
+
// Check if the resolved component implements HasUnsavedChanges
|
|
14906
|
+
if ('hasUnsavedChanges' in instance && typeof instance.hasUnsavedChanges === 'function') {
|
|
14907
|
+
return Boolean(instance.hasUnsavedChanges());
|
|
14908
|
+
}
|
|
14909
|
+
return false;
|
|
14910
|
+
}
|
|
14911
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AdminPageResolver, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
14912
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: AdminPageResolver, isStandalone: true, selector: "mcms-admin-page-resolver", host: { classAttribute: "block" }, viewQueries: [{ propertyName: "outlet", first: true, predicate: NgComponentOutlet, descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
14913
|
+
@if (resolvedComponent()) {
|
|
14914
|
+
<ng-container *ngComponentOutlet="resolvedComponent()" />
|
|
14915
|
+
} @else {
|
|
14916
|
+
<div role="status" aria-label="Loading page" class="flex h-full items-center justify-center">
|
|
14917
|
+
<div class="h-8 w-8 animate-spin rounded-full border-4 border-muted border-t-primary"></div>
|
|
14918
|
+
</div>
|
|
14919
|
+
}
|
|
14920
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
14921
|
+
}
|
|
14922
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AdminPageResolver, decorators: [{
|
|
14923
|
+
type: Component,
|
|
14924
|
+
args: [{
|
|
14925
|
+
selector: 'mcms-admin-page-resolver',
|
|
14926
|
+
imports: [NgComponentOutlet],
|
|
14927
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
14928
|
+
host: { class: 'block' },
|
|
14929
|
+
template: `
|
|
14930
|
+
@if (resolvedComponent()) {
|
|
14931
|
+
<ng-container *ngComponentOutlet="resolvedComponent()" />
|
|
14932
|
+
} @else {
|
|
14933
|
+
<div role="status" aria-label="Loading page" class="flex h-full items-center justify-center">
|
|
14934
|
+
<div class="h-8 w-8 animate-spin rounded-full border-4 border-muted border-t-primary"></div>
|
|
14935
|
+
</div>
|
|
14936
|
+
}
|
|
14937
|
+
`,
|
|
14938
|
+
}]
|
|
14939
|
+
}], propDecorators: { outlet: [{ type: i0.ViewChild, args: [i0.forwardRef(() => NgComponentOutlet), { isSignal: true }] }] } });
|
|
14940
|
+
|
|
14941
|
+
var adminPageResolver_component = /*#__PURE__*/Object.freeze({
|
|
14942
|
+
__proto__: null,
|
|
14943
|
+
AdminPageResolver: AdminPageResolver
|
|
14944
|
+
});
|
|
14945
|
+
|
|
14271
14946
|
/* eslint-disable @typescript-eslint/consistent-type-assertions -- Type assertions needed to narrow Field union to TextField/TextareaField after type guard */
|
|
14272
14947
|
/**
|
|
14273
14948
|
* Text field renderer for text and textarea field types.
|
|
@@ -16012,5 +16687,5 @@ var uploadField_component = /*#__PURE__*/Object.freeze({
|
|
|
16012
16687
|
* Generated bundle index. Do not edit.
|
|
16013
16688
|
*/
|
|
16014
16689
|
|
|
16015
|
-
export {
|
|
16016
|
-
//# sourceMappingURL=momentumcms-admin-momentumcms-admin-
|
|
16690
|
+
export { UploadService as $, AdminComponentRegistry as A, BlockEditDialog as B, CheckboxFieldRenderer as C, DashboardPage as D, EntityFormWidget as E, FieldRenderer as F, ForgotPasswordPage as G, LoginPage as H, MOMENTUM_API_CONTEXT as I, McmsThemeService as J, MediaLibraryPage as K, LivePreviewComponent as L, MOMENTUM_API as M, MediaPickerDialog as N, MediaPreviewComponent as O, MomentumApiService as P, MomentumAuthService as Q, NumberFieldRenderer as R, PublishControlsWidget as S, ResetPasswordFormComponent as T, ResetPasswordPage as U, SHEET_QUERY_PARAMS as V, SKIP_AUTO_TOAST as W, SelectFieldRenderer as X, SetupPage as Y, TextFieldRenderer as Z, UploadFieldRenderer as _, getFieldNodeState as a, VersionHistoryWidget as a0, VersionService as a1, VisualBlockEditorComponent as a2, adminGuard as a3, authGuard as a4, collectionAccessGuard as a5, crudToastInterceptor as a6, guestGuard as a7, injectHasAnyRole as a8, injectHasRole as a9, injectIsAdmin as aa, injectIsAuthenticated as ab, injectMomentumAPI as ac, injectTypedMomentumAPI as ad, injectUser as ae, injectUserRole as af, injectVersionService as ag, momentumAdminRoutes as ah, provideAdminComponent as ai, provideAdminSlot as aj, provideFieldRenderer as ak, provideMomentumAPI as al, provideMomentumFieldRenderers as am, registerConfigComponents as an, setupGuard as ao, unsavedChangesGuard as ap, getSubNode as b, getFieldDefaultValue as c, EntitySheetService as d, getTitleField as e, AdminPageResolver as f, getGlobalsFromRouteData as g, AdminShellComponent as h, isRecord as i, AdminSidebarWidget as j, AdminSlotOutlet as k, AdminSlotRegistry as l, BlockInserterComponent as m, normalizeBlockDefaults as n, BlockWrapperComponent as o, CollectionAccessService as p, CollectionCardWidget as q, CollectionEditPage as r, CollectionListPage as s, CollectionViewPage as t, DateFieldRenderer as u, EntityListWidget as v, EntityViewWidget as w, FeedbackService as x, FieldRendererRegistry as y, ForgotPasswordFormComponent as z };
|
|
16691
|
+
//# sourceMappingURL=momentumcms-admin-momentumcms-admin-D56RXzt0.mjs.map
|