@momentumcms/admin 0.5.2 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/{momentumcms-admin-array-field.component-DH6vaHO-.mjs → momentumcms-admin-array-field.component-BLL21bqy.mjs} +2 -2
- package/fesm2022/{momentumcms-admin-array-field.component-DH6vaHO-.mjs.map → momentumcms-admin-array-field.component-BLL21bqy.mjs.map} +1 -1
- package/fesm2022/{momentumcms-admin-blocks-field.component-BxJRfiV3.mjs → momentumcms-admin-blocks-field.component-BQHIguqN.mjs} +119 -30
- package/fesm2022/momentumcms-admin-blocks-field.component-BQHIguqN.mjs.map +1 -0
- package/fesm2022/{momentumcms-admin-collapsible-field.component-CsjYCkGw.mjs → momentumcms-admin-collapsible-field.component-DSE4X1xV.mjs} +2 -2
- package/fesm2022/{momentumcms-admin-collapsible-field.component-CsjYCkGw.mjs.map → momentumcms-admin-collapsible-field.component-DSE4X1xV.mjs.map} +1 -1
- package/fesm2022/{momentumcms-admin-global-edit.page-CmLAM17O.mjs → momentumcms-admin-global-edit.page-CHr9vOxQ.mjs} +2 -2
- package/fesm2022/{momentumcms-admin-global-edit.page-CmLAM17O.mjs.map → momentumcms-admin-global-edit.page-CHr9vOxQ.mjs.map} +1 -1
- package/fesm2022/{momentumcms-admin-group-field.component-CMKcqfjy.mjs → momentumcms-admin-group-field.component-qy0Z-cRK.mjs} +2 -2
- package/fesm2022/{momentumcms-admin-group-field.component-CMKcqfjy.mjs.map → momentumcms-admin-group-field.component-qy0Z-cRK.mjs.map} +1 -1
- package/fesm2022/{momentumcms-admin-momentumcms-admin-BTZEdMNj.mjs → momentumcms-admin-momentumcms-admin-DDwm1Rm3.mjs} +1123 -450
- package/fesm2022/momentumcms-admin-momentumcms-admin-DDwm1Rm3.mjs.map +1 -0
- package/fesm2022/{momentumcms-admin-relationship-field.component-DNZUCENa.mjs → momentumcms-admin-relationship-field.component-DS68G61P.mjs} +2 -2
- package/fesm2022/{momentumcms-admin-relationship-field.component-DNZUCENa.mjs.map → momentumcms-admin-relationship-field.component-DS68G61P.mjs.map} +1 -1
- package/fesm2022/{momentumcms-admin-rich-text-field.component-BVAQkX3O.mjs → momentumcms-admin-rich-text-field.component-CZQSu4lc.mjs} +2 -2
- package/fesm2022/{momentumcms-admin-rich-text-field.component-BVAQkX3O.mjs.map → momentumcms-admin-rich-text-field.component-CZQSu4lc.mjs.map} +1 -1
- package/fesm2022/{momentumcms-admin-row-field.component-0F6cnUK_.mjs → momentumcms-admin-row-field.component-109gO-Rp.mjs} +2 -2
- package/fesm2022/{momentumcms-admin-row-field.component-0F6cnUK_.mjs.map → momentumcms-admin-row-field.component-109gO-Rp.mjs.map} +1 -1
- package/fesm2022/{momentumcms-admin-tabs-field.component-qYlbl8Ud.mjs → momentumcms-admin-tabs-field.component-DG8vPjj4.mjs} +2 -2
- package/fesm2022/{momentumcms-admin-tabs-field.component-qYlbl8Ud.mjs.map → momentumcms-admin-tabs-field.component-DG8vPjj4.mjs.map} +1 -1
- package/fesm2022/momentumcms-admin.mjs +1 -1
- package/package.json +1 -1
- package/types/momentumcms-admin.d.ts +231 -5
- package/fesm2022/momentumcms-admin-blocks-field.component-BxJRfiV3.mjs.map +0 -1
- package/fesm2022/momentumcms-admin-momentumcms-admin-BTZEdMNj.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-CHr9vOxQ.mjs').then((m) => m.GlobalEditPage),
|
|
1177
|
+
},
|
|
1138
1178
|
canDeactivate: [unsavedChangesGuard],
|
|
1139
1179
|
},
|
|
1140
1180
|
// Plugin-registered routes
|
|
@@ -3185,6 +3225,239 @@ function getPluginRoutesFromRouteData(data) {
|
|
|
3185
3225
|
return [];
|
|
3186
3226
|
}
|
|
3187
3227
|
|
|
3228
|
+
/**
|
|
3229
|
+
* Registry for swappable admin page components.
|
|
3230
|
+
*
|
|
3231
|
+
* Maps page keys (e.g., 'dashboard', 'collection-list') to lazy component loaders.
|
|
3232
|
+
* Per-collection overrides use the pattern: 'collections/{slug}/{type}'.
|
|
3233
|
+
*
|
|
3234
|
+
* Resolution chain: per-collection → global → undefined (use built-in default).
|
|
3235
|
+
*/
|
|
3236
|
+
class AdminComponentRegistry {
|
|
3237
|
+
components = new Map();
|
|
3238
|
+
/** Register a lazy loader for a page key. Later registrations override earlier ones. */
|
|
3239
|
+
register(key, loader) {
|
|
3240
|
+
this.components.set(key, loader);
|
|
3241
|
+
}
|
|
3242
|
+
/** Get the lazy loader for a page key. Returns undefined if not registered. */
|
|
3243
|
+
get(key) {
|
|
3244
|
+
return this.components.get(key);
|
|
3245
|
+
}
|
|
3246
|
+
/** Check if a page key has a registered component. */
|
|
3247
|
+
has(key) {
|
|
3248
|
+
return this.components.has(key);
|
|
3249
|
+
}
|
|
3250
|
+
/**
|
|
3251
|
+
* Resolve a component loader with per-collection fallback.
|
|
3252
|
+
*
|
|
3253
|
+
* For collection pages, tries `collections/{slug}/{type}` first,
|
|
3254
|
+
* then falls back to the global key (e.g., 'collection-list').
|
|
3255
|
+
*/
|
|
3256
|
+
resolve(key, slug) {
|
|
3257
|
+
if (slug) {
|
|
3258
|
+
const type = key.replace('collection-', '');
|
|
3259
|
+
const perCollectionKey = `collections/${slug}/${type}`;
|
|
3260
|
+
const perCollection = this.components.get(perCollectionKey);
|
|
3261
|
+
if (perCollection)
|
|
3262
|
+
return perCollection;
|
|
3263
|
+
}
|
|
3264
|
+
return this.components.get(key);
|
|
3265
|
+
}
|
|
3266
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AdminComponentRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
3267
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AdminComponentRegistry, providedIn: 'root' });
|
|
3268
|
+
}
|
|
3269
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AdminComponentRegistry, decorators: [{
|
|
3270
|
+
type: Injectable,
|
|
3271
|
+
args: [{ providedIn: 'root' }]
|
|
3272
|
+
}] });
|
|
3273
|
+
|
|
3274
|
+
/**
|
|
3275
|
+
* Registry for admin layout slot components.
|
|
3276
|
+
*
|
|
3277
|
+
* Slots are additive — multiple components can be registered for the same slot key.
|
|
3278
|
+
* Per-collection slots use the pattern: `{base-slot}:{collection-slug}`.
|
|
3279
|
+
*
|
|
3280
|
+
* Resolution merges global + per-collection loaders (global first).
|
|
3281
|
+
*
|
|
3282
|
+
* The registry is signal-aware: `resolve()` reads an internal version signal,
|
|
3283
|
+
* so Angular effects that call `resolve()` will re-run when new slots are registered.
|
|
3284
|
+
*/
|
|
3285
|
+
class AdminSlotRegistry {
|
|
3286
|
+
slots = new Map();
|
|
3287
|
+
/** Incremented on every register() so signal-based consumers re-evaluate. */
|
|
3288
|
+
_version = signal(0, ...(ngDevMode ? [{ debugName: "_version" }] : []));
|
|
3289
|
+
/** Register a lazy loader for a slot. Multiple loaders per slot are supported. Duplicate loaders are skipped. */
|
|
3290
|
+
register(slot, loader) {
|
|
3291
|
+
const existing = this.slots.get(slot) ?? [];
|
|
3292
|
+
if (existing.includes(loader))
|
|
3293
|
+
return;
|
|
3294
|
+
existing.push(loader);
|
|
3295
|
+
this.slots.set(slot, existing);
|
|
3296
|
+
this._version.update((v) => v + 1);
|
|
3297
|
+
}
|
|
3298
|
+
/** Get all loaders for a slot key. Returns empty array if none registered. */
|
|
3299
|
+
getAll(slot) {
|
|
3300
|
+
return this.slots.get(slot) ?? [];
|
|
3301
|
+
}
|
|
3302
|
+
/** Check if a slot has any registered loaders. */
|
|
3303
|
+
has(slot) {
|
|
3304
|
+
return this.slots.has(slot);
|
|
3305
|
+
}
|
|
3306
|
+
/**
|
|
3307
|
+
* Resolve slot loaders, merging global and per-collection entries.
|
|
3308
|
+
*
|
|
3309
|
+
* Reads the internal version signal so Angular effects tracking this call
|
|
3310
|
+
* will re-run when new slots are registered.
|
|
3311
|
+
*/
|
|
3312
|
+
resolve(slot, slug) {
|
|
3313
|
+
this._version();
|
|
3314
|
+
const global = this.getAll(slot);
|
|
3315
|
+
if (!slug)
|
|
3316
|
+
return global;
|
|
3317
|
+
const perCollection = this.getAll(`${slot}:${slug}`);
|
|
3318
|
+
return [...global, ...perCollection];
|
|
3319
|
+
}
|
|
3320
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AdminSlotRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
3321
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AdminSlotRegistry, providedIn: 'root' });
|
|
3322
|
+
}
|
|
3323
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AdminSlotRegistry, decorators: [{
|
|
3324
|
+
type: Injectable,
|
|
3325
|
+
args: [{ providedIn: 'root' }]
|
|
3326
|
+
}] });
|
|
3327
|
+
|
|
3328
|
+
/**
|
|
3329
|
+
* Register a custom admin page component override.
|
|
3330
|
+
*
|
|
3331
|
+
* ```typescript
|
|
3332
|
+
* export const appConfig: ApplicationConfig = {
|
|
3333
|
+
* providers: [
|
|
3334
|
+
* provideAdminComponent('dashboard', () =>
|
|
3335
|
+
* import('./custom-dashboard.component').then(m => m.CustomDashboard)
|
|
3336
|
+
* ),
|
|
3337
|
+
* ],
|
|
3338
|
+
* };
|
|
3339
|
+
* ```
|
|
3340
|
+
*/
|
|
3341
|
+
function provideAdminComponent(key, loader) {
|
|
3342
|
+
return makeEnvironmentProviders([
|
|
3343
|
+
{
|
|
3344
|
+
provide: ENVIRONMENT_INITIALIZER,
|
|
3345
|
+
multi: true,
|
|
3346
|
+
useFactory: () => {
|
|
3347
|
+
const registry = inject(AdminComponentRegistry);
|
|
3348
|
+
return () => {
|
|
3349
|
+
registry.register(key, loader);
|
|
3350
|
+
};
|
|
3351
|
+
},
|
|
3352
|
+
},
|
|
3353
|
+
]);
|
|
3354
|
+
}
|
|
3355
|
+
/**
|
|
3356
|
+
* Register a component into an admin layout slot.
|
|
3357
|
+
*
|
|
3358
|
+
* ```typescript
|
|
3359
|
+
* export const appConfig: ApplicationConfig = {
|
|
3360
|
+
* providers: [
|
|
3361
|
+
* provideAdminSlot('dashboard:before', () =>
|
|
3362
|
+
* import('./welcome-banner.component').then(m => m.WelcomeBanner)
|
|
3363
|
+
* ),
|
|
3364
|
+
* ],
|
|
3365
|
+
* };
|
|
3366
|
+
* ```
|
|
3367
|
+
*/
|
|
3368
|
+
function provideAdminSlot(slot, loader) {
|
|
3369
|
+
return makeEnvironmentProviders([
|
|
3370
|
+
{
|
|
3371
|
+
provide: ENVIRONMENT_INITIALIZER,
|
|
3372
|
+
multi: true,
|
|
3373
|
+
useFactory: () => {
|
|
3374
|
+
const registry = inject(AdminSlotRegistry);
|
|
3375
|
+
return () => {
|
|
3376
|
+
registry.register(slot, loader);
|
|
3377
|
+
};
|
|
3378
|
+
},
|
|
3379
|
+
},
|
|
3380
|
+
]);
|
|
3381
|
+
}
|
|
3382
|
+
/** Mapping from AdminComponentsConfig page keys to registry keys. */
|
|
3383
|
+
const PAGE_KEY_MAP = {
|
|
3384
|
+
dashboard: 'dashboard',
|
|
3385
|
+
login: 'login',
|
|
3386
|
+
media: 'media',
|
|
3387
|
+
};
|
|
3388
|
+
/** Mapping from AdminComponentsConfig slot keys to registry slot keys. */
|
|
3389
|
+
const GLOBAL_SLOT_MAP = {
|
|
3390
|
+
beforeNavigation: 'shell:nav-start',
|
|
3391
|
+
afterNavigation: 'shell:nav-end',
|
|
3392
|
+
header: 'shell:header',
|
|
3393
|
+
footer: 'shell:footer',
|
|
3394
|
+
beforeDashboard: 'dashboard:before',
|
|
3395
|
+
afterDashboard: 'dashboard:after',
|
|
3396
|
+
beforeLogin: 'login:before',
|
|
3397
|
+
afterLogin: 'login:after',
|
|
3398
|
+
};
|
|
3399
|
+
/** Mapping from CollectionAdminComponentsConfig page keys to page type suffixes. */
|
|
3400
|
+
const COLLECTION_PAGE_MAP = {
|
|
3401
|
+
list: 'list',
|
|
3402
|
+
edit: 'edit',
|
|
3403
|
+
view: 'view',
|
|
3404
|
+
};
|
|
3405
|
+
/** Mapping from CollectionAdminComponentsConfig slot keys to slot key patterns. */
|
|
3406
|
+
const COLLECTION_SLOT_MAP = {
|
|
3407
|
+
beforeList: 'collection-list:before',
|
|
3408
|
+
afterList: 'collection-list:after',
|
|
3409
|
+
beforeEdit: 'collection-edit:before',
|
|
3410
|
+
afterEdit: 'collection-edit:after',
|
|
3411
|
+
editSidebar: 'collection-edit:sidebar',
|
|
3412
|
+
beforeView: 'collection-view:before',
|
|
3413
|
+
afterView: 'collection-view:after',
|
|
3414
|
+
};
|
|
3415
|
+
/**
|
|
3416
|
+
* Read AdminComponentsConfig and CollectionAdminComponentsConfig from config
|
|
3417
|
+
* and register them into AdminComponentRegistry and AdminSlotRegistry.
|
|
3418
|
+
*
|
|
3419
|
+
* Called once in AdminShellComponent.ngOnInit() after route data is available.
|
|
3420
|
+
*/
|
|
3421
|
+
function registerConfigComponents(collections, adminComponents, componentRegistry, slotRegistry) {
|
|
3422
|
+
// Register global page overrides and slots from AdminComponentsConfig
|
|
3423
|
+
if (adminComponents) {
|
|
3424
|
+
for (const [configKey, registryKey] of Object.entries(PAGE_KEY_MAP)) {
|
|
3425
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Object.entries returns string keys
|
|
3426
|
+
const loader = adminComponents[configKey];
|
|
3427
|
+
if (loader) {
|
|
3428
|
+
componentRegistry.register(registryKey, loader);
|
|
3429
|
+
}
|
|
3430
|
+
}
|
|
3431
|
+
for (const [configKey, slotKey] of Object.entries(GLOBAL_SLOT_MAP)) {
|
|
3432
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Object.entries returns string keys
|
|
3433
|
+
const loader = adminComponents[configKey];
|
|
3434
|
+
if (loader) {
|
|
3435
|
+
slotRegistry.register(slotKey, loader);
|
|
3436
|
+
}
|
|
3437
|
+
}
|
|
3438
|
+
}
|
|
3439
|
+
// Register per-collection page overrides and slots
|
|
3440
|
+
for (const collection of collections) {
|
|
3441
|
+
const components = collection.admin?.components;
|
|
3442
|
+
if (!components)
|
|
3443
|
+
continue;
|
|
3444
|
+
for (const [configKey, typeSuffix] of Object.entries(COLLECTION_PAGE_MAP)) {
|
|
3445
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Object.entries returns string keys
|
|
3446
|
+
const loader = components[configKey];
|
|
3447
|
+
if (loader) {
|
|
3448
|
+
componentRegistry.register(`collections/${collection.slug}/${typeSuffix}`, loader);
|
|
3449
|
+
}
|
|
3450
|
+
}
|
|
3451
|
+
for (const [configKey, baseSlotKey] of Object.entries(COLLECTION_SLOT_MAP)) {
|
|
3452
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Object.entries returns string keys
|
|
3453
|
+
const loader = components[configKey];
|
|
3454
|
+
if (loader) {
|
|
3455
|
+
slotRegistry.register(`${baseSlotKey}:${collection.slug}`, loader);
|
|
3456
|
+
}
|
|
3457
|
+
}
|
|
3458
|
+
}
|
|
3459
|
+
}
|
|
3460
|
+
|
|
3188
3461
|
/**
|
|
3189
3462
|
* Entity Form Widget Types
|
|
3190
3463
|
*
|
|
@@ -4229,106 +4502,302 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
4229
4502
|
}], 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
4503
|
|
|
4231
4504
|
/**
|
|
4232
|
-
*
|
|
4505
|
+
* Publish Controls Widget
|
|
4233
4506
|
*
|
|
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
|
|
4507
|
+
* Displays the current document status and provides publish/unpublish actions.
|
|
4240
4508
|
*
|
|
4241
4509
|
* @example
|
|
4242
4510
|
* ```html
|
|
4243
|
-
* <mcms-
|
|
4244
|
-
* [
|
|
4245
|
-
* [
|
|
4511
|
+
* <mcms-publish-controls
|
|
4512
|
+
* [collection]="'posts'"
|
|
4513
|
+
* [documentId]="'abc123'"
|
|
4514
|
+
* [documentLabel]="'Post'"
|
|
4515
|
+
* (statusChanged)="onStatusChanged($event)"
|
|
4246
4516
|
* />
|
|
4247
4517
|
* ```
|
|
4248
4518
|
*/
|
|
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;
|
|
4519
|
+
class PublishControlsWidget {
|
|
4520
|
+
versionService = inject(VersionService);
|
|
4521
|
+
feedback = inject(FeedbackService);
|
|
4522
|
+
/** Collection slug */
|
|
4523
|
+
collection = input.required(...(ngDevMode ? [{ debugName: "collection" }] : []));
|
|
4524
|
+
/** Document ID */
|
|
4525
|
+
documentId = input.required(...(ngDevMode ? [{ debugName: "documentId" }] : []));
|
|
4526
|
+
/** Document label for feedback messages */
|
|
4527
|
+
documentLabel = input('Document', ...(ngDevMode ? [{ debugName: "documentLabel" }] : []));
|
|
4528
|
+
/** Initial status (optional, will be fetched if not provided) */
|
|
4529
|
+
initialStatus = input(undefined, ...(ngDevMode ? [{ debugName: "initialStatus" }] : []));
|
|
4530
|
+
/** Emitted when the status changes */
|
|
4531
|
+
statusChanged = output();
|
|
4532
|
+
/** Current status */
|
|
4533
|
+
status = signal('draft', ...(ngDevMode ? [{ debugName: "status" }] : []));
|
|
4534
|
+
/** Whether a status update is in progress */
|
|
4535
|
+
isUpdating = signal(false, ...(ngDevMode ? [{ debugName: "isUpdating" }] : []));
|
|
4536
|
+
/** Whether status is loading */
|
|
4537
|
+
isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
|
|
4538
|
+
/** Badge variant based on status (derived from status signal) */
|
|
4539
|
+
statusVariant = computed(() => this.status() === 'published' ? 'default' : 'secondary', ...(ngDevMode ? [{ debugName: "statusVariant" }] : []));
|
|
4540
|
+
/** Status label (derived from status signal) */
|
|
4541
|
+
statusLabel = computed(() => (this.status() === 'published' ? 'Published' : 'Draft'), ...(ngDevMode ? [{ debugName: "statusLabel" }] : []));
|
|
4542
|
+
constructor() {
|
|
4543
|
+
// Load status when inputs change
|
|
4544
|
+
effect(() => {
|
|
4545
|
+
const collection = this.collection();
|
|
4546
|
+
const docId = this.documentId();
|
|
4547
|
+
const initial = this.initialStatus();
|
|
4548
|
+
if (initial !== undefined) {
|
|
4549
|
+
this.updateStatusDisplay(initial);
|
|
4550
|
+
}
|
|
4551
|
+
else if (collection && docId) {
|
|
4552
|
+
this.loadStatus(collection, docId);
|
|
4553
|
+
}
|
|
4554
|
+
});
|
|
4555
|
+
}
|
|
4556
|
+
/**
|
|
4557
|
+
* Load the current status from the API.
|
|
4558
|
+
*/
|
|
4559
|
+
async loadStatus(collection, docId) {
|
|
4560
|
+
this.isLoading.set(true);
|
|
4561
|
+
try {
|
|
4562
|
+
const status = await this.versionService.getStatus(collection, docId);
|
|
4563
|
+
this.updateStatusDisplay(status);
|
|
4316
4564
|
}
|
|
4317
|
-
|
|
4318
|
-
|
|
4565
|
+
catch {
|
|
4566
|
+
// Default to draft if we can't load status
|
|
4567
|
+
this.updateStatusDisplay('draft');
|
|
4319
4568
|
}
|
|
4320
|
-
|
|
4321
|
-
|
|
4569
|
+
finally {
|
|
4570
|
+
this.isLoading.set(false);
|
|
4322
4571
|
}
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4572
|
+
}
|
|
4573
|
+
/**
|
|
4574
|
+
* Update the status display.
|
|
4575
|
+
* statusVariant and statusLabel are computed signals that automatically update.
|
|
4576
|
+
*/
|
|
4577
|
+
updateStatusDisplay(status) {
|
|
4578
|
+
this.status.set(status);
|
|
4579
|
+
}
|
|
4580
|
+
/**
|
|
4581
|
+
* Publish the document.
|
|
4582
|
+
*/
|
|
4583
|
+
async onPublish() {
|
|
4584
|
+
this.isUpdating.set(true);
|
|
4585
|
+
try {
|
|
4586
|
+
await this.versionService.publish(this.collection(), this.documentId());
|
|
4587
|
+
this.updateStatusDisplay('published');
|
|
4588
|
+
this.statusChanged.emit('published');
|
|
4589
|
+
}
|
|
4590
|
+
catch {
|
|
4591
|
+
// Error handled by crudToastInterceptor
|
|
4592
|
+
}
|
|
4593
|
+
finally {
|
|
4594
|
+
this.isUpdating.set(false);
|
|
4595
|
+
}
|
|
4596
|
+
}
|
|
4597
|
+
/**
|
|
4598
|
+
* Unpublish the document.
|
|
4599
|
+
*/
|
|
4600
|
+
async onUnpublish() {
|
|
4601
|
+
const confirmed = await this.feedback.confirmUnpublish(this.documentLabel());
|
|
4602
|
+
if (!confirmed) {
|
|
4603
|
+
return;
|
|
4604
|
+
}
|
|
4605
|
+
this.isUpdating.set(true);
|
|
4606
|
+
try {
|
|
4607
|
+
await this.versionService.unpublish(this.collection(), this.documentId());
|
|
4608
|
+
this.updateStatusDisplay('draft');
|
|
4609
|
+
this.statusChanged.emit('draft');
|
|
4610
|
+
}
|
|
4611
|
+
catch {
|
|
4612
|
+
// Error handled by crudToastInterceptor
|
|
4613
|
+
}
|
|
4614
|
+
finally {
|
|
4615
|
+
this.isUpdating.set(false);
|
|
4616
|
+
}
|
|
4617
|
+
}
|
|
4618
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PublishControlsWidget, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4619
|
+
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: `
|
|
4620
|
+
<mcms-badge [variant]="statusVariant()">
|
|
4621
|
+
{{ statusLabel() }}
|
|
4622
|
+
</mcms-badge>
|
|
4623
|
+
|
|
4624
|
+
@if (status() === 'draft') {
|
|
4625
|
+
<button
|
|
4626
|
+
mcms-button
|
|
4627
|
+
variant="primary"
|
|
4628
|
+
size="sm"
|
|
4629
|
+
[disabled]="isUpdating()"
|
|
4630
|
+
(click)="onPublish()"
|
|
4631
|
+
>
|
|
4632
|
+
@if (isUpdating()) {
|
|
4633
|
+
Publishing...
|
|
4634
|
+
} @else {
|
|
4635
|
+
Publish
|
|
4636
|
+
}
|
|
4637
|
+
</button>
|
|
4638
|
+
} @else {
|
|
4639
|
+
<button
|
|
4640
|
+
mcms-button
|
|
4641
|
+
variant="outline"
|
|
4642
|
+
size="sm"
|
|
4643
|
+
[disabled]="isUpdating()"
|
|
4644
|
+
(click)="onUnpublish()"
|
|
4645
|
+
>
|
|
4646
|
+
@if (isUpdating()) {
|
|
4647
|
+
Unpublishing...
|
|
4648
|
+
} @else {
|
|
4649
|
+
Unpublish
|
|
4650
|
+
}
|
|
4651
|
+
</button>
|
|
4652
|
+
}
|
|
4653
|
+
`, 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 });
|
|
4654
|
+
}
|
|
4655
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PublishControlsWidget, decorators: [{
|
|
4656
|
+
type: Component,
|
|
4657
|
+
args: [{
|
|
4658
|
+
selector: 'mcms-publish-controls',
|
|
4659
|
+
imports: [Badge, Button],
|
|
4660
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
4661
|
+
host: { class: 'inline-flex items-center gap-3' },
|
|
4662
|
+
template: `
|
|
4663
|
+
<mcms-badge [variant]="statusVariant()">
|
|
4664
|
+
{{ statusLabel() }}
|
|
4665
|
+
</mcms-badge>
|
|
4666
|
+
|
|
4667
|
+
@if (status() === 'draft') {
|
|
4668
|
+
<button
|
|
4669
|
+
mcms-button
|
|
4670
|
+
variant="primary"
|
|
4671
|
+
size="sm"
|
|
4672
|
+
[disabled]="isUpdating()"
|
|
4673
|
+
(click)="onPublish()"
|
|
4674
|
+
>
|
|
4675
|
+
@if (isUpdating()) {
|
|
4676
|
+
Publishing...
|
|
4677
|
+
} @else {
|
|
4678
|
+
Publish
|
|
4679
|
+
}
|
|
4680
|
+
</button>
|
|
4681
|
+
} @else {
|
|
4682
|
+
<button
|
|
4683
|
+
mcms-button
|
|
4684
|
+
variant="outline"
|
|
4685
|
+
size="sm"
|
|
4686
|
+
[disabled]="isUpdating()"
|
|
4687
|
+
(click)="onUnpublish()"
|
|
4688
|
+
>
|
|
4689
|
+
@if (isUpdating()) {
|
|
4690
|
+
Unpublishing...
|
|
4691
|
+
} @else {
|
|
4692
|
+
Unpublish
|
|
4693
|
+
}
|
|
4694
|
+
</button>
|
|
4695
|
+
}
|
|
4696
|
+
`,
|
|
4697
|
+
}]
|
|
4698
|
+
}], 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"] }] } });
|
|
4699
|
+
|
|
4700
|
+
/**
|
|
4701
|
+
* Media Preview Component
|
|
4702
|
+
*
|
|
4703
|
+
* Displays a preview of media based on its type:
|
|
4704
|
+
* - Images: Thumbnail preview
|
|
4705
|
+
* - Videos: Video icon with optional poster
|
|
4706
|
+
* - Audio: Audio icon
|
|
4707
|
+
* - Documents: Document icon
|
|
4708
|
+
* - Other: Generic file icon
|
|
4709
|
+
*
|
|
4710
|
+
* @example
|
|
4711
|
+
* ```html
|
|
4712
|
+
* <mcms-media-preview
|
|
4713
|
+
* [media]="mediaDocument"
|
|
4714
|
+
* [size]="'md'"
|
|
4715
|
+
* />
|
|
4716
|
+
* ```
|
|
4717
|
+
*/
|
|
4718
|
+
class MediaPreviewComponent {
|
|
4719
|
+
/** Media data to preview */
|
|
4720
|
+
media = input(null, ...(ngDevMode ? [{ debugName: "media" }] : []));
|
|
4721
|
+
/** Size of the preview */
|
|
4722
|
+
size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : []));
|
|
4723
|
+
/** Custom class override */
|
|
4724
|
+
class = input('', ...(ngDevMode ? [{ debugName: "class" }] : []));
|
|
4725
|
+
/** Whether to show rounded corners */
|
|
4726
|
+
rounded = input(true, ...(ngDevMode ? [{ debugName: "rounded" }] : []));
|
|
4727
|
+
/** Host classes */
|
|
4728
|
+
hostClasses = computed(() => {
|
|
4729
|
+
const sizeClass = this.sizeClasses()[this.size()];
|
|
4730
|
+
const roundedClass = this.rounded() ? 'rounded-md overflow-hidden' : '';
|
|
4731
|
+
return `inline-block ${sizeClass} ${roundedClass} ${this.class()}`;
|
|
4732
|
+
}, ...(ngDevMode ? [{ debugName: "hostClasses" }] : []));
|
|
4733
|
+
/** Size classes map */
|
|
4734
|
+
sizeClasses = computed(() => ({
|
|
4735
|
+
xs: 'h-8 w-8',
|
|
4736
|
+
sm: 'h-12 w-12',
|
|
4737
|
+
md: 'h-20 w-20',
|
|
4738
|
+
lg: 'h-32 w-32',
|
|
4739
|
+
xl: 'h-48 w-48',
|
|
4740
|
+
}), ...(ngDevMode ? [{ debugName: "sizeClasses" }] : []));
|
|
4741
|
+
/** Icon size classes */
|
|
4742
|
+
iconClasses = computed(() => {
|
|
4743
|
+
const sizes = {
|
|
4744
|
+
xs: 'text-lg',
|
|
4745
|
+
sm: 'text-xl',
|
|
4746
|
+
md: 'text-3xl',
|
|
4747
|
+
lg: 'text-4xl',
|
|
4748
|
+
xl: 'text-6xl',
|
|
4749
|
+
};
|
|
4750
|
+
return `${sizes[this.size()]} text-mcms-muted-foreground`;
|
|
4751
|
+
}, ...(ngDevMode ? [{ debugName: "iconClasses" }] : []));
|
|
4752
|
+
/** Whether the media is an image */
|
|
4753
|
+
isImage = computed(() => {
|
|
4754
|
+
const mimeType = this.media()?.mimeType ?? '';
|
|
4755
|
+
return mimeType.startsWith('image/');
|
|
4756
|
+
}, ...(ngDevMode ? [{ debugName: "isImage" }] : []));
|
|
4757
|
+
/** Whether the media is a video */
|
|
4758
|
+
isVideo = computed(() => {
|
|
4759
|
+
const mimeType = this.media()?.mimeType ?? '';
|
|
4760
|
+
return mimeType.startsWith('video/');
|
|
4761
|
+
}, ...(ngDevMode ? [{ debugName: "isVideo" }] : []));
|
|
4762
|
+
/** Whether the media is audio */
|
|
4763
|
+
isAudio = computed(() => {
|
|
4764
|
+
const mimeType = this.media()?.mimeType ?? '';
|
|
4765
|
+
return mimeType.startsWith('audio/');
|
|
4766
|
+
}, ...(ngDevMode ? [{ debugName: "isAudio" }] : []));
|
|
4767
|
+
/** Image URL for preview */
|
|
4768
|
+
imageUrl = computed(() => {
|
|
4769
|
+
const media = this.media();
|
|
4770
|
+
if (!media)
|
|
4771
|
+
return '';
|
|
4772
|
+
return media.url ?? `/api/media/file/${media.path}`;
|
|
4773
|
+
}, ...(ngDevMode ? [{ debugName: "imageUrl" }] : []));
|
|
4774
|
+
/** Icon name based on media type */
|
|
4775
|
+
iconName = computed(() => {
|
|
4776
|
+
const mimeType = this.media()?.mimeType ?? '';
|
|
4777
|
+
if (mimeType.startsWith('video/')) {
|
|
4778
|
+
return heroFilm;
|
|
4779
|
+
}
|
|
4780
|
+
if (mimeType.startsWith('audio/')) {
|
|
4781
|
+
return heroMusicalNote;
|
|
4782
|
+
}
|
|
4783
|
+
if (mimeType === 'application/pdf') {
|
|
4784
|
+
return heroDocumentText;
|
|
4785
|
+
}
|
|
4786
|
+
if (mimeType.startsWith('application/zip') || mimeType.includes('compressed')) {
|
|
4787
|
+
return heroArchiveBox;
|
|
4788
|
+
}
|
|
4789
|
+
if (mimeType.startsWith('image/')) {
|
|
4790
|
+
return heroPhoto;
|
|
4791
|
+
}
|
|
4792
|
+
return heroDocument;
|
|
4793
|
+
}, ...(ngDevMode ? [{ debugName: "iconName" }] : []));
|
|
4794
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: MediaPreviewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4795
|
+
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: `
|
|
4796
|
+
@if (isImage()) {
|
|
4797
|
+
<img
|
|
4798
|
+
[src]="imageUrl()"
|
|
4799
|
+
[alt]="media()?.alt ?? media()?.filename ?? 'Media preview'"
|
|
4800
|
+
class="h-full w-full object-cover"
|
|
4332
4801
|
/>
|
|
4333
4802
|
} @else {
|
|
4334
4803
|
<div class="flex h-full w-full items-center justify-center bg-mcms-muted">
|
|
@@ -5307,8 +5776,17 @@ class EntityFormWidget {
|
|
|
5307
5776
|
draftSaved = output();
|
|
5308
5777
|
/** Model signal — the single source of truth for form data */
|
|
5309
5778
|
formModel = signal({}, ...(ngDevMode ? [{ debugName: "formModel" }] : []));
|
|
5310
|
-
/**
|
|
5311
|
-
|
|
5779
|
+
/** Reactive form data that tracks Signal Forms changes for downstream consumers (e.g. live preview).
|
|
5780
|
+
* Reads the root FieldState value directly so it re-triggers when any field value changes,
|
|
5781
|
+
* unlike formModel which may keep the same object reference after in-place mutations. */
|
|
5782
|
+
formData = computed(() => {
|
|
5783
|
+
const ef = this.entityForm();
|
|
5784
|
+
if (!ef)
|
|
5785
|
+
return this.formModel();
|
|
5786
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- FieldState value is the full model
|
|
5787
|
+
const val = ef().value();
|
|
5788
|
+
return val ?? this.formModel();
|
|
5789
|
+
}, ...(ngDevMode ? [{ debugName: "formData" }] : []));
|
|
5312
5790
|
/** Signal forms tree — created once when collection is available */
|
|
5313
5791
|
entityForm = signal(null, ...(ngDevMode ? [{ debugName: "entityForm" }] : []));
|
|
5314
5792
|
/** Original data for edit mode */
|
|
@@ -5473,6 +5951,13 @@ class EntityFormWidget {
|
|
|
5473
5951
|
}
|
|
5474
5952
|
return false;
|
|
5475
5953
|
}, ...(ngDevMode ? [{ debugName: "hasVersioning" }] : []));
|
|
5954
|
+
/** Current document status (from form model or default to 'draft') */
|
|
5955
|
+
documentStatus = computed(() => {
|
|
5956
|
+
const data = this.formModel();
|
|
5957
|
+
if (data['_status'] === 'published')
|
|
5958
|
+
return 'published';
|
|
5959
|
+
return 'draft';
|
|
5960
|
+
}, ...(ngDevMode ? [{ debugName: "documentStatus" }] : []));
|
|
5476
5961
|
/** Whether draft save is available (edit mode with existing entity) */
|
|
5477
5962
|
canSaveDraft = computed(() => {
|
|
5478
5963
|
return this.hasVersioning() && this.mode() === 'edit' && !!this.entityId();
|
|
@@ -5836,6 +6321,13 @@ class EntityFormWidget {
|
|
|
5836
6321
|
switchToEdit() {
|
|
5837
6322
|
this.modeChange.emit('edit');
|
|
5838
6323
|
}
|
|
6324
|
+
/**
|
|
6325
|
+
* Handle status change from publish controls.
|
|
6326
|
+
*/
|
|
6327
|
+
onStatusChanged(status) {
|
|
6328
|
+
const data = { ...this.formModel(), _status: status };
|
|
6329
|
+
this.formModel.set(data);
|
|
6330
|
+
}
|
|
5839
6331
|
/**
|
|
5840
6332
|
* Handle version restore — reload the entity data.
|
|
5841
6333
|
*/
|
|
@@ -5889,17 +6381,28 @@ class EntityFormWidget {
|
|
|
5889
6381
|
<div class="mb-8">
|
|
5890
6382
|
<div class="flex items-start justify-between gap-4">
|
|
5891
6383
|
<div>
|
|
5892
|
-
<
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
6384
|
+
<div class="flex items-center gap-3">
|
|
6385
|
+
<h1 class="text-2xl font-semibold tracking-tight">
|
|
6386
|
+
@if (isGlobal()) {
|
|
6387
|
+
{{ collectionLabelSingular() }}
|
|
6388
|
+
} @else if (mode() === 'create') {
|
|
6389
|
+
Create {{ collectionLabelSingular() }}
|
|
6390
|
+
} @else if (mode() === 'edit') {
|
|
6391
|
+
Edit {{ collectionLabelSingular() }}
|
|
6392
|
+
} @else {
|
|
6393
|
+
View {{ collectionLabelSingular() }}
|
|
6394
|
+
}
|
|
6395
|
+
</h1>
|
|
6396
|
+
@if (hasVersioning() && mode() === 'edit' && entityId()) {
|
|
6397
|
+
<mcms-publish-controls
|
|
6398
|
+
[collection]="collection().slug"
|
|
6399
|
+
[documentId]="entityId() ?? ''"
|
|
6400
|
+
[documentLabel]="collectionLabelSingular()"
|
|
6401
|
+
[initialStatus]="documentStatus()"
|
|
6402
|
+
(statusChanged)="onStatusChanged($event)"
|
|
6403
|
+
/>
|
|
5901
6404
|
}
|
|
5902
|
-
</
|
|
6405
|
+
</div>
|
|
5903
6406
|
<p class="mt-1 text-muted-foreground">
|
|
5904
6407
|
@if (isGlobal()) {
|
|
5905
6408
|
Manage {{ collectionLabelSingular().toLowerCase() }} settings.
|
|
@@ -6025,14 +6528,14 @@ class EntityFormWidget {
|
|
|
6025
6528
|
<div class="mt-8">
|
|
6026
6529
|
<mcms-version-history
|
|
6027
6530
|
[collection]="collection().slug"
|
|
6028
|
-
[documentId]="entityId()
|
|
6531
|
+
[documentId]="entityId() ?? ''"
|
|
6029
6532
|
[documentLabel]="collectionLabelSingular()"
|
|
6030
6533
|
(restored)="onVersionRestored()"
|
|
6031
6534
|
/>
|
|
6032
6535
|
</div>
|
|
6033
6536
|
}
|
|
6034
6537
|
</div>
|
|
6035
|
-
`, 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 });
|
|
6538
|
+
`, 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 });
|
|
6036
6539
|
}
|
|
6037
6540
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: EntityFormWidget, decorators: [{
|
|
6038
6541
|
type: Component,
|
|
@@ -6050,6 +6553,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
6050
6553
|
BreadcrumbItem,
|
|
6051
6554
|
BreadcrumbSeparator,
|
|
6052
6555
|
VersionHistoryWidget,
|
|
6556
|
+
PublishControlsWidget,
|
|
6053
6557
|
CollectionUploadZoneComponent,
|
|
6054
6558
|
FocalPointPickerComponent,
|
|
6055
6559
|
ImageVariantsDisplay,
|
|
@@ -6074,17 +6578,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
6074
6578
|
<div class="mb-8">
|
|
6075
6579
|
<div class="flex items-start justify-between gap-4">
|
|
6076
6580
|
<div>
|
|
6077
|
-
<
|
|
6078
|
-
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
|
|
6581
|
+
<div class="flex items-center gap-3">
|
|
6582
|
+
<h1 class="text-2xl font-semibold tracking-tight">
|
|
6583
|
+
@if (isGlobal()) {
|
|
6584
|
+
{{ collectionLabelSingular() }}
|
|
6585
|
+
} @else if (mode() === 'create') {
|
|
6586
|
+
Create {{ collectionLabelSingular() }}
|
|
6587
|
+
} @else if (mode() === 'edit') {
|
|
6588
|
+
Edit {{ collectionLabelSingular() }}
|
|
6589
|
+
} @else {
|
|
6590
|
+
View {{ collectionLabelSingular() }}
|
|
6591
|
+
}
|
|
6592
|
+
</h1>
|
|
6593
|
+
@if (hasVersioning() && mode() === 'edit' && entityId()) {
|
|
6594
|
+
<mcms-publish-controls
|
|
6595
|
+
[collection]="collection().slug"
|
|
6596
|
+
[documentId]="entityId() ?? ''"
|
|
6597
|
+
[documentLabel]="collectionLabelSingular()"
|
|
6598
|
+
[initialStatus]="documentStatus()"
|
|
6599
|
+
(statusChanged)="onStatusChanged($event)"
|
|
6600
|
+
/>
|
|
6086
6601
|
}
|
|
6087
|
-
</
|
|
6602
|
+
</div>
|
|
6088
6603
|
<p class="mt-1 text-muted-foreground">
|
|
6089
6604
|
@if (isGlobal()) {
|
|
6090
6605
|
Manage {{ collectionLabelSingular().toLowerCase() }} settings.
|
|
@@ -6145,277 +6660,81 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
6145
6660
|
|
|
6146
6661
|
@if (isUploadCol() && formModelSizes()) {
|
|
6147
6662
|
<div class="mb-6">
|
|
6148
|
-
<mcms-image-variants-display [sizes]="formModelSizes()" />
|
|
6149
|
-
</div>
|
|
6150
|
-
}
|
|
6151
|
-
|
|
6152
|
-
<div class="space-y-6">
|
|
6153
|
-
@for (field of visibleFields(); track field.name) {
|
|
6154
|
-
<mcms-field-renderer
|
|
6155
|
-
[field]="field"
|
|
6156
|
-
[formNode]="getFormNode(field.name)"
|
|
6157
|
-
[formTree]="entityForm()"
|
|
6158
|
-
[formModel]="formModel()"
|
|
6159
|
-
[mode]="mode()"
|
|
6160
|
-
[path]="field.name"
|
|
6161
|
-
/>
|
|
6162
|
-
}
|
|
6163
|
-
</div>
|
|
6164
|
-
}
|
|
6165
|
-
</mcms-card-content>
|
|
6166
|
-
|
|
6167
|
-
<mcms-card-footer class="flex justify-end gap-3 border-t bg-muted/50 px-6 py-4">
|
|
6168
|
-
@if (mode() !== 'view') {
|
|
6169
|
-
<button
|
|
6170
|
-
mcms-button
|
|
6171
|
-
variant="outline"
|
|
6172
|
-
[disabled]="isSubmitting() || isSavingDraft()"
|
|
6173
|
-
(click)="onCancel()"
|
|
6174
|
-
>
|
|
6175
|
-
Cancel
|
|
6176
|
-
</button>
|
|
6177
|
-
@if (canSaveDraft()) {
|
|
6178
|
-
<button
|
|
6179
|
-
mcms-button
|
|
6180
|
-
variant="outline"
|
|
6181
|
-
[disabled]="isSubmitting() || isSavingDraft()"
|
|
6182
|
-
(click)="onSaveDraft()"
|
|
6183
|
-
>
|
|
6184
|
-
@if (isSavingDraft()) {
|
|
6185
|
-
<mcms-spinner size="sm" class="mr-2" />
|
|
6186
|
-
}
|
|
6187
|
-
Save Draft
|
|
6188
|
-
</button>
|
|
6189
|
-
}
|
|
6190
|
-
<button
|
|
6191
|
-
mcms-button
|
|
6192
|
-
variant="primary"
|
|
6193
|
-
[disabled]="isSubmitting() || isSavingDraft()"
|
|
6194
|
-
(click)="onSubmit()"
|
|
6195
|
-
>
|
|
6196
|
-
@if (isSubmitting()) {
|
|
6197
|
-
<mcms-spinner size="sm" class="mr-2" />
|
|
6198
|
-
}
|
|
6199
|
-
{{ mode() === 'create' ? 'Create' : 'Save Changes' }}
|
|
6200
|
-
</button>
|
|
6201
|
-
} @else {
|
|
6202
|
-
@if (canEdit()) {
|
|
6203
|
-
<button mcms-button variant="primary" (click)="switchToEdit()">Edit</button>
|
|
6204
|
-
}
|
|
6205
|
-
}
|
|
6206
|
-
</mcms-card-footer>
|
|
6207
|
-
</mcms-card>
|
|
6208
|
-
|
|
6209
|
-
@if (hasVersioning() && mode() === 'edit' && entityId()) {
|
|
6210
|
-
<div class="mt-8">
|
|
6211
|
-
<mcms-version-history
|
|
6212
|
-
[collection]="collection().slug"
|
|
6213
|
-
[documentId]="entityId()!"
|
|
6214
|
-
[documentLabel]="collectionLabelSingular()"
|
|
6215
|
-
(restored)="onVersionRestored()"
|
|
6216
|
-
/>
|
|
6217
|
-
</div>
|
|
6218
|
-
}
|
|
6219
|
-
</div>
|
|
6220
|
-
`,
|
|
6221
|
-
}]
|
|
6222
|
-
}], 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"] }] } });
|
|
6223
|
-
|
|
6224
|
-
/**
|
|
6225
|
-
* Publish Controls Widget
|
|
6226
|
-
*
|
|
6227
|
-
* Displays the current document status and provides publish/unpublish actions.
|
|
6228
|
-
*
|
|
6229
|
-
* @example
|
|
6230
|
-
* ```html
|
|
6231
|
-
* <mcms-publish-controls
|
|
6232
|
-
* [collection]="'posts'"
|
|
6233
|
-
* [documentId]="'abc123'"
|
|
6234
|
-
* [documentLabel]="'Post'"
|
|
6235
|
-
* (statusChanged)="onStatusChanged($event)"
|
|
6236
|
-
* />
|
|
6237
|
-
* ```
|
|
6238
|
-
*/
|
|
6239
|
-
class PublishControlsWidget {
|
|
6240
|
-
versionService = inject(VersionService);
|
|
6241
|
-
feedback = inject(FeedbackService);
|
|
6242
|
-
/** Collection slug */
|
|
6243
|
-
collection = input.required(...(ngDevMode ? [{ debugName: "collection" }] : []));
|
|
6244
|
-
/** Document ID */
|
|
6245
|
-
documentId = input.required(...(ngDevMode ? [{ debugName: "documentId" }] : []));
|
|
6246
|
-
/** Document label for feedback messages */
|
|
6247
|
-
documentLabel = input('Document', ...(ngDevMode ? [{ debugName: "documentLabel" }] : []));
|
|
6248
|
-
/** Initial status (optional, will be fetched if not provided) */
|
|
6249
|
-
initialStatus = input(undefined, ...(ngDevMode ? [{ debugName: "initialStatus" }] : []));
|
|
6250
|
-
/** Emitted when the status changes */
|
|
6251
|
-
statusChanged = output();
|
|
6252
|
-
/** Current status */
|
|
6253
|
-
status = signal('draft', ...(ngDevMode ? [{ debugName: "status" }] : []));
|
|
6254
|
-
/** Whether a status update is in progress */
|
|
6255
|
-
isUpdating = signal(false, ...(ngDevMode ? [{ debugName: "isUpdating" }] : []));
|
|
6256
|
-
/** Whether status is loading */
|
|
6257
|
-
isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
|
|
6258
|
-
/** Badge variant based on status (derived from status signal) */
|
|
6259
|
-
statusVariant = computed(() => this.status() === 'published' ? 'default' : 'secondary', ...(ngDevMode ? [{ debugName: "statusVariant" }] : []));
|
|
6260
|
-
/** Status label (derived from status signal) */
|
|
6261
|
-
statusLabel = computed(() => (this.status() === 'published' ? 'Published' : 'Draft'), ...(ngDevMode ? [{ debugName: "statusLabel" }] : []));
|
|
6262
|
-
constructor() {
|
|
6263
|
-
// Load status when inputs change
|
|
6264
|
-
effect(() => {
|
|
6265
|
-
const collection = this.collection();
|
|
6266
|
-
const docId = this.documentId();
|
|
6267
|
-
const initial = this.initialStatus();
|
|
6268
|
-
if (initial !== undefined) {
|
|
6269
|
-
this.updateStatusDisplay(initial);
|
|
6270
|
-
}
|
|
6271
|
-
else if (collection && docId) {
|
|
6272
|
-
this.loadStatus(collection, docId);
|
|
6273
|
-
}
|
|
6274
|
-
});
|
|
6275
|
-
}
|
|
6276
|
-
/**
|
|
6277
|
-
* Load the current status from the API.
|
|
6278
|
-
*/
|
|
6279
|
-
async loadStatus(collection, docId) {
|
|
6280
|
-
this.isLoading.set(true);
|
|
6281
|
-
try {
|
|
6282
|
-
const status = await this.versionService.getStatus(collection, docId);
|
|
6283
|
-
this.updateStatusDisplay(status);
|
|
6284
|
-
}
|
|
6285
|
-
catch {
|
|
6286
|
-
// Default to draft if we can't load status
|
|
6287
|
-
this.updateStatusDisplay('draft');
|
|
6288
|
-
}
|
|
6289
|
-
finally {
|
|
6290
|
-
this.isLoading.set(false);
|
|
6291
|
-
}
|
|
6292
|
-
}
|
|
6293
|
-
/**
|
|
6294
|
-
* Update the status display.
|
|
6295
|
-
* statusVariant and statusLabel are computed signals that automatically update.
|
|
6296
|
-
*/
|
|
6297
|
-
updateStatusDisplay(status) {
|
|
6298
|
-
this.status.set(status);
|
|
6299
|
-
}
|
|
6300
|
-
/**
|
|
6301
|
-
* Publish the document.
|
|
6302
|
-
*/
|
|
6303
|
-
async onPublish() {
|
|
6304
|
-
this.isUpdating.set(true);
|
|
6305
|
-
try {
|
|
6306
|
-
await this.versionService.publish(this.collection(), this.documentId());
|
|
6307
|
-
this.updateStatusDisplay('published');
|
|
6308
|
-
this.statusChanged.emit('published');
|
|
6309
|
-
}
|
|
6310
|
-
catch {
|
|
6311
|
-
// Error handled by crudToastInterceptor
|
|
6312
|
-
}
|
|
6313
|
-
finally {
|
|
6314
|
-
this.isUpdating.set(false);
|
|
6315
|
-
}
|
|
6316
|
-
}
|
|
6317
|
-
/**
|
|
6318
|
-
* Unpublish the document.
|
|
6319
|
-
*/
|
|
6320
|
-
async onUnpublish() {
|
|
6321
|
-
const confirmed = await this.feedback.confirmUnpublish(this.documentLabel());
|
|
6322
|
-
if (!confirmed) {
|
|
6323
|
-
return;
|
|
6324
|
-
}
|
|
6325
|
-
this.isUpdating.set(true);
|
|
6326
|
-
try {
|
|
6327
|
-
await this.versionService.unpublish(this.collection(), this.documentId());
|
|
6328
|
-
this.updateStatusDisplay('draft');
|
|
6329
|
-
this.statusChanged.emit('draft');
|
|
6330
|
-
}
|
|
6331
|
-
catch {
|
|
6332
|
-
// Error handled by crudToastInterceptor
|
|
6333
|
-
}
|
|
6334
|
-
finally {
|
|
6335
|
-
this.isUpdating.set(false);
|
|
6336
|
-
}
|
|
6337
|
-
}
|
|
6338
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PublishControlsWidget, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6339
|
-
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: `
|
|
6340
|
-
<mcms-badge [variant]="statusVariant()">
|
|
6341
|
-
{{ statusLabel() }}
|
|
6342
|
-
</mcms-badge>
|
|
6343
|
-
|
|
6344
|
-
@if (status() === 'draft') {
|
|
6345
|
-
<button
|
|
6346
|
-
mcms-button
|
|
6347
|
-
variant="primary"
|
|
6348
|
-
size="sm"
|
|
6349
|
-
[disabled]="isUpdating()"
|
|
6350
|
-
(click)="onPublish()"
|
|
6351
|
-
>
|
|
6352
|
-
@if (isUpdating()) {
|
|
6353
|
-
Publishing...
|
|
6354
|
-
} @else {
|
|
6355
|
-
Publish
|
|
6356
|
-
}
|
|
6357
|
-
</button>
|
|
6358
|
-
} @else {
|
|
6359
|
-
<button
|
|
6360
|
-
mcms-button
|
|
6361
|
-
variant="outline"
|
|
6362
|
-
size="sm"
|
|
6363
|
-
[disabled]="isUpdating()"
|
|
6364
|
-
(click)="onUnpublish()"
|
|
6365
|
-
>
|
|
6366
|
-
@if (isUpdating()) {
|
|
6367
|
-
Unpublishing...
|
|
6368
|
-
} @else {
|
|
6369
|
-
Unpublish
|
|
6370
|
-
}
|
|
6371
|
-
</button>
|
|
6372
|
-
}
|
|
6373
|
-
`, 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 });
|
|
6374
|
-
}
|
|
6375
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PublishControlsWidget, decorators: [{
|
|
6376
|
-
type: Component,
|
|
6377
|
-
args: [{
|
|
6378
|
-
selector: 'mcms-publish-controls',
|
|
6379
|
-
imports: [Badge, Button],
|
|
6380
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
6381
|
-
host: { class: 'inline-flex items-center gap-3' },
|
|
6382
|
-
template: `
|
|
6383
|
-
<mcms-badge [variant]="statusVariant()">
|
|
6384
|
-
{{ statusLabel() }}
|
|
6385
|
-
</mcms-badge>
|
|
6663
|
+
<mcms-image-variants-display [sizes]="formModelSizes()" />
|
|
6664
|
+
</div>
|
|
6665
|
+
}
|
|
6386
6666
|
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
|
|
6392
|
-
|
|
6393
|
-
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
|
|
6400
|
-
|
|
6401
|
-
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
6407
|
-
|
|
6408
|
-
|
|
6409
|
-
|
|
6410
|
-
|
|
6411
|
-
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6667
|
+
<div class="space-y-6">
|
|
6668
|
+
@for (field of visibleFields(); track field.name) {
|
|
6669
|
+
<mcms-field-renderer
|
|
6670
|
+
[field]="field"
|
|
6671
|
+
[formNode]="getFormNode(field.name)"
|
|
6672
|
+
[formTree]="entityForm()"
|
|
6673
|
+
[formModel]="formModel()"
|
|
6674
|
+
[mode]="mode()"
|
|
6675
|
+
[path]="field.name"
|
|
6676
|
+
/>
|
|
6677
|
+
}
|
|
6678
|
+
</div>
|
|
6679
|
+
}
|
|
6680
|
+
</mcms-card-content>
|
|
6681
|
+
|
|
6682
|
+
<mcms-card-footer class="flex justify-end gap-3 border-t bg-muted/50 px-6 py-4">
|
|
6683
|
+
@if (mode() !== 'view') {
|
|
6684
|
+
<button
|
|
6685
|
+
mcms-button
|
|
6686
|
+
variant="outline"
|
|
6687
|
+
[disabled]="isSubmitting() || isSavingDraft()"
|
|
6688
|
+
(click)="onCancel()"
|
|
6689
|
+
>
|
|
6690
|
+
Cancel
|
|
6691
|
+
</button>
|
|
6692
|
+
@if (canSaveDraft()) {
|
|
6693
|
+
<button
|
|
6694
|
+
mcms-button
|
|
6695
|
+
variant="outline"
|
|
6696
|
+
[disabled]="isSubmitting() || isSavingDraft()"
|
|
6697
|
+
(click)="onSaveDraft()"
|
|
6698
|
+
>
|
|
6699
|
+
@if (isSavingDraft()) {
|
|
6700
|
+
<mcms-spinner size="sm" class="mr-2" />
|
|
6701
|
+
}
|
|
6702
|
+
Save Draft
|
|
6703
|
+
</button>
|
|
6704
|
+
}
|
|
6705
|
+
<button
|
|
6706
|
+
mcms-button
|
|
6707
|
+
variant="primary"
|
|
6708
|
+
[disabled]="isSubmitting() || isSavingDraft()"
|
|
6709
|
+
(click)="onSubmit()"
|
|
6710
|
+
>
|
|
6711
|
+
@if (isSubmitting()) {
|
|
6712
|
+
<mcms-spinner size="sm" class="mr-2" />
|
|
6713
|
+
}
|
|
6714
|
+
{{ mode() === 'create' ? 'Create' : 'Save Changes' }}
|
|
6715
|
+
</button>
|
|
6716
|
+
} @else {
|
|
6717
|
+
@if (canEdit()) {
|
|
6718
|
+
<button mcms-button variant="primary" (click)="switchToEdit()">Edit</button>
|
|
6719
|
+
}
|
|
6720
|
+
}
|
|
6721
|
+
</mcms-card-footer>
|
|
6722
|
+
</mcms-card>
|
|
6723
|
+
|
|
6724
|
+
@if (hasVersioning() && mode() === 'edit' && entityId()) {
|
|
6725
|
+
<div class="mt-8">
|
|
6726
|
+
<mcms-version-history
|
|
6727
|
+
[collection]="collection().slug"
|
|
6728
|
+
[documentId]="entityId() ?? ''"
|
|
6729
|
+
[documentLabel]="collectionLabelSingular()"
|
|
6730
|
+
(restored)="onVersionRestored()"
|
|
6731
|
+
/>
|
|
6732
|
+
</div>
|
|
6733
|
+
}
|
|
6734
|
+
</div>
|
|
6416
6735
|
`,
|
|
6417
6736
|
}]
|
|
6418
|
-
}], ctorParameters: () => [], propDecorators: { collection: [{ type: i0.Input, args: [{ isSignal: true, alias: "collection", required: true }] }],
|
|
6737
|
+
}], 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"] }] } });
|
|
6419
6738
|
|
|
6420
6739
|
/**
|
|
6421
6740
|
* Entity View Widget
|
|
@@ -7521,6 +7840,78 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
7521
7840
|
}]
|
|
7522
7841
|
}] });
|
|
7523
7842
|
|
|
7843
|
+
/**
|
|
7844
|
+
* Renders all components registered for a named admin layout slot.
|
|
7845
|
+
*
|
|
7846
|
+
* Usage:
|
|
7847
|
+
* ```html
|
|
7848
|
+
* <mcms-admin-slot slot="dashboard:before" />
|
|
7849
|
+
* <mcms-admin-slot slot="collection-list:before" [collectionSlug]="slug" />
|
|
7850
|
+
* ```
|
|
7851
|
+
*/
|
|
7852
|
+
class AdminSlotOutlet {
|
|
7853
|
+
registry = inject(AdminSlotRegistry);
|
|
7854
|
+
/** The slot key to render (e.g., 'dashboard:before'). */
|
|
7855
|
+
slot = input.required(...(ngDevMode ? [{ debugName: "slot" }] : []));
|
|
7856
|
+
/** Optional collection slug for per-collection slot resolution. */
|
|
7857
|
+
collectionSlug = input(...(ngDevMode ? [undefined, { debugName: "collectionSlug" }] : []));
|
|
7858
|
+
/** Optional context passed as inputs to slot components. */
|
|
7859
|
+
context = input({}, ...(ngDevMode ? [{ debugName: "context" }] : []));
|
|
7860
|
+
/** Resolved component types after lazy loading. */
|
|
7861
|
+
resolvedComponents = signal([], ...(ngDevMode ? [{ debugName: "resolvedComponents" }] : []));
|
|
7862
|
+
/** Inputs to pass to each slot component. */
|
|
7863
|
+
slotInputs = computed(() => ({
|
|
7864
|
+
...this.context(),
|
|
7865
|
+
}), ...(ngDevMode ? [{ debugName: "slotInputs" }] : []));
|
|
7866
|
+
/** Incremented on every effect run to detect stale promise resolutions. */
|
|
7867
|
+
loadGeneration = 0;
|
|
7868
|
+
constructor() {
|
|
7869
|
+
effect(() => {
|
|
7870
|
+
const slot = this.slot();
|
|
7871
|
+
const slug = this.collectionSlug();
|
|
7872
|
+
const loaders = this.registry.resolve(slot, slug);
|
|
7873
|
+
if (loaders.length === 0) {
|
|
7874
|
+
this.resolvedComponents.set([]);
|
|
7875
|
+
return;
|
|
7876
|
+
}
|
|
7877
|
+
const generation = ++this.loadGeneration;
|
|
7878
|
+
Promise.all(loaders.map((loader) => loader()))
|
|
7879
|
+
.then((components) => {
|
|
7880
|
+
if (generation !== this.loadGeneration)
|
|
7881
|
+
return;
|
|
7882
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- loaders resolve to unknown from registry, safe cast to Type[]
|
|
7883
|
+
this.resolvedComponents.set(components);
|
|
7884
|
+
})
|
|
7885
|
+
.catch((err) => {
|
|
7886
|
+
if (generation !== this.loadGeneration)
|
|
7887
|
+
return;
|
|
7888
|
+
console.error('[AdminSlotOutlet] Failed to load slot components:', err);
|
|
7889
|
+
this.resolvedComponents.set([]);
|
|
7890
|
+
});
|
|
7891
|
+
});
|
|
7892
|
+
}
|
|
7893
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AdminSlotOutlet, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
7894
|
+
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: `
|
|
7895
|
+
@for (component of resolvedComponents(); track $index) {
|
|
7896
|
+
<ng-container *ngComponentOutlet="component; inputs: slotInputs()" />
|
|
7897
|
+
}
|
|
7898
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
7899
|
+
}
|
|
7900
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AdminSlotOutlet, decorators: [{
|
|
7901
|
+
type: Component,
|
|
7902
|
+
args: [{
|
|
7903
|
+
selector: 'mcms-admin-slot',
|
|
7904
|
+
imports: [NgComponentOutlet],
|
|
7905
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
7906
|
+
host: { class: 'block' },
|
|
7907
|
+
template: `
|
|
7908
|
+
@for (component of resolvedComponents(); track $index) {
|
|
7909
|
+
<ng-container *ngComponentOutlet="component; inputs: slotInputs()" />
|
|
7910
|
+
}
|
|
7911
|
+
`,
|
|
7912
|
+
}]
|
|
7913
|
+
}], 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 }] }] } });
|
|
7914
|
+
|
|
7524
7915
|
const DEFAULT_GROUP = 'Collections';
|
|
7525
7916
|
/**
|
|
7526
7917
|
* Slugify a group name into a valid HTML id attribute value.
|
|
@@ -7763,6 +8154,8 @@ class AdminSidebarWidget {
|
|
|
7763
8154
|
[exact]="true"
|
|
7764
8155
|
/>
|
|
7765
8156
|
|
|
8157
|
+
<mcms-admin-slot slot="shell:nav-start" />
|
|
8158
|
+
|
|
7766
8159
|
<!-- Collection Sections (grouped by admin.group) -->
|
|
7767
8160
|
@for (group of collectionGroups(); track group.id) {
|
|
7768
8161
|
<mcms-sidebar-section [title]="group.name">
|
|
@@ -7807,6 +8200,8 @@ class AdminSidebarWidget {
|
|
|
7807
8200
|
}
|
|
7808
8201
|
</mcms-sidebar-section>
|
|
7809
8202
|
}
|
|
8203
|
+
|
|
8204
|
+
<mcms-admin-slot slot="shell:nav-end" />
|
|
7810
8205
|
</mcms-sidebar-nav>
|
|
7811
8206
|
</div>
|
|
7812
8207
|
|
|
@@ -7854,7 +8249,7 @@ class AdminSidebarWidget {
|
|
|
7854
8249
|
}
|
|
7855
8250
|
</div>
|
|
7856
8251
|
</mcms-sidebar>
|
|
7857
|
-
`, 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 });
|
|
8252
|
+
`, 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 });
|
|
7858
8253
|
}
|
|
7859
8254
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AdminSidebarWidget, decorators: [{
|
|
7860
8255
|
type: Component,
|
|
@@ -7873,6 +8268,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
7873
8268
|
DropdownLabel,
|
|
7874
8269
|
DropdownTrigger,
|
|
7875
8270
|
NgIcon,
|
|
8271
|
+
AdminSlotOutlet,
|
|
7876
8272
|
],
|
|
7877
8273
|
providers: [
|
|
7878
8274
|
provideIcons({
|
|
@@ -7938,6 +8334,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
7938
8334
|
[exact]="true"
|
|
7939
8335
|
/>
|
|
7940
8336
|
|
|
8337
|
+
<mcms-admin-slot slot="shell:nav-start" />
|
|
8338
|
+
|
|
7941
8339
|
<!-- Collection Sections (grouped by admin.group) -->
|
|
7942
8340
|
@for (group of collectionGroups(); track group.id) {
|
|
7943
8341
|
<mcms-sidebar-section [title]="group.name">
|
|
@@ -7982,6 +8380,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
7982
8380
|
}
|
|
7983
8381
|
</mcms-sidebar-section>
|
|
7984
8382
|
}
|
|
8383
|
+
|
|
8384
|
+
<mcms-admin-slot slot="shell:nav-end" />
|
|
7985
8385
|
</mcms-sidebar-nav>
|
|
7986
8386
|
</div>
|
|
7987
8387
|
|
|
@@ -8052,6 +8452,8 @@ class AdminShellComponent {
|
|
|
8052
8452
|
auth = inject(MomentumAuthService);
|
|
8053
8453
|
collectionAccess = inject(CollectionAccessService);
|
|
8054
8454
|
sidebar = inject(SidebarService);
|
|
8455
|
+
componentRegistry = inject(AdminComponentRegistry);
|
|
8456
|
+
slotRegistry = inject(AdminSlotRegistry);
|
|
8055
8457
|
entitySheet = inject(EntitySheetService);
|
|
8056
8458
|
/** All collections from route data */
|
|
8057
8459
|
allCollections = computed(() => {
|
|
@@ -8101,6 +8503,23 @@ class AdminShellComponent {
|
|
|
8101
8503
|
};
|
|
8102
8504
|
}, ...(ngDevMode ? [{ debugName: "sidebarUser" }] : []));
|
|
8103
8505
|
ngOnInit() {
|
|
8506
|
+
// Register config-level component overrides and slot registrations.
|
|
8507
|
+
// This reads from route data (which has the full MomentumConfig-derived data)
|
|
8508
|
+
// and bridges config declarations into the runtime registries.
|
|
8509
|
+
const routeData = this.route.snapshot.data;
|
|
8510
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- route data is Record<string, unknown>
|
|
8511
|
+
const adminComponents = routeData['adminComponents'];
|
|
8512
|
+
registerConfigComponents(this.allCollections(), adminComponents, this.componentRegistry, this.slotRegistry);
|
|
8513
|
+
// Also register plugin-declared admin components
|
|
8514
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- route data is Record<string, unknown>
|
|
8515
|
+
const plugins = routeData['plugins'];
|
|
8516
|
+
if (plugins) {
|
|
8517
|
+
for (const plugin of plugins) {
|
|
8518
|
+
if (plugin.adminComponents) {
|
|
8519
|
+
registerConfigComponents([], plugin.adminComponents, this.componentRegistry, this.slotRegistry);
|
|
8520
|
+
}
|
|
8521
|
+
}
|
|
8522
|
+
}
|
|
8104
8523
|
// Keyboard shortcuts, auth, and sheet restoration only run in the browser.
|
|
8105
8524
|
// SSR user is provided via MOMENTUM_API_CONTEXT (used by injectUser above).
|
|
8106
8525
|
if (!isPlatformBrowser(this.platformId)) {
|
|
@@ -8176,9 +8595,11 @@ class AdminShellComponent {
|
|
|
8176
8595
|
|
|
8177
8596
|
<!-- Main Content (with top padding on mobile for header, normal padding at md+) -->
|
|
8178
8597
|
<main id="mcms-main-content" class="flex-1 p-8 overflow-y-auto overflow-x-hidden pt-20 md:pt-8">
|
|
8598
|
+
<mcms-admin-slot slot="shell:header" />
|
|
8179
8599
|
@defer (hydrate on immediate) {
|
|
8180
8600
|
<router-outlet />
|
|
8181
8601
|
}
|
|
8602
|
+
<mcms-admin-slot slot="shell:footer" />
|
|
8182
8603
|
</main>
|
|
8183
8604
|
|
|
8184
8605
|
<mcms-toast-container />
|
|
@@ -8213,7 +8634,7 @@ class AdminShellComponent {
|
|
|
8213
8634
|
</div>
|
|
8214
8635
|
</div>
|
|
8215
8636
|
}
|
|
8216
|
-
`, 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]] });
|
|
8637
|
+
`, 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]] });
|
|
8217
8638
|
}
|
|
8218
8639
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AdminShellComponent, decorators: [{
|
|
8219
8640
|
type: Component,
|
|
@@ -8224,6 +8645,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
8224
8645
|
A11yModule,
|
|
8225
8646
|
EntitySheetContentComponent,
|
|
8226
8647
|
ToastContainer,
|
|
8648
|
+
AdminSlotOutlet,
|
|
8227
8649
|
], changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
8228
8650
|
class: 'flex h-screen overflow-hidden bg-background',
|
|
8229
8651
|
'(document:keydown.escape)': 'onEscapeKey()',
|
|
@@ -8256,9 +8678,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
8256
8678
|
|
|
8257
8679
|
<!-- Main Content (with top padding on mobile for header, normal padding at md+) -->
|
|
8258
8680
|
<main id="mcms-main-content" class="flex-1 p-8 overflow-y-auto overflow-x-hidden pt-20 md:pt-8">
|
|
8681
|
+
<mcms-admin-slot slot="shell:header" />
|
|
8259
8682
|
@defer (hydrate on immediate) {
|
|
8260
8683
|
<router-outlet />
|
|
8261
8684
|
}
|
|
8685
|
+
<mcms-admin-slot slot="shell:footer" />
|
|
8262
8686
|
</main>
|
|
8263
8687
|
|
|
8264
8688
|
<mcms-toast-container />
|
|
@@ -9081,6 +9505,8 @@ class DashboardPage {
|
|
|
9081
9505
|
collectionGroups = computed(() => groupCollections(this.collections()), ...(ngDevMode ? [{ debugName: "collectionGroups" }] : []));
|
|
9082
9506
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DashboardPage, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
9083
9507
|
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: `
|
|
9508
|
+
<mcms-admin-slot slot="dashboard:before" />
|
|
9509
|
+
|
|
9084
9510
|
<header class="mb-10">
|
|
9085
9511
|
<h1 class="text-4xl font-bold tracking-tight text-foreground">Dashboard</h1>
|
|
9086
9512
|
<p class="text-muted-foreground mt-3 text-lg">Manage your content and collections</p>
|
|
@@ -9130,16 +9556,20 @@ class DashboardPage {
|
|
|
9130
9556
|
</section>
|
|
9131
9557
|
}
|
|
9132
9558
|
}
|
|
9133
|
-
|
|
9559
|
+
|
|
9560
|
+
<mcms-admin-slot slot="dashboard:after" />
|
|
9561
|
+
`, 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 });
|
|
9134
9562
|
}
|
|
9135
9563
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DashboardPage, decorators: [{
|
|
9136
9564
|
type: Component,
|
|
9137
9565
|
args: [{
|
|
9138
9566
|
selector: 'mcms-dashboard',
|
|
9139
|
-
imports: [CollectionCardWidget],
|
|
9567
|
+
imports: [CollectionCardWidget, AdminSlotOutlet],
|
|
9140
9568
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
9141
9569
|
host: { class: 'block max-w-6xl' },
|
|
9142
9570
|
template: `
|
|
9571
|
+
<mcms-admin-slot slot="dashboard:before" />
|
|
9572
|
+
|
|
9143
9573
|
<header class="mb-10">
|
|
9144
9574
|
<h1 class="text-4xl font-bold tracking-tight text-foreground">Dashboard</h1>
|
|
9145
9575
|
<p class="text-muted-foreground mt-3 text-lg">Manage your content and collections</p>
|
|
@@ -9189,6 +9619,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
9189
9619
|
</section>
|
|
9190
9620
|
}
|
|
9191
9621
|
}
|
|
9622
|
+
|
|
9623
|
+
<mcms-admin-slot slot="dashboard:after" />
|
|
9192
9624
|
`,
|
|
9193
9625
|
}]
|
|
9194
9626
|
}] });
|
|
@@ -9335,6 +9767,8 @@ class EntityListWidget {
|
|
|
9335
9767
|
route = inject(ActivatedRoute);
|
|
9336
9768
|
/** Template ref for complex cell rendering (group, array, json). */
|
|
9337
9769
|
complexCellTemplate = viewChild('complexCell', ...(ngDevMode ? [{ debugName: "complexCellTemplate" }] : []));
|
|
9770
|
+
/** Template ref for badge cell rendering (_status column). */
|
|
9771
|
+
badgeCellTemplate = viewChild('badgeCell', ...(ngDevMode ? [{ debugName: "badgeCellTemplate" }] : []));
|
|
9338
9772
|
/** The collection configuration */
|
|
9339
9773
|
collection = input.required(...(ngDevMode ? [{ debugName: "collection" }] : []));
|
|
9340
9774
|
/** Base path for entity routes */
|
|
@@ -9386,8 +9820,17 @@ class EntityListWidget {
|
|
|
9386
9820
|
selectedEntities = signal([], ...(ngDevMode ? [{ debugName: "selectedEntities" }] : []));
|
|
9387
9821
|
searchQuery = model('', ...(ngDevMode ? [{ debugName: "searchQuery" }] : []));
|
|
9388
9822
|
viewingTrash = signal(false, ...(ngDevMode ? [{ debugName: "viewingTrash" }] : []));
|
|
9823
|
+
statusFilter = signal(null, ...(ngDevMode ? [{ debugName: "statusFilter" }] : []));
|
|
9824
|
+
/** Status filter options for versioned collections */
|
|
9825
|
+
statusFilterOptions = [
|
|
9826
|
+
{ label: 'All', value: null },
|
|
9827
|
+
{ label: 'Draft', value: 'draft' },
|
|
9828
|
+
{ label: 'Published', value: 'published' },
|
|
9829
|
+
];
|
|
9389
9830
|
/** Whether the collection has soft delete enabled */
|
|
9390
9831
|
hasSoftDelete = computed(() => getSoftDeleteField(this.collection()) !== null, ...(ngDevMode ? [{ debugName: "hasSoftDelete" }] : []));
|
|
9832
|
+
/** Whether the collection has versioning with drafts enabled */
|
|
9833
|
+
hasVersioning = computed(() => hasVersionDrafts(this.collection()), ...(ngDevMode ? [{ debugName: "hasVersioning" }] : []));
|
|
9391
9834
|
/** Computed collection label */
|
|
9392
9835
|
collectionLabel = computed(() => {
|
|
9393
9836
|
const col = this.collection();
|
|
@@ -9405,8 +9848,9 @@ class EntityListWidget {
|
|
|
9405
9848
|
}, ...(ngDevMode ? [{ debugName: "dashboardPath" }] : []));
|
|
9406
9849
|
/** Auto-derive columns from collection fields if not provided */
|
|
9407
9850
|
tableColumns = computed(() => {
|
|
9408
|
-
// Read template
|
|
9851
|
+
// Read template signals at top level so the computed re-runs when viewChild resolves
|
|
9409
9852
|
const complexTemplate = this.complexCellTemplate();
|
|
9853
|
+
const badgeTemplate = this.badgeCellTemplate();
|
|
9410
9854
|
const customColumns = this.columns();
|
|
9411
9855
|
if (customColumns.length > 0) {
|
|
9412
9856
|
return customColumns;
|
|
@@ -9424,6 +9868,22 @@ class EntityListWidget {
|
|
|
9424
9868
|
if (columns.length >= 5)
|
|
9425
9869
|
break;
|
|
9426
9870
|
}
|
|
9871
|
+
// Add _status badge column for versioned collections with drafts
|
|
9872
|
+
if (hasVersionDrafts(col)) {
|
|
9873
|
+
columns.push({
|
|
9874
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
9875
|
+
field: '_status',
|
|
9876
|
+
header: 'Status',
|
|
9877
|
+
sortable: true,
|
|
9878
|
+
type: 'badge',
|
|
9879
|
+
width: '110px',
|
|
9880
|
+
badgeMap: {
|
|
9881
|
+
draft: { label: 'Draft', variant: 'secondary' },
|
|
9882
|
+
published: { label: 'Published', variant: 'default' },
|
|
9883
|
+
},
|
|
9884
|
+
...(badgeTemplate ? { template: badgeTemplate } : {}),
|
|
9885
|
+
});
|
|
9886
|
+
}
|
|
9427
9887
|
// Add deletedAt column when viewing trash
|
|
9428
9888
|
if (this.viewingTrash() && this.hasSoftDelete()) {
|
|
9429
9889
|
const softDeleteField = getSoftDeleteField(col) ?? 'deletedAt';
|
|
@@ -9477,6 +9937,11 @@ class EntityListWidget {
|
|
|
9477
9937
|
if (typeof initialSearch === 'string') {
|
|
9478
9938
|
this.searchQuery.set(initialSearch);
|
|
9479
9939
|
}
|
|
9940
|
+
// Initialize status filter from URL
|
|
9941
|
+
const initialStatus = queryParams['status'];
|
|
9942
|
+
if (initialStatus === 'draft' || initialStatus === 'published') {
|
|
9943
|
+
this.statusFilter.set(initialStatus);
|
|
9944
|
+
}
|
|
9480
9945
|
// Initialize sort from URL (format: "field" for asc, "-field" for desc)
|
|
9481
9946
|
const initialSort = queryParams['sort'];
|
|
9482
9947
|
if (typeof initialSort === 'string' && initialSort) {
|
|
@@ -9488,11 +9953,12 @@ class EntityListWidget {
|
|
|
9488
9953
|
direction: isDesc ? 'desc' : 'asc',
|
|
9489
9954
|
});
|
|
9490
9955
|
}
|
|
9491
|
-
// Load data when collection, pagination,
|
|
9956
|
+
// Load data when collection, pagination, trash view, or status filter changes
|
|
9492
9957
|
effect(() => {
|
|
9493
9958
|
const col = this.collection();
|
|
9494
9959
|
const page = this.currentPage();
|
|
9495
9960
|
const trash = this.viewingTrash();
|
|
9961
|
+
const status = this.statusFilter();
|
|
9496
9962
|
let sortState = this.sort();
|
|
9497
9963
|
let search = this.searchQuery();
|
|
9498
9964
|
if (col) {
|
|
@@ -9502,24 +9968,25 @@ class EntityListWidget {
|
|
|
9502
9968
|
this.sort.set(undefined);
|
|
9503
9969
|
this.currentPage.set(1);
|
|
9504
9970
|
this.viewingTrash.set(false);
|
|
9971
|
+
this.statusFilter.set(null);
|
|
9505
9972
|
search = '';
|
|
9506
9973
|
sortState = undefined;
|
|
9507
9974
|
// Clear URL params
|
|
9508
9975
|
this.router.navigate([], {
|
|
9509
|
-
queryParams: { search: null, sort: null },
|
|
9976
|
+
queryParams: { search: null, sort: null, status: null },
|
|
9510
9977
|
queryParamsHandling: 'merge',
|
|
9511
9978
|
replaceUrl: true,
|
|
9512
9979
|
});
|
|
9513
9980
|
}
|
|
9514
9981
|
this.previousCollectionSlug = col.slug;
|
|
9515
|
-
this.loadData(col.slug, page, sortState, search, trash);
|
|
9982
|
+
this.loadData(col.slug, page, sortState, search, trash, status);
|
|
9516
9983
|
}
|
|
9517
9984
|
});
|
|
9518
9985
|
}
|
|
9519
9986
|
/**
|
|
9520
9987
|
* Load data from API.
|
|
9521
9988
|
*/
|
|
9522
|
-
async loadData(slug, page, sortState, search, onlyDeleted) {
|
|
9989
|
+
async loadData(slug, page, sortState, search, onlyDeleted, statusFilter) {
|
|
9523
9990
|
this.loading.set(true);
|
|
9524
9991
|
this.error.set(null);
|
|
9525
9992
|
try {
|
|
@@ -9535,17 +10002,25 @@ class EntityListWidget {
|
|
|
9535
10002
|
options['sort'] =
|
|
9536
10003
|
sortState.direction === 'desc' ? `-${String(sortState.field)}` : String(sortState.field);
|
|
9537
10004
|
}
|
|
10005
|
+
// Build where clause combining search and status filter
|
|
10006
|
+
const whereConditions = [];
|
|
9538
10007
|
if (search) {
|
|
9539
|
-
// Build where clause for search
|
|
9540
10008
|
const searchFields = this.searchFields().length > 0 ? this.searchFields() : this.getDefaultSearchFields();
|
|
9541
10009
|
if (searchFields.length > 0) {
|
|
9542
|
-
|
|
9543
|
-
|
|
9544
|
-
|
|
9545
|
-
})),
|
|
9546
|
-
};
|
|
10010
|
+
whereConditions.push(...searchFields.map((field) => ({
|
|
10011
|
+
[field]: { contains: search },
|
|
10012
|
+
})));
|
|
9547
10013
|
}
|
|
9548
10014
|
}
|
|
10015
|
+
if (statusFilter) {
|
|
10016
|
+
options['where'] = {
|
|
10017
|
+
...(whereConditions.length > 0 ? { or: whereConditions } : {}),
|
|
10018
|
+
_status: { equals: statusFilter },
|
|
10019
|
+
};
|
|
10020
|
+
}
|
|
10021
|
+
else if (whereConditions.length > 0) {
|
|
10022
|
+
options['where'] = { or: whereConditions };
|
|
10023
|
+
}
|
|
9549
10024
|
const result = await this.api.collection(slug).find(options);
|
|
9550
10025
|
this.entities.set(result.docs);
|
|
9551
10026
|
this.totalItems.set(result.totalDocs);
|
|
@@ -9723,6 +10198,15 @@ class EntityListWidget {
|
|
|
9723
10198
|
}
|
|
9724
10199
|
}
|
|
9725
10200
|
}
|
|
10201
|
+
/**
|
|
10202
|
+
* Look up a badge configuration from a column's badgeMap.
|
|
10203
|
+
*/
|
|
10204
|
+
getBadgeConfig(value, column) {
|
|
10205
|
+
if (!column.badgeMap || value === null || value === undefined)
|
|
10206
|
+
return null;
|
|
10207
|
+
const key = String(value);
|
|
10208
|
+
return column.badgeMap[key] ?? null;
|
|
10209
|
+
}
|
|
9726
10210
|
/**
|
|
9727
10211
|
* Generate a brief summary string for complex field values (group, array, json).
|
|
9728
10212
|
*/
|
|
@@ -9855,7 +10339,7 @@ class EntityListWidget {
|
|
|
9855
10339
|
reload() {
|
|
9856
10340
|
const col = this.collection();
|
|
9857
10341
|
if (col) {
|
|
9858
|
-
this.loadData(col.slug, this.currentPage(), this.sort(), this.searchQuery(), this.viewingTrash());
|
|
10342
|
+
this.loadData(col.slug, this.currentPage(), this.sort(), this.searchQuery(), this.viewingTrash(), this.statusFilter());
|
|
9859
10343
|
}
|
|
9860
10344
|
}
|
|
9861
10345
|
/**
|
|
@@ -9866,6 +10350,18 @@ class EntityListWidget {
|
|
|
9866
10350
|
this.currentPage.set(1);
|
|
9867
10351
|
this.selectedEntities.set([]);
|
|
9868
10352
|
}
|
|
10353
|
+
/**
|
|
10354
|
+
* Set the status filter for versioned collections.
|
|
10355
|
+
*/
|
|
10356
|
+
setStatusFilter(status) {
|
|
10357
|
+
this.statusFilter.set(status);
|
|
10358
|
+
this.currentPage.set(1);
|
|
10359
|
+
this.router.navigate([], {
|
|
10360
|
+
queryParams: { status: status ?? null },
|
|
10361
|
+
queryParamsHandling: 'merge',
|
|
10362
|
+
replaceUrl: true,
|
|
10363
|
+
});
|
|
10364
|
+
}
|
|
9869
10365
|
/**
|
|
9870
10366
|
* Handle create button click.
|
|
9871
10367
|
*/
|
|
@@ -9874,7 +10370,7 @@ class EntityListWidget {
|
|
|
9874
10370
|
this.router.navigate([path]);
|
|
9875
10371
|
}
|
|
9876
10372
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: EntityListWidget, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
9877
|
-
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: `
|
|
10373
|
+
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: `
|
|
9878
10374
|
@if (showHeader()) {
|
|
9879
10375
|
@if (showBreadcrumbs()) {
|
|
9880
10376
|
<mcms-breadcrumbs class="mb-6">
|
|
@@ -9897,6 +10393,22 @@ class EntityListWidget {
|
|
|
9897
10393
|
}
|
|
9898
10394
|
</div>
|
|
9899
10395
|
<div class="flex items-center gap-2">
|
|
10396
|
+
@if (hasVersioning()) {
|
|
10397
|
+
<div class="flex items-center gap-1" role="group" aria-label="Filter by status">
|
|
10398
|
+
@for (option of statusFilterOptions; track option.value) {
|
|
10399
|
+
<button
|
|
10400
|
+
mcms-button
|
|
10401
|
+
[variant]="statusFilter() === option.value ? 'outline' : 'ghost'"
|
|
10402
|
+
size="sm"
|
|
10403
|
+
[attr.data-testid]="'status-filter-' + (option.value ?? 'all')"
|
|
10404
|
+
[attr.aria-pressed]="statusFilter() === option.value"
|
|
10405
|
+
(click)="setStatusFilter(option.value)"
|
|
10406
|
+
>
|
|
10407
|
+
{{ option.label }}
|
|
10408
|
+
</button>
|
|
10409
|
+
}
|
|
10410
|
+
</div>
|
|
10411
|
+
}
|
|
9900
10412
|
@if (hasSoftDelete()) {
|
|
9901
10413
|
<button
|
|
9902
10414
|
mcms-button
|
|
@@ -9970,6 +10482,15 @@ class EntityListWidget {
|
|
|
9970
10482
|
}
|
|
9971
10483
|
</mcms-data-table>
|
|
9972
10484
|
|
|
10485
|
+
<!-- Template for badge cells (_status column) -->
|
|
10486
|
+
<ng-template #badgeCell let-value let-column="column">
|
|
10487
|
+
@if (getBadgeConfig(value, column); as badge) {
|
|
10488
|
+
<mcms-badge [variant]="badge.variant">{{ badge.label }}</mcms-badge>
|
|
10489
|
+
} @else {
|
|
10490
|
+
{{ value ?? '-' }}
|
|
10491
|
+
}
|
|
10492
|
+
</ng-template>
|
|
10493
|
+
|
|
9973
10494
|
<!-- Template for complex field cells (group, array, json) -->
|
|
9974
10495
|
<ng-template #complexCell let-value let-column="column">
|
|
9975
10496
|
<div class="flex items-center gap-1.5">
|
|
@@ -10021,6 +10542,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
10021
10542
|
}
|
|
10022
10543
|
</div>
|
|
10023
10544
|
<div class="flex items-center gap-2">
|
|
10545
|
+
@if (hasVersioning()) {
|
|
10546
|
+
<div class="flex items-center gap-1" role="group" aria-label="Filter by status">
|
|
10547
|
+
@for (option of statusFilterOptions; track option.value) {
|
|
10548
|
+
<button
|
|
10549
|
+
mcms-button
|
|
10550
|
+
[variant]="statusFilter() === option.value ? 'outline' : 'ghost'"
|
|
10551
|
+
size="sm"
|
|
10552
|
+
[attr.data-testid]="'status-filter-' + (option.value ?? 'all')"
|
|
10553
|
+
[attr.aria-pressed]="statusFilter() === option.value"
|
|
10554
|
+
(click)="setStatusFilter(option.value)"
|
|
10555
|
+
>
|
|
10556
|
+
{{ option.label }}
|
|
10557
|
+
</button>
|
|
10558
|
+
}
|
|
10559
|
+
</div>
|
|
10560
|
+
}
|
|
10024
10561
|
@if (hasSoftDelete()) {
|
|
10025
10562
|
<button
|
|
10026
10563
|
mcms-button
|
|
@@ -10094,6 +10631,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
10094
10631
|
}
|
|
10095
10632
|
</mcms-data-table>
|
|
10096
10633
|
|
|
10634
|
+
<!-- Template for badge cells (_status column) -->
|
|
10635
|
+
<ng-template #badgeCell let-value let-column="column">
|
|
10636
|
+
@if (getBadgeConfig(value, column); as badge) {
|
|
10637
|
+
<mcms-badge [variant]="badge.variant">{{ badge.label }}</mcms-badge>
|
|
10638
|
+
} @else {
|
|
10639
|
+
{{ value ?? '-' }}
|
|
10640
|
+
}
|
|
10641
|
+
</ng-template>
|
|
10642
|
+
|
|
10097
10643
|
<!-- Template for complex field cells (group, array, json) -->
|
|
10098
10644
|
<ng-template #complexCell let-value let-column="column">
|
|
10099
10645
|
<div class="flex items-center gap-1.5">
|
|
@@ -10114,7 +10660,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
10114
10660
|
</ng-template>
|
|
10115
10661
|
`,
|
|
10116
10662
|
}]
|
|
10117
|
-
}], 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"] }] } });
|
|
10663
|
+
}], 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"] }] } });
|
|
10118
10664
|
|
|
10119
10665
|
/** Role hierarchy: lower index = higher privilege.
|
|
10120
10666
|
* Keep in sync with AUTH_ROLES in @momentumcms/auth/collections */
|
|
@@ -10501,6 +11047,7 @@ class CollectionListPage {
|
|
|
10501
11047
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: CollectionListPage, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
10502
11048
|
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: `
|
|
10503
11049
|
@if (collection(); as col) {
|
|
11050
|
+
<mcms-admin-slot slot="collection-list:before" [collectionSlug]="col.slug" />
|
|
10504
11051
|
<mcms-entity-list
|
|
10505
11052
|
#entityList
|
|
10506
11053
|
[collection]="col"
|
|
@@ -10512,20 +11059,22 @@ class CollectionListPage {
|
|
|
10512
11059
|
(bulkAction)="onBulkAction($event)"
|
|
10513
11060
|
(headerActionClick)="onHeaderAction($event)"
|
|
10514
11061
|
/>
|
|
11062
|
+
<mcms-admin-slot slot="collection-list:after" [collectionSlug]="col.slug" />
|
|
10515
11063
|
} @else {
|
|
10516
11064
|
<div class="p-12 text-center text-muted-foreground">Collection not found</div>
|
|
10517
11065
|
}
|
|
10518
|
-
`, 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 });
|
|
11066
|
+
`, 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 });
|
|
10519
11067
|
}
|
|
10520
11068
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: CollectionListPage, decorators: [{
|
|
10521
11069
|
type: Component,
|
|
10522
11070
|
args: [{
|
|
10523
11071
|
selector: 'mcms-collection-list',
|
|
10524
|
-
imports: [EntityListWidget],
|
|
11072
|
+
imports: [EntityListWidget, AdminSlotOutlet],
|
|
10525
11073
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
10526
11074
|
host: { class: 'block' },
|
|
10527
11075
|
template: `
|
|
10528
11076
|
@if (collection(); as col) {
|
|
11077
|
+
<mcms-admin-slot slot="collection-list:before" [collectionSlug]="col.slug" />
|
|
10529
11078
|
<mcms-entity-list
|
|
10530
11079
|
#entityList
|
|
10531
11080
|
[collection]="col"
|
|
@@ -10537,6 +11086,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
10537
11086
|
(bulkAction)="onBulkAction($event)"
|
|
10538
11087
|
(headerActionClick)="onHeaderAction($event)"
|
|
10539
11088
|
/>
|
|
11089
|
+
<mcms-admin-slot slot="collection-list:after" [collectionSlug]="col.slug" />
|
|
10540
11090
|
} @else {
|
|
10541
11091
|
<div class="p-12 text-center text-muted-foreground">Collection not found</div>
|
|
10542
11092
|
}
|
|
@@ -10586,6 +11136,7 @@ class CollectionViewPage {
|
|
|
10586
11136
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: CollectionViewPage, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
10587
11137
|
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: `
|
|
10588
11138
|
@if (collection(); as col) {
|
|
11139
|
+
<mcms-admin-slot slot="collection-view:before" [collectionSlug]="col.slug" />
|
|
10589
11140
|
@if (entityId(); as id) {
|
|
10590
11141
|
<mcms-entity-view
|
|
10591
11142
|
[collection]="col"
|
|
@@ -10597,20 +11148,22 @@ class CollectionViewPage {
|
|
|
10597
11148
|
} @else {
|
|
10598
11149
|
<div class="p-12 text-center text-muted-foreground">Entity ID not provided</div>
|
|
10599
11150
|
}
|
|
11151
|
+
<mcms-admin-slot slot="collection-view:after" [collectionSlug]="col.slug" />
|
|
10600
11152
|
} @else {
|
|
10601
11153
|
<div class="p-12 text-center text-muted-foreground">Collection not found</div>
|
|
10602
11154
|
}
|
|
10603
|
-
`, 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 });
|
|
11155
|
+
`, 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 });
|
|
10604
11156
|
}
|
|
10605
11157
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: CollectionViewPage, decorators: [{
|
|
10606
11158
|
type: Component,
|
|
10607
11159
|
args: [{
|
|
10608
11160
|
selector: 'mcms-collection-view',
|
|
10609
|
-
imports: [EntityViewWidget],
|
|
11161
|
+
imports: [EntityViewWidget, AdminSlotOutlet],
|
|
10610
11162
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
10611
11163
|
host: { class: 'block' },
|
|
10612
11164
|
template: `
|
|
10613
11165
|
@if (collection(); as col) {
|
|
11166
|
+
<mcms-admin-slot slot="collection-view:before" [collectionSlug]="col.slug" />
|
|
10614
11167
|
@if (entityId(); as id) {
|
|
10615
11168
|
<mcms-entity-view
|
|
10616
11169
|
[collection]="col"
|
|
@@ -10622,6 +11175,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
10622
11175
|
} @else {
|
|
10623
11176
|
<div class="p-12 text-center text-muted-foreground">Entity ID not provided</div>
|
|
10624
11177
|
}
|
|
11178
|
+
<mcms-admin-slot slot="collection-view:after" [collectionSlug]="col.slug" />
|
|
10625
11179
|
} @else {
|
|
10626
11180
|
<div class="p-12 text-center text-muted-foreground">Collection not found</div>
|
|
10627
11181
|
}
|
|
@@ -11236,6 +11790,7 @@ class CollectionEditPage {
|
|
|
11236
11790
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: CollectionEditPage, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
11237
11791
|
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: `
|
|
11238
11792
|
@if (collection(); as col) {
|
|
11793
|
+
<mcms-admin-slot slot="collection-edit:before" [collectionSlug]="col.slug" />
|
|
11239
11794
|
@if (previewConfig(); as preview) {
|
|
11240
11795
|
@if (showPreview()) {
|
|
11241
11796
|
<!-- Split layout: form + preview -->
|
|
@@ -11270,6 +11825,7 @@ class CollectionEditPage {
|
|
|
11270
11825
|
(editBlockRequest)="onEditBlockRequest($event)"
|
|
11271
11826
|
/>
|
|
11272
11827
|
</div>
|
|
11828
|
+
<mcms-admin-slot slot="collection-edit:sidebar" [collectionSlug]="col.slug" />
|
|
11273
11829
|
</div>
|
|
11274
11830
|
} @else {
|
|
11275
11831
|
<!-- Full-width form (preview hidden) -->
|
|
@@ -11294,29 +11850,36 @@ class CollectionEditPage {
|
|
|
11294
11850
|
</mcms-entity-form>
|
|
11295
11851
|
}
|
|
11296
11852
|
} @else {
|
|
11297
|
-
<!-- Standard layout: form
|
|
11298
|
-
<
|
|
11299
|
-
|
|
11300
|
-
|
|
11301
|
-
|
|
11302
|
-
|
|
11303
|
-
|
|
11304
|
-
|
|
11853
|
+
<!-- Standard layout: form + optional sidebar -->
|
|
11854
|
+
<div class="flex gap-6">
|
|
11855
|
+
<div class="min-w-0 flex-1">
|
|
11856
|
+
<mcms-entity-form
|
|
11857
|
+
#entityForm
|
|
11858
|
+
[collection]="col"
|
|
11859
|
+
[entityId]="entityId()"
|
|
11860
|
+
[mode]="mode()"
|
|
11861
|
+
[basePath]="basePath"
|
|
11862
|
+
/>
|
|
11863
|
+
</div>
|
|
11864
|
+
<mcms-admin-slot slot="collection-edit:sidebar" [collectionSlug]="col.slug" />
|
|
11865
|
+
</div>
|
|
11305
11866
|
}
|
|
11867
|
+
<mcms-admin-slot slot="collection-edit:after" [collectionSlug]="col.slug" />
|
|
11306
11868
|
} @else {
|
|
11307
11869
|
<div class="p-12 text-center text-muted-foreground">Collection not found</div>
|
|
11308
11870
|
}
|
|
11309
|
-
`, 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 });
|
|
11871
|
+
`, 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 });
|
|
11310
11872
|
}
|
|
11311
11873
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: CollectionEditPage, decorators: [{
|
|
11312
11874
|
type: Component,
|
|
11313
11875
|
args: [{
|
|
11314
11876
|
selector: 'mcms-collection-edit',
|
|
11315
|
-
imports: [EntityFormWidget, LivePreviewComponent, Button],
|
|
11877
|
+
imports: [EntityFormWidget, LivePreviewComponent, Button, AdminSlotOutlet],
|
|
11316
11878
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
11317
11879
|
host: { class: 'block' },
|
|
11318
11880
|
template: `
|
|
11319
11881
|
@if (collection(); as col) {
|
|
11882
|
+
<mcms-admin-slot slot="collection-edit:before" [collectionSlug]="col.slug" />
|
|
11320
11883
|
@if (previewConfig(); as preview) {
|
|
11321
11884
|
@if (showPreview()) {
|
|
11322
11885
|
<!-- Split layout: form + preview -->
|
|
@@ -11351,6 +11914,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
11351
11914
|
(editBlockRequest)="onEditBlockRequest($event)"
|
|
11352
11915
|
/>
|
|
11353
11916
|
</div>
|
|
11917
|
+
<mcms-admin-slot slot="collection-edit:sidebar" [collectionSlug]="col.slug" />
|
|
11354
11918
|
</div>
|
|
11355
11919
|
} @else {
|
|
11356
11920
|
<!-- Full-width form (preview hidden) -->
|
|
@@ -11375,15 +11939,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
11375
11939
|
</mcms-entity-form>
|
|
11376
11940
|
}
|
|
11377
11941
|
} @else {
|
|
11378
|
-
<!-- Standard layout: form
|
|
11379
|
-
<
|
|
11380
|
-
|
|
11381
|
-
|
|
11382
|
-
|
|
11383
|
-
|
|
11384
|
-
|
|
11385
|
-
|
|
11942
|
+
<!-- Standard layout: form + optional sidebar -->
|
|
11943
|
+
<div class="flex gap-6">
|
|
11944
|
+
<div class="min-w-0 flex-1">
|
|
11945
|
+
<mcms-entity-form
|
|
11946
|
+
#entityForm
|
|
11947
|
+
[collection]="col"
|
|
11948
|
+
[entityId]="entityId()"
|
|
11949
|
+
[mode]="mode()"
|
|
11950
|
+
[basePath]="basePath"
|
|
11951
|
+
/>
|
|
11952
|
+
</div>
|
|
11953
|
+
<mcms-admin-slot slot="collection-edit:sidebar" [collectionSlug]="col.slug" />
|
|
11954
|
+
</div>
|
|
11386
11955
|
}
|
|
11956
|
+
<mcms-admin-slot slot="collection-edit:after" [collectionSlug]="col.slug" />
|
|
11387
11957
|
} @else {
|
|
11388
11958
|
<div class="p-12 text-center text-muted-foreground">Collection not found</div>
|
|
11389
11959
|
}
|
|
@@ -11476,6 +12046,7 @@ class LoginPage {
|
|
|
11476
12046
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: LoginPage, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
11477
12047
|
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: `
|
|
11478
12048
|
<main>
|
|
12049
|
+
<mcms-admin-slot slot="login:before" />
|
|
11479
12050
|
<mcms-card class="w-full max-w-md">
|
|
11480
12051
|
<mcms-card-header class="text-center">
|
|
11481
12052
|
<mcms-card-title>Sign In</mcms-card-title>
|
|
@@ -11613,8 +12184,9 @@ class LoginPage {
|
|
|
11613
12184
|
<p class="text-sm text-muted-foreground">Momentum CMS</p>
|
|
11614
12185
|
</mcms-card-footer>
|
|
11615
12186
|
</mcms-card>
|
|
12187
|
+
<mcms-admin-slot slot="login:after" />
|
|
11616
12188
|
</main>
|
|
11617
|
-
`, 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 });
|
|
12189
|
+
`, 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 });
|
|
11618
12190
|
}
|
|
11619
12191
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: LoginPage, decorators: [{
|
|
11620
12192
|
type: Component,
|
|
@@ -11631,6 +12203,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
11631
12203
|
CardDescription,
|
|
11632
12204
|
CardContent,
|
|
11633
12205
|
CardFooter,
|
|
12206
|
+
AdminSlotOutlet,
|
|
11634
12207
|
],
|
|
11635
12208
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
11636
12209
|
host: {
|
|
@@ -11638,6 +12211,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
11638
12211
|
},
|
|
11639
12212
|
template: `
|
|
11640
12213
|
<main>
|
|
12214
|
+
<mcms-admin-slot slot="login:before" />
|
|
11641
12215
|
<mcms-card class="w-full max-w-md">
|
|
11642
12216
|
<mcms-card-header class="text-center">
|
|
11643
12217
|
<mcms-card-title>Sign In</mcms-card-title>
|
|
@@ -11775,6 +12349,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
|
|
|
11775
12349
|
<p class="text-sm text-muted-foreground">Momentum CMS</p>
|
|
11776
12350
|
</mcms-card-footer>
|
|
11777
12351
|
</mcms-card>
|
|
12352
|
+
<mcms-admin-slot slot="login:after" />
|
|
11778
12353
|
</main>
|
|
11779
12354
|
`,
|
|
11780
12355
|
}]
|
|
@@ -14213,18 +14788,18 @@ function provideMomentumFieldRenderers() {
|
|
|
14213
14788
|
registry.register('checkbox', () => Promise.resolve().then(function () { return checkboxField_component; }).then((m) => m.CheckboxFieldRenderer));
|
|
14214
14789
|
registry.register('date', () => Promise.resolve().then(function () { return dateField_component; }).then((m) => m.DateFieldRenderer));
|
|
14215
14790
|
registry.register('upload', () => Promise.resolve().then(function () { return uploadField_component; }).then((m) => m.UploadFieldRenderer));
|
|
14216
|
-
registry.register('richText', () => import('./momentumcms-admin-rich-text-field.component-
|
|
14791
|
+
registry.register('richText', () => import('./momentumcms-admin-rich-text-field.component-CZQSu4lc.mjs').then((m) => m.RichTextFieldRenderer));
|
|
14217
14792
|
// Layout field renderers (support nested field rendering)
|
|
14218
|
-
registry.register('group', () => import('./momentumcms-admin-group-field.component-
|
|
14219
|
-
registry.register('array', () => import('./momentumcms-admin-array-field.component-
|
|
14220
|
-
registry.register('blocks', () => import('./momentumcms-admin-blocks-field.component-
|
|
14793
|
+
registry.register('group', () => import('./momentumcms-admin-group-field.component-qy0Z-cRK.mjs').then((m) => m.GroupFieldRenderer));
|
|
14794
|
+
registry.register('array', () => import('./momentumcms-admin-array-field.component-BLL21bqy.mjs').then((m) => m.ArrayFieldRenderer));
|
|
14795
|
+
registry.register('blocks', () => import('./momentumcms-admin-blocks-field.component-BQHIguqN.mjs').then((m) => m.BlocksFieldRenderer));
|
|
14221
14796
|
// Visual block editor variant (blocks field with admin.editor === 'visual')
|
|
14222
14797
|
registry.register('blocks-visual', () => Promise.resolve().then(function () { return visualBlockEditor_component; }).then((m) => m.VisualBlockEditorComponent));
|
|
14223
|
-
registry.register('relationship', () => import('./momentumcms-admin-relationship-field.component-
|
|
14798
|
+
registry.register('relationship', () => import('./momentumcms-admin-relationship-field.component-DS68G61P.mjs').then((m) => m.RelationshipFieldRenderer));
|
|
14224
14799
|
// Layout-only renderers (tabs, collapsible, row)
|
|
14225
|
-
registry.register('tabs', () => import('./momentumcms-admin-tabs-field.component-
|
|
14226
|
-
registry.register('collapsible', () => import('./momentumcms-admin-collapsible-field.component-
|
|
14227
|
-
registry.register('row', () => import('./momentumcms-admin-row-field.component-
|
|
14800
|
+
registry.register('tabs', () => import('./momentumcms-admin-tabs-field.component-DG8vPjj4.mjs').then((m) => m.TabsFieldRenderer));
|
|
14801
|
+
registry.register('collapsible', () => import('./momentumcms-admin-collapsible-field.component-DSE4X1xV.mjs').then((m) => m.CollapsibleFieldRenderer));
|
|
14802
|
+
registry.register('row', () => import('./momentumcms-admin-row-field.component-109gO-Rp.mjs').then((m) => m.RowFieldRenderer));
|
|
14228
14803
|
};
|
|
14229
14804
|
},
|
|
14230
14805
|
},
|
|
@@ -14259,6 +14834,104 @@ function provideFieldRenderer(type, loader) {
|
|
|
14259
14834
|
]);
|
|
14260
14835
|
}
|
|
14261
14836
|
|
|
14837
|
+
/**
|
|
14838
|
+
* Registry-aware page resolver that delegates to either a registered
|
|
14839
|
+
* custom component or the built-in fallback.
|
|
14840
|
+
*
|
|
14841
|
+
* Used as the `loadComponent` for every admin route. Route `data` provides:
|
|
14842
|
+
* - `adminPageKey` — the registry key (e.g., 'dashboard', 'collection-list')
|
|
14843
|
+
* - `adminPageFallback` — lazy loader for the built-in default
|
|
14844
|
+
*
|
|
14845
|
+
* For collection pages, reads `:slug` from route params to check per-collection overrides.
|
|
14846
|
+
*
|
|
14847
|
+
* Subscribes to route observables so the component re-resolves when Angular
|
|
14848
|
+
* reuses the instance for sibling routes (e.g., navigating between collections).
|
|
14849
|
+
*
|
|
14850
|
+
* Implements HasUnsavedChanges to delegate to the resolved component for
|
|
14851
|
+
* the unsaved changes guard.
|
|
14852
|
+
*/
|
|
14853
|
+
class AdminPageResolver {
|
|
14854
|
+
registry = inject(AdminComponentRegistry);
|
|
14855
|
+
route = inject(ActivatedRoute);
|
|
14856
|
+
destroyRef = inject(DestroyRef);
|
|
14857
|
+
outlet = viewChild(NgComponentOutlet, ...(ngDevMode ? [{ debugName: "outlet" }] : []));
|
|
14858
|
+
resolvedComponent = signal(null, ...(ngDevMode ? [{ debugName: "resolvedComponent" }] : []));
|
|
14859
|
+
/** Incremented on every load to detect stale promise resolutions. */
|
|
14860
|
+
loadGeneration = 0;
|
|
14861
|
+
ngOnInit() {
|
|
14862
|
+
combineLatest([this.route.data, this.route.params])
|
|
14863
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
14864
|
+
.subscribe(([data, params]) => {
|
|
14865
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- route data is Record<string, unknown>
|
|
14866
|
+
const pageKey = data['adminPageKey'];
|
|
14867
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- route data is Record<string, unknown>
|
|
14868
|
+
const fallback = data['adminPageFallback'];
|
|
14869
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- route params are Record<string, string>
|
|
14870
|
+
const slug = params['slug'];
|
|
14871
|
+
const override = this.registry.resolve(pageKey, slug);
|
|
14872
|
+
const loader = override ?? fallback;
|
|
14873
|
+
if (loader) {
|
|
14874
|
+
const generation = ++this.loadGeneration;
|
|
14875
|
+
loader()
|
|
14876
|
+
.then((component) => {
|
|
14877
|
+
if (generation !== this.loadGeneration)
|
|
14878
|
+
return;
|
|
14879
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- loader resolves to unknown from registry, safe cast to Type
|
|
14880
|
+
this.resolvedComponent.set(component);
|
|
14881
|
+
})
|
|
14882
|
+
.catch((err) => {
|
|
14883
|
+
if (generation !== this.loadGeneration)
|
|
14884
|
+
return;
|
|
14885
|
+
console.error('[AdminPageResolver] Failed to load component:', err);
|
|
14886
|
+
});
|
|
14887
|
+
}
|
|
14888
|
+
});
|
|
14889
|
+
}
|
|
14890
|
+
hasUnsavedChanges() {
|
|
14891
|
+
const instance = this.outlet()?.componentInstance;
|
|
14892
|
+
if (instance == null)
|
|
14893
|
+
return false;
|
|
14894
|
+
// Check if the resolved component implements HasUnsavedChanges
|
|
14895
|
+
if ('hasUnsavedChanges' in instance && typeof instance.hasUnsavedChanges === 'function') {
|
|
14896
|
+
return Boolean(instance.hasUnsavedChanges());
|
|
14897
|
+
}
|
|
14898
|
+
return false;
|
|
14899
|
+
}
|
|
14900
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AdminPageResolver, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
14901
|
+
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: `
|
|
14902
|
+
@if (resolvedComponent()) {
|
|
14903
|
+
<ng-container *ngComponentOutlet="resolvedComponent()" />
|
|
14904
|
+
} @else {
|
|
14905
|
+
<div role="status" aria-label="Loading page" class="flex h-full items-center justify-center">
|
|
14906
|
+
<div class="h-8 w-8 animate-spin rounded-full border-4 border-muted border-t-primary"></div>
|
|
14907
|
+
</div>
|
|
14908
|
+
}
|
|
14909
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
14910
|
+
}
|
|
14911
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: AdminPageResolver, decorators: [{
|
|
14912
|
+
type: Component,
|
|
14913
|
+
args: [{
|
|
14914
|
+
selector: 'mcms-admin-page-resolver',
|
|
14915
|
+
imports: [NgComponentOutlet],
|
|
14916
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
14917
|
+
host: { class: 'block' },
|
|
14918
|
+
template: `
|
|
14919
|
+
@if (resolvedComponent()) {
|
|
14920
|
+
<ng-container *ngComponentOutlet="resolvedComponent()" />
|
|
14921
|
+
} @else {
|
|
14922
|
+
<div role="status" aria-label="Loading page" class="flex h-full items-center justify-center">
|
|
14923
|
+
<div class="h-8 w-8 animate-spin rounded-full border-4 border-muted border-t-primary"></div>
|
|
14924
|
+
</div>
|
|
14925
|
+
}
|
|
14926
|
+
`,
|
|
14927
|
+
}]
|
|
14928
|
+
}], propDecorators: { outlet: [{ type: i0.ViewChild, args: [i0.forwardRef(() => NgComponentOutlet), { isSignal: true }] }] } });
|
|
14929
|
+
|
|
14930
|
+
var adminPageResolver_component = /*#__PURE__*/Object.freeze({
|
|
14931
|
+
__proto__: null,
|
|
14932
|
+
AdminPageResolver: AdminPageResolver
|
|
14933
|
+
});
|
|
14934
|
+
|
|
14262
14935
|
/* eslint-disable @typescript-eslint/consistent-type-assertions -- Type assertions needed to narrow Field union to TextField/TextareaField after type guard */
|
|
14263
14936
|
/**
|
|
14264
14937
|
* Text field renderer for text and textarea field types.
|
|
@@ -16003,5 +16676,5 @@ var uploadField_component = /*#__PURE__*/Object.freeze({
|
|
|
16003
16676
|
* Generated bundle index. Do not edit.
|
|
16004
16677
|
*/
|
|
16005
16678
|
|
|
16006
|
-
export {
|
|
16007
|
-
//# sourceMappingURL=momentumcms-admin-momentumcms-admin-
|
|
16679
|
+
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 };
|
|
16680
|
+
//# sourceMappingURL=momentumcms-admin-momentumcms-admin-DDwm1Rm3.mjs.map
|