@momentumcms/admin 0.0.1
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/LICENSE +21 -0
- package/README.md +7 -0
- package/fesm2022/momentumcms-admin-global-edit.page-DFDV-uh3.mjs +90 -0
- package/fesm2022/momentumcms-admin-global-edit.page-DFDV-uh3.mjs.map +1 -0
- package/fesm2022/momentumcms-admin-momentumcms-admin-z82jXEsP.mjs +16886 -0
- package/fesm2022/momentumcms-admin-momentumcms-admin-z82jXEsP.mjs.map +1 -0
- package/fesm2022/momentumcms-admin.mjs +2 -0
- package/fesm2022/momentumcms-admin.mjs.map +1 -0
- package/package.json +58 -0
- package/tailwind.preset.js +181 -0
- package/types/momentumcms-admin.d.ts +3706 -0
|
@@ -0,0 +1,3706 @@
|
|
|
1
|
+
import { Routes, CanActivateFn, CanDeactivateFn } from '@angular/router';
|
|
2
|
+
import * as _angular_core from '@angular/core';
|
|
3
|
+
import { Type, InjectionToken, Signal, Provider, OnInit, WritableSignal, ElementRef } from '@angular/core';
|
|
4
|
+
import { CollectionConfig, GlobalConfig, PluginAdminRouteDescriptor, MomentumPlugin, MomentumConfig, MediaDocument, AdminConfig, Field, DocumentStatus as DocumentStatus$1, BlockConfig, UploadField } from '@momentumcms/core';
|
|
5
|
+
import { HttpContextToken, HttpInterceptorFn } from '@angular/common/http';
|
|
6
|
+
import { Observable } from 'rxjs';
|
|
7
|
+
import { DataTableColumn, DataTableSort, DataTableRowAction, DataTableRowActionEvent, FieldDisplayType, FieldDisplayFieldMeta, FieldDisplayNumberFormat, FieldDisplayDateFormat, PopoverTrigger, ValidationError, SelectOption } from '@momentumcms/ui';
|
|
8
|
+
import * as _angular_forms_signals from '@angular/forms/signals';
|
|
9
|
+
import { SafeResourceUrl } from '@angular/platform-browser';
|
|
10
|
+
import { CdkDragDrop } from '@angular/cdk/drag-drop';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Momentum Admin Routes Factory
|
|
14
|
+
*
|
|
15
|
+
* Creates Angular routes for the admin UI that can be integrated into
|
|
16
|
+
* any Angular application or Analog.js app.
|
|
17
|
+
*
|
|
18
|
+
* Usage in Angular:
|
|
19
|
+
* ```typescript
|
|
20
|
+
* import { momentumAdminRoutes } from '@momentumcms/admin';
|
|
21
|
+
* import { collections } from './collections';
|
|
22
|
+
*
|
|
23
|
+
* export const routes: Routes = [
|
|
24
|
+
* ...momentumAdminRoutes({
|
|
25
|
+
* basePath: '/admin',
|
|
26
|
+
* collections,
|
|
27
|
+
* }),
|
|
28
|
+
* ];
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
interface MomentumAdminBranding {
|
|
33
|
+
/** Logo URL */
|
|
34
|
+
logo?: string;
|
|
35
|
+
/** Site title */
|
|
36
|
+
title?: string;
|
|
37
|
+
/** Primary color (CSS color value) */
|
|
38
|
+
primaryColor?: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Admin plugin route descriptor with Angular-typed loadComponent.
|
|
42
|
+
* Mirrors Angular Route concepts (path, loadComponent, data) with
|
|
43
|
+
* additional Momentum sidebar metadata (label, icon, group).
|
|
44
|
+
*/
|
|
45
|
+
interface AdminPluginRoute {
|
|
46
|
+
/** Route path under admin (e.g., 'analytics') — same as Angular Route.path */
|
|
47
|
+
path: string;
|
|
48
|
+
/** Lazy component loader — same as Angular Route.loadComponent */
|
|
49
|
+
loadComponent: () => Promise<Type<unknown>>;
|
|
50
|
+
/** Optional route data — same as Angular Route.data */
|
|
51
|
+
data?: Record<string, unknown>;
|
|
52
|
+
/** Sidebar display label */
|
|
53
|
+
label: string;
|
|
54
|
+
/** Icon name from ng-icons (e.g., 'heroChartBarSquare') */
|
|
55
|
+
icon: string;
|
|
56
|
+
/** Sidebar section name. @default 'Plugins' */
|
|
57
|
+
group?: string;
|
|
58
|
+
}
|
|
59
|
+
interface MomentumAdminOptions {
|
|
60
|
+
/** Base path for admin routes (e.g., '/admin') */
|
|
61
|
+
basePath: string;
|
|
62
|
+
/** Collection configurations */
|
|
63
|
+
collections: CollectionConfig[];
|
|
64
|
+
/** Global configurations (singleton documents) */
|
|
65
|
+
globals?: GlobalConfig[];
|
|
66
|
+
/** Optional branding customization */
|
|
67
|
+
branding?: MomentumAdminBranding;
|
|
68
|
+
/** Whether to include auth routes (login, setup). Defaults to true */
|
|
69
|
+
includeAuthRoutes?: boolean;
|
|
70
|
+
/** Plugin-registered admin routes. Accepts both Angular-typed and core-typed descriptors. */
|
|
71
|
+
pluginRoutes?: AdminPluginRoute[] | PluginAdminRouteDescriptor[];
|
|
72
|
+
/** Plugins — their static collections and admin routes are merged automatically. */
|
|
73
|
+
plugins?: MomentumPlugin[];
|
|
74
|
+
}
|
|
75
|
+
interface MomentumAdminRouteData {
|
|
76
|
+
collections: CollectionConfig[];
|
|
77
|
+
globals?: GlobalConfig[];
|
|
78
|
+
branding?: MomentumAdminBranding;
|
|
79
|
+
pluginRoutes?: AdminPluginRoute[];
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Creates Angular routes for the Momentum CMS admin UI.
|
|
83
|
+
*
|
|
84
|
+
* Can be called with a full `MomentumConfig` (config-driven) or explicit options.
|
|
85
|
+
*
|
|
86
|
+
* Config-driven usage (recommended):
|
|
87
|
+
* ```typescript
|
|
88
|
+
* import config from '../momentum.config';
|
|
89
|
+
* export const routes = [...momentumAdminRoutes(config)];
|
|
90
|
+
* ```
|
|
91
|
+
*
|
|
92
|
+
* Options-based usage:
|
|
93
|
+
* ```typescript
|
|
94
|
+
* momentumAdminRoutes({ basePath: '/admin', collections, branding })
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
declare function momentumAdminRoutes(config: MomentumConfig): Routes;
|
|
98
|
+
declare function momentumAdminRoutes(options: MomentumAdminOptions): Routes;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Set this context token to `true` on any HTTP request to skip
|
|
102
|
+
* the automatic CUD toast for that specific request.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```typescript
|
|
106
|
+
* const context = new HttpContext().set(SKIP_AUTO_TOAST, true);
|
|
107
|
+
* this.http.post('/api/posts', data, { context });
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
declare const SKIP_AUTO_TOAST: HttpContextToken<boolean>;
|
|
111
|
+
/**
|
|
112
|
+
* HTTP interceptor that automatically shows toast notifications
|
|
113
|
+
* for CUD (Create, Update, Delete) operations on collection API endpoints.
|
|
114
|
+
*
|
|
115
|
+
* Handles both success and error responses:
|
|
116
|
+
* - Success: Shows appropriate created/updated/deleted toast
|
|
117
|
+
* - Error 400 with validation errors: Shows validation failed toast
|
|
118
|
+
* - Error 403: Shows not authorized toast
|
|
119
|
+
* - Error 404: Shows entity not found toast
|
|
120
|
+
* - Other errors: Shows generic operation failed toast
|
|
121
|
+
*
|
|
122
|
+
* Use `SKIP_AUTO_TOAST` context token to opt out for specific requests.
|
|
123
|
+
*/
|
|
124
|
+
declare const crudToastInterceptor: HttpInterceptorFn;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Momentum CMS API Service
|
|
128
|
+
*
|
|
129
|
+
* Handles communication with the Momentum CMS REST API.
|
|
130
|
+
*/
|
|
131
|
+
declare class MomentumApiService {
|
|
132
|
+
private readonly http;
|
|
133
|
+
private readonly baseUrl;
|
|
134
|
+
/**
|
|
135
|
+
* Fetch all documents from a collection
|
|
136
|
+
*/
|
|
137
|
+
findAll(collection: string): Observable<Record<string, unknown>[]>;
|
|
138
|
+
/**
|
|
139
|
+
* Fetch a single document by ID
|
|
140
|
+
*/
|
|
141
|
+
findById(collection: string, id: string): Observable<Record<string, unknown> | null>;
|
|
142
|
+
/**
|
|
143
|
+
* Create a new document
|
|
144
|
+
*/
|
|
145
|
+
create(collection: string, data: Record<string, unknown>): Observable<Record<string, unknown>>;
|
|
146
|
+
/**
|
|
147
|
+
* Update an existing document
|
|
148
|
+
*/
|
|
149
|
+
update(collection: string, id: string, data: Record<string, unknown>): Observable<Record<string, unknown>>;
|
|
150
|
+
/**
|
|
151
|
+
* Delete a document
|
|
152
|
+
*/
|
|
153
|
+
delete(collection: string, id: string): Observable<boolean>;
|
|
154
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<MomentumApiService, never>;
|
|
155
|
+
static ɵprov: _angular_core.ɵɵInjectableDeclaration<MomentumApiService>;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Response type for the setup status endpoint.
|
|
160
|
+
*/
|
|
161
|
+
interface SetupStatus {
|
|
162
|
+
/** True if no users exist and setup is required */
|
|
163
|
+
needsSetup: boolean;
|
|
164
|
+
/** True if at least one user exists */
|
|
165
|
+
hasUsers: boolean;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* User data returned from Better Auth.
|
|
169
|
+
*/
|
|
170
|
+
interface AuthUser {
|
|
171
|
+
id: string;
|
|
172
|
+
email: string;
|
|
173
|
+
name: string;
|
|
174
|
+
role: string;
|
|
175
|
+
emailVerified: boolean;
|
|
176
|
+
twoFactorEnabled?: boolean;
|
|
177
|
+
image?: string | null;
|
|
178
|
+
createdAt: string;
|
|
179
|
+
updatedAt: string;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Session data from Better Auth.
|
|
183
|
+
*/
|
|
184
|
+
interface AuthSession {
|
|
185
|
+
id: string;
|
|
186
|
+
userId: string;
|
|
187
|
+
token: string;
|
|
188
|
+
expiresAt: string;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Result type for auth operations.
|
|
192
|
+
*/
|
|
193
|
+
interface AuthResult {
|
|
194
|
+
success: boolean;
|
|
195
|
+
error?: string;
|
|
196
|
+
user?: AuthUser;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Result type for enabling two-factor authentication.
|
|
200
|
+
*/
|
|
201
|
+
interface TwoFactorEnableResult {
|
|
202
|
+
success: boolean;
|
|
203
|
+
error?: string;
|
|
204
|
+
totpURI?: string;
|
|
205
|
+
backupCodes?: string[];
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Momentum Auth Service
|
|
209
|
+
*
|
|
210
|
+
* Manages authentication state using signals and communicates with Better Auth
|
|
211
|
+
* endpoints on the server.
|
|
212
|
+
*
|
|
213
|
+
* @example
|
|
214
|
+
* ```typescript
|
|
215
|
+
* const auth = inject(MomentumAuthService);
|
|
216
|
+
*
|
|
217
|
+
* // Check if authenticated
|
|
218
|
+
* if (auth.isAuthenticated()) {
|
|
219
|
+
* console.log('User:', auth.user()?.name);
|
|
220
|
+
* }
|
|
221
|
+
*
|
|
222
|
+
* // Sign in
|
|
223
|
+
* const result = await auth.signIn('user@example.com', 'password');
|
|
224
|
+
* if (!result.success) {
|
|
225
|
+
* console.error(result.error);
|
|
226
|
+
* }
|
|
227
|
+
* ```
|
|
228
|
+
*/
|
|
229
|
+
declare class MomentumAuthService {
|
|
230
|
+
private readonly http;
|
|
231
|
+
private readonly document;
|
|
232
|
+
private readonly baseUrl;
|
|
233
|
+
/** Current authenticated user */
|
|
234
|
+
readonly user: _angular_core.WritableSignal<AuthUser | null>;
|
|
235
|
+
/** Whether auth state is being loaded */
|
|
236
|
+
readonly loading: _angular_core.WritableSignal<boolean>;
|
|
237
|
+
/** Whether the app needs initial setup (no users exist) */
|
|
238
|
+
readonly needsSetup: _angular_core.WritableSignal<boolean>;
|
|
239
|
+
/** Whether a user is currently authenticated */
|
|
240
|
+
readonly isAuthenticated: _angular_core.Signal<boolean>;
|
|
241
|
+
/** Current user's role */
|
|
242
|
+
readonly role: _angular_core.Signal<string | null>;
|
|
243
|
+
/** Whether current user is an admin */
|
|
244
|
+
readonly isAdmin: _angular_core.Signal<boolean>;
|
|
245
|
+
/** Whether current user has verified their email */
|
|
246
|
+
readonly emailVerified: _angular_core.Signal<boolean>;
|
|
247
|
+
/** Tracks in-flight initialization to prevent duplicate requests */
|
|
248
|
+
private initPromise;
|
|
249
|
+
/**
|
|
250
|
+
* Initialize auth state by checking current session.
|
|
251
|
+
* Should be called on app startup. Safe to call concurrently from multiple guards.
|
|
252
|
+
*/
|
|
253
|
+
initialize(): Promise<void>;
|
|
254
|
+
private doInitialize;
|
|
255
|
+
/**
|
|
256
|
+
* Check if the application needs initial setup (no users exist).
|
|
257
|
+
*/
|
|
258
|
+
checkSetupStatus(): Promise<SetupStatus>;
|
|
259
|
+
/**
|
|
260
|
+
* Refresh the current session from the server.
|
|
261
|
+
*/
|
|
262
|
+
refreshSession(): Promise<AuthUser | null>;
|
|
263
|
+
/**
|
|
264
|
+
* Fetch the current user from the Momentum session resolver,
|
|
265
|
+
* which has the correctly-resolved role from the users collection.
|
|
266
|
+
* Better Auth's get-session returns the default role, not the Momentum role.
|
|
267
|
+
*/
|
|
268
|
+
private resolveUserRole;
|
|
269
|
+
/**
|
|
270
|
+
* Sign in with email and password.
|
|
271
|
+
*/
|
|
272
|
+
signIn(email: string, password: string): Promise<AuthResult>;
|
|
273
|
+
/**
|
|
274
|
+
* Sign up a new user with email and password.
|
|
275
|
+
*
|
|
276
|
+
* @param name User's display name
|
|
277
|
+
* @param email User's email address
|
|
278
|
+
* @param password User's password (min 8 characters)
|
|
279
|
+
* @param isFirstUser If true, grants admin role (for setup flow)
|
|
280
|
+
*/
|
|
281
|
+
signUp(name: string, email: string, password: string, isFirstUser?: boolean): Promise<AuthResult>;
|
|
282
|
+
/**
|
|
283
|
+
* Sign out the current user.
|
|
284
|
+
*/
|
|
285
|
+
signOut(): Promise<void>;
|
|
286
|
+
/**
|
|
287
|
+
* Request a password reset email.
|
|
288
|
+
*
|
|
289
|
+
* @param email The email address to send the reset link to
|
|
290
|
+
* @param redirectTo URL to redirect user after clicking the reset link (defaults to /admin/reset-password)
|
|
291
|
+
* @returns Always returns success to prevent email enumeration attacks
|
|
292
|
+
*/
|
|
293
|
+
requestPasswordReset(email: string, redirectTo?: string): Promise<AuthResult>;
|
|
294
|
+
/**
|
|
295
|
+
* Reset password using a reset token.
|
|
296
|
+
*
|
|
297
|
+
* @param token The reset token from the email link
|
|
298
|
+
* @param newPassword The new password to set
|
|
299
|
+
*/
|
|
300
|
+
resetPassword(token: string, newPassword: string): Promise<AuthResult>;
|
|
301
|
+
/** Whether current user has 2FA enabled */
|
|
302
|
+
readonly twoFactorEnabled: _angular_core.Signal<boolean>;
|
|
303
|
+
/**
|
|
304
|
+
* Enable two-factor authentication for the current user.
|
|
305
|
+
* Returns the TOTP URI (for QR code generation) and backup codes.
|
|
306
|
+
*/
|
|
307
|
+
enableTwoFactor(password: string): Promise<TwoFactorEnableResult>;
|
|
308
|
+
/**
|
|
309
|
+
* Verify a TOTP code to complete 2FA setup or sign-in.
|
|
310
|
+
*/
|
|
311
|
+
verifyTOTP(code: string, trustDevice?: boolean): Promise<AuthResult>;
|
|
312
|
+
/**
|
|
313
|
+
* Disable two-factor authentication for the current user.
|
|
314
|
+
*/
|
|
315
|
+
disableTwoFactor(password: string): Promise<AuthResult>;
|
|
316
|
+
/**
|
|
317
|
+
* Get the list of enabled OAuth providers from the server.
|
|
318
|
+
*/
|
|
319
|
+
getOAuthProviders(): Promise<string[]>;
|
|
320
|
+
/**
|
|
321
|
+
* Initiate OAuth sign-in by redirecting to the provider's authorization page.
|
|
322
|
+
* Better Auth handles the redirect flow via /api/auth/sign-in/social.
|
|
323
|
+
*
|
|
324
|
+
* @param provider The OAuth provider name (e.g., 'google', 'github')
|
|
325
|
+
* @param callbackURL URL to redirect to after successful authentication
|
|
326
|
+
*/
|
|
327
|
+
signInWithOAuth(provider: string, callbackURL?: string): void;
|
|
328
|
+
/**
|
|
329
|
+
* Extract error message from HTTP error response.
|
|
330
|
+
*/
|
|
331
|
+
private extractErrorMessage;
|
|
332
|
+
/**
|
|
333
|
+
* Type guard for HTTP error responses with message.
|
|
334
|
+
*/
|
|
335
|
+
private isHttpErrorWithMessage;
|
|
336
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<MomentumAuthService, never>;
|
|
337
|
+
static ɵprov: _angular_core.ɵɵInjectableDeclaration<MomentumAuthService>;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Permissions for a single collection.
|
|
342
|
+
*/
|
|
343
|
+
interface CollectionPermissions {
|
|
344
|
+
slug: string;
|
|
345
|
+
canAccess: boolean;
|
|
346
|
+
canCreate: boolean;
|
|
347
|
+
canRead: boolean;
|
|
348
|
+
canUpdate: boolean;
|
|
349
|
+
canDelete: boolean;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Collection Access Service
|
|
353
|
+
*
|
|
354
|
+
* Manages collection-level access permissions for the admin panel.
|
|
355
|
+
* Fetches permissions from the server and provides reactive state
|
|
356
|
+
* for guards and components to use.
|
|
357
|
+
*
|
|
358
|
+
* @example
|
|
359
|
+
* ```typescript
|
|
360
|
+
* const accessService = inject(CollectionAccessService);
|
|
361
|
+
*
|
|
362
|
+
* // Load permissions
|
|
363
|
+
* await accessService.loadAccess();
|
|
364
|
+
*
|
|
365
|
+
* // Check access
|
|
366
|
+
* if (accessService.canAccess('posts')) {
|
|
367
|
+
* // Show posts collection
|
|
368
|
+
* }
|
|
369
|
+
*
|
|
370
|
+
* // Check specific operation
|
|
371
|
+
* if (accessService.canCreate('posts')) {
|
|
372
|
+
* // Show create button
|
|
373
|
+
* }
|
|
374
|
+
* ```
|
|
375
|
+
*/
|
|
376
|
+
declare class CollectionAccessService {
|
|
377
|
+
private readonly http;
|
|
378
|
+
/** Full permissions for all collections */
|
|
379
|
+
private readonly _permissions;
|
|
380
|
+
/** Whether permissions are being loaded */
|
|
381
|
+
readonly loading: _angular_core.WritableSignal<boolean>;
|
|
382
|
+
/** Whether permissions have been loaded at least once */
|
|
383
|
+
readonly initialized: _angular_core.WritableSignal<boolean>;
|
|
384
|
+
/** Error message if loading failed */
|
|
385
|
+
readonly error: _angular_core.WritableSignal<string | null>;
|
|
386
|
+
/** All collection permissions (read-only) */
|
|
387
|
+
readonly permissions: _angular_core.Signal<CollectionPermissions[]>;
|
|
388
|
+
/** Slugs of collections the user can access in admin panel */
|
|
389
|
+
readonly accessibleCollections: _angular_core.Signal<string[]>;
|
|
390
|
+
/** Tracks in-flight load to prevent duplicate requests */
|
|
391
|
+
private loadPromise;
|
|
392
|
+
/**
|
|
393
|
+
* Load collection permissions from the server.
|
|
394
|
+
* Should be called when the user authenticates or on app init.
|
|
395
|
+
* Safe to call concurrently from multiple guards.
|
|
396
|
+
*/
|
|
397
|
+
loadAccess(): Promise<void>;
|
|
398
|
+
private doLoadAccess;
|
|
399
|
+
/**
|
|
400
|
+
* Reset permissions state.
|
|
401
|
+
* Should be called when the user logs out.
|
|
402
|
+
*/
|
|
403
|
+
reset(): void;
|
|
404
|
+
/**
|
|
405
|
+
* Check if the user can access a collection in the admin panel.
|
|
406
|
+
*/
|
|
407
|
+
canAccess(slug: string): boolean;
|
|
408
|
+
/**
|
|
409
|
+
* Check if the user can create documents in a collection.
|
|
410
|
+
*/
|
|
411
|
+
canCreate(slug: string): boolean;
|
|
412
|
+
/**
|
|
413
|
+
* Check if the user can read documents in a collection.
|
|
414
|
+
*/
|
|
415
|
+
canRead(slug: string): boolean;
|
|
416
|
+
/**
|
|
417
|
+
* Check if the user can update documents in a collection.
|
|
418
|
+
*/
|
|
419
|
+
canUpdate(slug: string): boolean;
|
|
420
|
+
/**
|
|
421
|
+
* Check if the user can delete documents in a collection.
|
|
422
|
+
*/
|
|
423
|
+
canDelete(slug: string): boolean;
|
|
424
|
+
/**
|
|
425
|
+
* Get full permissions for a specific collection.
|
|
426
|
+
*/
|
|
427
|
+
getPermissions(slug: string): CollectionPermissions | undefined;
|
|
428
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<CollectionAccessService, never>;
|
|
429
|
+
static ɵprov: _angular_core.ɵɵInjectableDeclaration<CollectionAccessService>;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Upload progress state.
|
|
434
|
+
*/
|
|
435
|
+
interface UploadProgress {
|
|
436
|
+
/** Current status of the upload */
|
|
437
|
+
status: 'pending' | 'uploading' | 'complete' | 'error';
|
|
438
|
+
/** Upload progress percentage (0-100) */
|
|
439
|
+
progress: number;
|
|
440
|
+
/** Original file being uploaded */
|
|
441
|
+
file: File;
|
|
442
|
+
/** Created media document (when complete) */
|
|
443
|
+
result?: MediaDocument;
|
|
444
|
+
/** Error message (when error) */
|
|
445
|
+
error?: string;
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Service for handling file uploads to Momentum CMS.
|
|
449
|
+
*
|
|
450
|
+
* @example
|
|
451
|
+
* ```typescript
|
|
452
|
+
* const uploadService = inject(UploadService);
|
|
453
|
+
* uploadService.upload(file).subscribe(progress => {
|
|
454
|
+
* console.log(`Progress: ${progress.progress}%`);
|
|
455
|
+
* if (progress.status === 'complete') {
|
|
456
|
+
* console.log('Uploaded:', progress.result);
|
|
457
|
+
* }
|
|
458
|
+
* });
|
|
459
|
+
* ```
|
|
460
|
+
*/
|
|
461
|
+
declare class UploadService {
|
|
462
|
+
private readonly http;
|
|
463
|
+
private readonly uploadUrl;
|
|
464
|
+
/** Active uploads signal */
|
|
465
|
+
private readonly activeUploadsSignal;
|
|
466
|
+
/** Number of active uploads */
|
|
467
|
+
readonly activeUploadCount: _angular_core.Signal<number>;
|
|
468
|
+
/** Whether any uploads are in progress */
|
|
469
|
+
readonly isUploading: _angular_core.Signal<boolean>;
|
|
470
|
+
/**
|
|
471
|
+
* Upload a single file.
|
|
472
|
+
*
|
|
473
|
+
* @param file - The file to upload
|
|
474
|
+
* @param alt - Optional alt text for images
|
|
475
|
+
* @returns Observable emitting upload progress
|
|
476
|
+
*/
|
|
477
|
+
upload(file: File, alt?: string): Observable<UploadProgress>;
|
|
478
|
+
/**
|
|
479
|
+
* Upload multiple files.
|
|
480
|
+
*
|
|
481
|
+
* @param files - Array of files to upload
|
|
482
|
+
* @returns Array of observables for each file's progress
|
|
483
|
+
*/
|
|
484
|
+
uploadMultiple(files: File[]): Observable<UploadProgress>[];
|
|
485
|
+
/**
|
|
486
|
+
* Get the URL for a media document.
|
|
487
|
+
*
|
|
488
|
+
* @param media - Media document or ID
|
|
489
|
+
* @returns URL to access the file
|
|
490
|
+
*/
|
|
491
|
+
getMediaUrl(media: MediaDocument | string): string;
|
|
492
|
+
/**
|
|
493
|
+
* Update active upload progress.
|
|
494
|
+
*/
|
|
495
|
+
private updateActiveUpload;
|
|
496
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<UploadService, never>;
|
|
497
|
+
static ɵprov: _angular_core.ɵɵInjectableDeclaration<UploadService>;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Momentum API Service for Angular
|
|
502
|
+
*
|
|
503
|
+
* Provides a unified interface for data operations that works seamlessly
|
|
504
|
+
* in both server-side rendering (SSR) and browser contexts.
|
|
505
|
+
*
|
|
506
|
+
* @example
|
|
507
|
+
* ```typescript
|
|
508
|
+
* import { injectMomentumAPI } from '@momentumcms/admin';
|
|
509
|
+
*
|
|
510
|
+
* @Component({...})
|
|
511
|
+
* export class PostsComponent {
|
|
512
|
+
* private readonly api = injectMomentumAPI();
|
|
513
|
+
*
|
|
514
|
+
* async loadPosts(): Promise<void> {
|
|
515
|
+
* const result = await this.api.collection<Post>('posts').find({ limit: 10 });
|
|
516
|
+
* this.posts.set(result.docs);
|
|
517
|
+
* }
|
|
518
|
+
* }
|
|
519
|
+
* ```
|
|
520
|
+
*/
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* User context for API operations.
|
|
524
|
+
*/
|
|
525
|
+
interface UserContext {
|
|
526
|
+
id: string | number;
|
|
527
|
+
email?: string;
|
|
528
|
+
role?: string;
|
|
529
|
+
[key: string]: unknown;
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Context passed to API operations.
|
|
533
|
+
*/
|
|
534
|
+
interface MomentumAPIContext {
|
|
535
|
+
user?: UserContext;
|
|
536
|
+
depth?: number;
|
|
537
|
+
showHiddenFields?: boolean;
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Options for find operations.
|
|
541
|
+
*/
|
|
542
|
+
interface FindOptions {
|
|
543
|
+
where?: Record<string, unknown>;
|
|
544
|
+
sort?: string;
|
|
545
|
+
limit?: number;
|
|
546
|
+
page?: number;
|
|
547
|
+
depth?: number;
|
|
548
|
+
/** Enable TransferState caching for SSR hydration (default: true) */
|
|
549
|
+
transfer?: boolean;
|
|
550
|
+
/** Include soft-deleted documents in results. @default false */
|
|
551
|
+
withDeleted?: boolean;
|
|
552
|
+
/** Only return soft-deleted documents. @default false */
|
|
553
|
+
onlyDeleted?: boolean;
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Options for findById operations.
|
|
557
|
+
*/
|
|
558
|
+
interface FindByIdOptions {
|
|
559
|
+
depth?: number;
|
|
560
|
+
/** Enable TransferState caching for SSR hydration (default: true) */
|
|
561
|
+
transfer?: boolean;
|
|
562
|
+
/** Include soft-deleted documents. @default false */
|
|
563
|
+
withDeleted?: boolean;
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Result of a find operation with pagination info.
|
|
567
|
+
*/
|
|
568
|
+
interface FindResult<T> {
|
|
569
|
+
docs: T[];
|
|
570
|
+
totalDocs: number;
|
|
571
|
+
totalPages: number;
|
|
572
|
+
page: number;
|
|
573
|
+
limit: number;
|
|
574
|
+
hasNextPage: boolean;
|
|
575
|
+
hasPrevPage: boolean;
|
|
576
|
+
nextPage?: number;
|
|
577
|
+
prevPage?: number;
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Result of a delete operation.
|
|
581
|
+
*/
|
|
582
|
+
interface DeleteResult {
|
|
583
|
+
id: string;
|
|
584
|
+
deleted: boolean;
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Server-side Momentum API instance.
|
|
588
|
+
* Provided by Express during SSR via provideMomentumAPI().
|
|
589
|
+
* Must be explicitly provided during SSR - not available in browser.
|
|
590
|
+
*/
|
|
591
|
+
declare const MOMENTUM_API: InjectionToken<MomentumAPIServer>;
|
|
592
|
+
/**
|
|
593
|
+
* User context for the current request.
|
|
594
|
+
* Provided by Express during SSR.
|
|
595
|
+
*/
|
|
596
|
+
declare const MOMENTUM_API_CONTEXT: InjectionToken<MomentumAPIContext>;
|
|
597
|
+
/**
|
|
598
|
+
* Creates providers for Momentum API during SSR.
|
|
599
|
+
* Use this in your server.ts when calling angularApp.handle().
|
|
600
|
+
*
|
|
601
|
+
* @example
|
|
602
|
+
* ```typescript
|
|
603
|
+
* import { provideMomentumAPI } from '@momentumcms/admin';
|
|
604
|
+
*
|
|
605
|
+
* angularApp.handle(req, {
|
|
606
|
+
* providers: provideMomentumAPI(getMomentumAPI(), { user: req.user }),
|
|
607
|
+
* });
|
|
608
|
+
* ```
|
|
609
|
+
*/
|
|
610
|
+
declare function provideMomentumAPI(api: MomentumAPIServer, context?: MomentumAPIContext): Provider[];
|
|
611
|
+
/**
|
|
612
|
+
* Server-side API interface (from @momentumcms/server-core).
|
|
613
|
+
* This mirrors the MomentumAPI interface for type safety.
|
|
614
|
+
*/
|
|
615
|
+
interface MomentumAPIServer {
|
|
616
|
+
collection<T = Record<string, unknown>>(slug: string): CollectionOperationsServer<T>;
|
|
617
|
+
global<T = Record<string, unknown>>(slug: string): GlobalOperationsServer<T>;
|
|
618
|
+
getConfig(): unknown;
|
|
619
|
+
setContext(ctx: MomentumAPIContext): MomentumAPIServer;
|
|
620
|
+
getContext(): MomentumAPIContext;
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Server-side collection operations.
|
|
624
|
+
*/
|
|
625
|
+
interface CollectionOperationsServer<T = Record<string, unknown>> {
|
|
626
|
+
find(options?: FindOptions): Promise<FindResult<T>>;
|
|
627
|
+
findById(id: string, options?: {
|
|
628
|
+
depth?: number;
|
|
629
|
+
withDeleted?: boolean;
|
|
630
|
+
}): Promise<T | null>;
|
|
631
|
+
create(data: Partial<T>): Promise<T>;
|
|
632
|
+
update(id: string, data: Partial<T>): Promise<T>;
|
|
633
|
+
delete(id: string): Promise<DeleteResult>;
|
|
634
|
+
forceDelete(id: string): Promise<DeleteResult>;
|
|
635
|
+
restore(id: string): Promise<T>;
|
|
636
|
+
count(where?: Record<string, unknown>, options?: {
|
|
637
|
+
withDeleted?: boolean;
|
|
638
|
+
}): Promise<number>;
|
|
639
|
+
batchCreate(items: Partial<T>[]): Promise<T[]>;
|
|
640
|
+
batchUpdate(items: {
|
|
641
|
+
id: string;
|
|
642
|
+
data: Partial<T>;
|
|
643
|
+
}[]): Promise<T[]>;
|
|
644
|
+
batchDelete(ids: string[]): Promise<DeleteResult[]>;
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Server-side global operations.
|
|
648
|
+
*/
|
|
649
|
+
interface GlobalOperationsServer<T = Record<string, unknown>> {
|
|
650
|
+
findOne(options?: {
|
|
651
|
+
depth?: number;
|
|
652
|
+
}): Promise<T>;
|
|
653
|
+
update(data: Partial<T>): Promise<T>;
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Client-side global operations (both Observable and Promise).
|
|
657
|
+
*/
|
|
658
|
+
interface MomentumGlobalAPI<T = Record<string, unknown>> {
|
|
659
|
+
findOne$(options?: {
|
|
660
|
+
depth?: number;
|
|
661
|
+
}): Observable<T>;
|
|
662
|
+
update$(data: Partial<T>): Observable<T>;
|
|
663
|
+
findOne(options?: {
|
|
664
|
+
depth?: number;
|
|
665
|
+
}): Promise<T>;
|
|
666
|
+
update(data: Partial<T>): Promise<T>;
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Unified client API interface.
|
|
670
|
+
* Works identically on both server (SSR) and browser.
|
|
671
|
+
*/
|
|
672
|
+
interface MomentumClientAPI {
|
|
673
|
+
/**
|
|
674
|
+
* Get operations for a specific collection.
|
|
675
|
+
*/
|
|
676
|
+
collection<T = Record<string, unknown>>(slug: string): MomentumCollectionAPI<T>;
|
|
677
|
+
/**
|
|
678
|
+
* Get operations for a global (singleton document).
|
|
679
|
+
*/
|
|
680
|
+
global<T = Record<string, unknown>>(slug: string): MomentumGlobalAPI<T>;
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Collection operations with both Observable and Promise methods.
|
|
684
|
+
*/
|
|
685
|
+
interface MomentumCollectionAPI<T = Record<string, unknown>> {
|
|
686
|
+
find$(options?: FindOptions): Observable<FindResult<T>>;
|
|
687
|
+
findById$(id: string, options?: FindByIdOptions): Observable<T | null>;
|
|
688
|
+
create$(data: Partial<T>): Observable<T>;
|
|
689
|
+
update$(id: string, data: Partial<T>): Observable<T>;
|
|
690
|
+
delete$(id: string): Observable<DeleteResult>;
|
|
691
|
+
forceDelete$(id: string): Observable<DeleteResult>;
|
|
692
|
+
restore$(id: string): Observable<T>;
|
|
693
|
+
batchCreate$(items: Partial<T>[]): Observable<T[]>;
|
|
694
|
+
batchUpdate$(items: {
|
|
695
|
+
id: string;
|
|
696
|
+
data: Partial<T>;
|
|
697
|
+
}[]): Observable<T[]>;
|
|
698
|
+
batchDelete$(ids: string[]): Observable<DeleteResult[]>;
|
|
699
|
+
find(options?: FindOptions): Promise<FindResult<T>>;
|
|
700
|
+
findById(id: string, options?: FindByIdOptions): Promise<T | null>;
|
|
701
|
+
create(data: Partial<T>): Promise<T>;
|
|
702
|
+
update(id: string, data: Partial<T>): Promise<T>;
|
|
703
|
+
delete(id: string): Promise<DeleteResult>;
|
|
704
|
+
forceDelete(id: string): Promise<DeleteResult>;
|
|
705
|
+
restore(id: string): Promise<T>;
|
|
706
|
+
batchCreate(items: Partial<T>[]): Promise<T[]>;
|
|
707
|
+
batchUpdate(items: {
|
|
708
|
+
id: string;
|
|
709
|
+
data: Partial<T>;
|
|
710
|
+
}[]): Promise<T[]>;
|
|
711
|
+
batchDelete(ids: string[]): Promise<DeleteResult[]>;
|
|
712
|
+
findSignal(options?: FindOptions): Signal<FindResult<T> | undefined>;
|
|
713
|
+
findByIdSignal(id: string, options?: FindByIdOptions): Signal<T | null | undefined>;
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Typed find options with where clause type.
|
|
717
|
+
*/
|
|
718
|
+
interface TypedFindOptions<TWhere> {
|
|
719
|
+
where?: TWhere;
|
|
720
|
+
sort?: string;
|
|
721
|
+
limit?: number;
|
|
722
|
+
page?: number;
|
|
723
|
+
depth?: number;
|
|
724
|
+
/** Enable TransferState caching for SSR hydration */
|
|
725
|
+
transfer?: boolean;
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Typed findById options.
|
|
729
|
+
*/
|
|
730
|
+
interface TypedFindByIdOptions {
|
|
731
|
+
depth?: number;
|
|
732
|
+
/** Enable TransferState caching for SSR hydration */
|
|
733
|
+
transfer?: boolean;
|
|
734
|
+
/** Include soft-deleted documents. @default false */
|
|
735
|
+
withDeleted?: boolean;
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Type-safe collection API with typed where clauses.
|
|
739
|
+
*/
|
|
740
|
+
interface TypedCollectionAPI<TDoc, TWhere = Record<string, unknown>> {
|
|
741
|
+
find$(options?: TypedFindOptions<TWhere>): Observable<FindResult<TDoc>>;
|
|
742
|
+
findById$(id: string, options?: TypedFindByIdOptions): Observable<TDoc | null>;
|
|
743
|
+
create$(data: Partial<TDoc>): Observable<TDoc>;
|
|
744
|
+
update$(id: string, data: Partial<TDoc>): Observable<TDoc>;
|
|
745
|
+
delete$(id: string): Observable<DeleteResult>;
|
|
746
|
+
find(options?: TypedFindOptions<TWhere>): Promise<FindResult<TDoc>>;
|
|
747
|
+
findById(id: string, options?: TypedFindByIdOptions): Promise<TDoc | null>;
|
|
748
|
+
create(data: Partial<TDoc>): Promise<TDoc>;
|
|
749
|
+
update(id: string, data: Partial<TDoc>): Promise<TDoc>;
|
|
750
|
+
delete(id: string): Promise<DeleteResult>;
|
|
751
|
+
findSignal(options?: TypedFindOptions<TWhere>): Signal<FindResult<TDoc> | undefined>;
|
|
752
|
+
findByIdSignal(id: string, options?: TypedFindByIdOptions): Signal<TDoc | null | undefined>;
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* Type-safe API interface with direct property access.
|
|
756
|
+
* Maps collection slugs to typed collection APIs.
|
|
757
|
+
*
|
|
758
|
+
* @example
|
|
759
|
+
* ```typescript
|
|
760
|
+
* interface TypedMomentumCollections {
|
|
761
|
+
* 'posts': { doc: Post; where: PostsWhereClause };
|
|
762
|
+
* 'users': { doc: User; where: UsersWhereClause };
|
|
763
|
+
* }
|
|
764
|
+
*
|
|
765
|
+
* const api = injectTypedMomentumAPI<TypedMomentumCollections>();
|
|
766
|
+
* const posts = await api.posts.find(); // posts.docs is Post[]
|
|
767
|
+
* const users = await api.collection('users').find(); // 'users' autocompletes
|
|
768
|
+
* ```
|
|
769
|
+
*/
|
|
770
|
+
type TypedMomentumClientAPI<TCollections extends {
|
|
771
|
+
[key: string]: {
|
|
772
|
+
doc: unknown;
|
|
773
|
+
where?: unknown;
|
|
774
|
+
};
|
|
775
|
+
}> = {
|
|
776
|
+
readonly [K in keyof TCollections]: TypedCollectionAPI<TCollections[K]['doc'], TCollections[K]['where'] extends undefined ? Record<string, unknown> : TCollections[K]['where']>;
|
|
777
|
+
} & {
|
|
778
|
+
collection<K extends keyof TCollections & string>(slug: K): TypedCollectionAPI<TCollections[K]['doc'], TCollections[K]['where'] extends undefined ? Record<string, unknown> : TCollections[K]['where']>;
|
|
779
|
+
};
|
|
780
|
+
/**
|
|
781
|
+
* Inject the Momentum API with automatic platform detection.
|
|
782
|
+
*
|
|
783
|
+
* - On server (SSR): Uses the direct Momentum API (no HTTP overhead)
|
|
784
|
+
* - On browser: Uses HTTP calls to the REST API
|
|
785
|
+
*
|
|
786
|
+
* @returns The platform-appropriate API implementation
|
|
787
|
+
*
|
|
788
|
+
* @example
|
|
789
|
+
* ```typescript
|
|
790
|
+
* @Component({...})
|
|
791
|
+
* export class PostsComponent {
|
|
792
|
+
* private readonly api = injectMomentumAPI();
|
|
793
|
+
* readonly posts = signal<Post[]>([]);
|
|
794
|
+
*
|
|
795
|
+
* constructor() {
|
|
796
|
+
* // Using observables
|
|
797
|
+
* this.api.collection<Post>('posts').find$().subscribe(result => {
|
|
798
|
+
* this.posts.set(result.docs);
|
|
799
|
+
* });
|
|
800
|
+
*
|
|
801
|
+
* // Or using promises
|
|
802
|
+
* this.loadPosts();
|
|
803
|
+
* }
|
|
804
|
+
*
|
|
805
|
+
* async loadPosts(): Promise<void> {
|
|
806
|
+
* const result = await this.api.collection<Post>('posts').find({ limit: 10 });
|
|
807
|
+
* this.posts.set(result.docs);
|
|
808
|
+
* }
|
|
809
|
+
* }
|
|
810
|
+
* ```
|
|
811
|
+
*/
|
|
812
|
+
declare function injectMomentumAPI(): MomentumClientAPI;
|
|
813
|
+
/**
|
|
814
|
+
* Inject a type-safe Momentum API with full intellisense for collections.
|
|
815
|
+
*
|
|
816
|
+
* @example
|
|
817
|
+
* ```typescript
|
|
818
|
+
* // First, generate types or define them manually
|
|
819
|
+
* interface TypedMomentumCollections {
|
|
820
|
+
* 'posts': { doc: Post; where: PostsWhereClause };
|
|
821
|
+
* 'users': { doc: User; where: UsersWhereClause };
|
|
822
|
+
* }
|
|
823
|
+
*
|
|
824
|
+
* // Then inject the typed API
|
|
825
|
+
* const api = injectTypedMomentumAPI<TypedMomentumCollections>();
|
|
826
|
+
*
|
|
827
|
+
* // Use with full type safety
|
|
828
|
+
* const posts = await api.posts.find(); // posts.docs is Post[]
|
|
829
|
+
* const users = await api.collection('users').find(); // 'users' autocompletes
|
|
830
|
+
*
|
|
831
|
+
* // Typed where clauses
|
|
832
|
+
* const published = await api.posts.find({
|
|
833
|
+
* where: { status: 'published' } // type-checked
|
|
834
|
+
* });
|
|
835
|
+
* ```
|
|
836
|
+
*/
|
|
837
|
+
declare function injectTypedMomentumAPI<TCollections extends {
|
|
838
|
+
[key: string]: {
|
|
839
|
+
doc: unknown;
|
|
840
|
+
where?: unknown;
|
|
841
|
+
};
|
|
842
|
+
}>(): TypedMomentumClientAPI<TCollections>;
|
|
843
|
+
|
|
844
|
+
/**
|
|
845
|
+
* Widget Types for Momentum CMS Admin
|
|
846
|
+
*
|
|
847
|
+
* Common types used across admin widgets.
|
|
848
|
+
*/
|
|
849
|
+
|
|
850
|
+
/**
|
|
851
|
+
* Branding configuration for admin sidebar.
|
|
852
|
+
*/
|
|
853
|
+
interface AdminBranding {
|
|
854
|
+
/** The title displayed in the sidebar header */
|
|
855
|
+
title?: string;
|
|
856
|
+
/** URL to the logo image */
|
|
857
|
+
logo?: string;
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Navigation item in the admin sidebar.
|
|
861
|
+
*/
|
|
862
|
+
interface AdminNavItem {
|
|
863
|
+
/** Display label */
|
|
864
|
+
label: string;
|
|
865
|
+
/** Router link path */
|
|
866
|
+
href: string;
|
|
867
|
+
/** Icon name (for future icon support) */
|
|
868
|
+
icon?: string;
|
|
869
|
+
/** Badge content (count or text) */
|
|
870
|
+
badge?: string | number;
|
|
871
|
+
/** Whether this item is active */
|
|
872
|
+
active?: boolean;
|
|
873
|
+
/** Whether this item is disabled */
|
|
874
|
+
disabled?: boolean;
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* Navigation section with optional grouping.
|
|
878
|
+
*/
|
|
879
|
+
interface AdminNavSection {
|
|
880
|
+
/** Section title (optional) */
|
|
881
|
+
title?: string;
|
|
882
|
+
/** Navigation items in this section */
|
|
883
|
+
items: AdminNavItem[];
|
|
884
|
+
/** Whether the section is collapsible */
|
|
885
|
+
collapsible?: boolean;
|
|
886
|
+
/** Whether the section is expanded (if collapsible) */
|
|
887
|
+
expanded?: boolean;
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* User information for the sidebar user section.
|
|
891
|
+
*/
|
|
892
|
+
interface AdminUser {
|
|
893
|
+
/** User's unique ID */
|
|
894
|
+
id: string | number;
|
|
895
|
+
/** User's full name */
|
|
896
|
+
name: string;
|
|
897
|
+
/** User's email */
|
|
898
|
+
email: string;
|
|
899
|
+
/** User's avatar URL (optional) */
|
|
900
|
+
avatarUrl?: string;
|
|
901
|
+
/** User's role (optional) */
|
|
902
|
+
role?: string;
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* Collection with count information for the dashboard.
|
|
906
|
+
*/
|
|
907
|
+
interface CollectionWithCount {
|
|
908
|
+
/** Collection configuration */
|
|
909
|
+
collection: CollectionConfig;
|
|
910
|
+
/** Number of documents in the collection */
|
|
911
|
+
count: number;
|
|
912
|
+
/** Whether the count is loading */
|
|
913
|
+
loading?: boolean;
|
|
914
|
+
/** Error message if count fetch failed */
|
|
915
|
+
error?: string;
|
|
916
|
+
}
|
|
917
|
+
/**
|
|
918
|
+
* Action that can be performed on an entity.
|
|
919
|
+
*/
|
|
920
|
+
interface EntityAction {
|
|
921
|
+
/** Unique action identifier */
|
|
922
|
+
id: string;
|
|
923
|
+
/** Display label */
|
|
924
|
+
label: string;
|
|
925
|
+
/** Icon name (for future icon support) */
|
|
926
|
+
icon?: string;
|
|
927
|
+
/** Variant for styling (default, destructive) */
|
|
928
|
+
variant?: 'default' | 'destructive';
|
|
929
|
+
/** Whether action is disabled */
|
|
930
|
+
disabled?: boolean;
|
|
931
|
+
/** Whether action requires confirmation */
|
|
932
|
+
requiresConfirmation?: boolean;
|
|
933
|
+
/** Confirmation message (if requiresConfirmation is true) */
|
|
934
|
+
confirmationMessage?: string;
|
|
935
|
+
}
|
|
936
|
+
/**
|
|
937
|
+
* Generic entity record from the API.
|
|
938
|
+
*/
|
|
939
|
+
interface Entity {
|
|
940
|
+
id: string | number;
|
|
941
|
+
createdAt?: string;
|
|
942
|
+
updatedAt?: string;
|
|
943
|
+
[key: string]: unknown;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
/**
|
|
947
|
+
* Result emitted when the entity sheet closes.
|
|
948
|
+
*/
|
|
949
|
+
interface EntitySheetResult {
|
|
950
|
+
/** What happened in the sheet */
|
|
951
|
+
action: 'created' | 'updated' | 'deleted' | 'cancelled';
|
|
952
|
+
/** The entity that was created or updated (undefined on cancel) */
|
|
953
|
+
entity?: Entity;
|
|
954
|
+
/** The collection slug */
|
|
955
|
+
collection: string;
|
|
956
|
+
}
|
|
957
|
+
/** Query parameter keys used by the entity sheet */
|
|
958
|
+
declare const SHEET_QUERY_PARAMS: {
|
|
959
|
+
collection: string;
|
|
960
|
+
entityId: string;
|
|
961
|
+
mode: string;
|
|
962
|
+
callback: string;
|
|
963
|
+
};
|
|
964
|
+
/**
|
|
965
|
+
* Service for opening/closing the entity sheet via query parameters.
|
|
966
|
+
*
|
|
967
|
+
* The sheet is rendered directly in the AdminShell (no named router outlet).
|
|
968
|
+
* Query parameters carry the state: `sheetCollection`, `sheetEntityId`, `sheetMode`, `sheetCallback`.
|
|
969
|
+
* This approach keeps the URL clean and SSR-friendly (no auxiliary route parentheses).
|
|
970
|
+
*
|
|
971
|
+
* Usage:
|
|
972
|
+
* ```typescript
|
|
973
|
+
* const sheetService = inject(EntitySheetService);
|
|
974
|
+
*
|
|
975
|
+
* sheetService.openCreate('authors').subscribe((result) => {
|
|
976
|
+
* if (result.action === 'created') {
|
|
977
|
+
* console.log('Created:', result.entity);
|
|
978
|
+
* }
|
|
979
|
+
* });
|
|
980
|
+
* ```
|
|
981
|
+
*/
|
|
982
|
+
declare class EntitySheetService {
|
|
983
|
+
private readonly router;
|
|
984
|
+
private readonly destroyRef;
|
|
985
|
+
private readonly document;
|
|
986
|
+
/** Pending callback subjects keyed by callback ID */
|
|
987
|
+
private readonly pendingCallbacks;
|
|
988
|
+
/** Handle for the close animation timer (for cancellation on rapid open/close) */
|
|
989
|
+
private closeTimerId;
|
|
990
|
+
/** Element that had focus when the sheet was opened (for focus restoration) */
|
|
991
|
+
private triggerElement;
|
|
992
|
+
/** Whether the sheet is currently open */
|
|
993
|
+
readonly isOpen: _angular_core.WritableSignal<boolean>;
|
|
994
|
+
/** Whether the sheet is playing its close animation */
|
|
995
|
+
readonly isClosing: _angular_core.WritableSignal<boolean>;
|
|
996
|
+
/** Whether the sheet DOM should be present (open or animating closed) */
|
|
997
|
+
readonly isVisible: _angular_core.Signal<boolean>;
|
|
998
|
+
/**
|
|
999
|
+
* Open the entity sheet to create a new entity.
|
|
1000
|
+
*/
|
|
1001
|
+
openCreate(collection: string): Observable<EntitySheetResult>;
|
|
1002
|
+
/**
|
|
1003
|
+
* Open the entity sheet to edit an existing entity.
|
|
1004
|
+
*/
|
|
1005
|
+
openEdit(collection: string, entityId: string): Observable<EntitySheetResult>;
|
|
1006
|
+
/**
|
|
1007
|
+
* Open the entity sheet to view an existing entity.
|
|
1008
|
+
*/
|
|
1009
|
+
openView(collection: string, entityId: string): Observable<EntitySheetResult>;
|
|
1010
|
+
/**
|
|
1011
|
+
* Close the sheet with a slide-out animation, then emit the result to any pending callback.
|
|
1012
|
+
*/
|
|
1013
|
+
close(result?: EntitySheetResult): void;
|
|
1014
|
+
/**
|
|
1015
|
+
* Restore sheet state from query params and sync on navigation.
|
|
1016
|
+
* Handles page refresh and browser back/forward.
|
|
1017
|
+
* Called by AdminShellComponent on browser init.
|
|
1018
|
+
*/
|
|
1019
|
+
initFromQueryParams(): void;
|
|
1020
|
+
private syncIsOpenFromUrl;
|
|
1021
|
+
private openSheet;
|
|
1022
|
+
private cancelCloseAnimation;
|
|
1023
|
+
/** Complete and remove all pending callback subjects (prevents memory leaks) */
|
|
1024
|
+
private cleanupAllPendingCallbacks;
|
|
1025
|
+
/** Return focus to the element that triggered the sheet open */
|
|
1026
|
+
private restoreFocus;
|
|
1027
|
+
private getCurrentCallbackId;
|
|
1028
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<EntitySheetService, never>;
|
|
1029
|
+
static ɵprov: _angular_core.ɵɵInjectableDeclaration<EntitySheetService>;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
/**
|
|
1033
|
+
* Document status for versioned collections.
|
|
1034
|
+
*/
|
|
1035
|
+
type DocumentStatus = 'draft' | 'published';
|
|
1036
|
+
/**
|
|
1037
|
+
* Version query options.
|
|
1038
|
+
*/
|
|
1039
|
+
interface VersionFindOptions {
|
|
1040
|
+
limit?: number;
|
|
1041
|
+
page?: number;
|
|
1042
|
+
includeAutosave?: boolean;
|
|
1043
|
+
status?: DocumentStatus;
|
|
1044
|
+
}
|
|
1045
|
+
/**
|
|
1046
|
+
* Parsed document version with typed data.
|
|
1047
|
+
*/
|
|
1048
|
+
interface DocumentVersionParsed<T = Record<string, unknown>> {
|
|
1049
|
+
id: string;
|
|
1050
|
+
parent: string;
|
|
1051
|
+
version: T;
|
|
1052
|
+
_status: DocumentStatus;
|
|
1053
|
+
autosave: boolean;
|
|
1054
|
+
publishedAt?: string;
|
|
1055
|
+
createdAt: string;
|
|
1056
|
+
updatedAt: string;
|
|
1057
|
+
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Result of a version query with pagination.
|
|
1060
|
+
*/
|
|
1061
|
+
interface VersionQueryResult<T = Record<string, unknown>> {
|
|
1062
|
+
docs: DocumentVersionParsed<T>[];
|
|
1063
|
+
totalDocs: number;
|
|
1064
|
+
totalPages: number;
|
|
1065
|
+
page: number;
|
|
1066
|
+
limit: number;
|
|
1067
|
+
hasNextPage: boolean;
|
|
1068
|
+
hasPrevPage: boolean;
|
|
1069
|
+
}
|
|
1070
|
+
/**
|
|
1071
|
+
* Options for restoring a version.
|
|
1072
|
+
*/
|
|
1073
|
+
interface RestoreVersionOptions {
|
|
1074
|
+
versionId: string;
|
|
1075
|
+
publish?: boolean;
|
|
1076
|
+
}
|
|
1077
|
+
/**
|
|
1078
|
+
* Result of a restore operation.
|
|
1079
|
+
*/
|
|
1080
|
+
interface RestoreResult<T = Record<string, unknown>> {
|
|
1081
|
+
doc: T;
|
|
1082
|
+
message: string;
|
|
1083
|
+
}
|
|
1084
|
+
/**
|
|
1085
|
+
* Result of a publish/unpublish operation.
|
|
1086
|
+
*/
|
|
1087
|
+
interface PublishResult<T = Record<string, unknown>> {
|
|
1088
|
+
doc: T;
|
|
1089
|
+
message: string;
|
|
1090
|
+
}
|
|
1091
|
+
/**
|
|
1092
|
+
* Result of a draft save operation.
|
|
1093
|
+
*/
|
|
1094
|
+
interface DraftSaveResult<T = Record<string, unknown>> {
|
|
1095
|
+
version: DocumentVersionParsed<T>;
|
|
1096
|
+
message: string;
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Result of a status query.
|
|
1100
|
+
*/
|
|
1101
|
+
interface StatusResult {
|
|
1102
|
+
status: DocumentStatus;
|
|
1103
|
+
}
|
|
1104
|
+
/**
|
|
1105
|
+
* A single field difference between two versions.
|
|
1106
|
+
*/
|
|
1107
|
+
interface VersionFieldDiff {
|
|
1108
|
+
field: string;
|
|
1109
|
+
oldValue: unknown;
|
|
1110
|
+
newValue: unknown;
|
|
1111
|
+
}
|
|
1112
|
+
/**
|
|
1113
|
+
* Service for managing document versions in the admin UI.
|
|
1114
|
+
*
|
|
1115
|
+
* @example
|
|
1116
|
+
* ```typescript
|
|
1117
|
+
* @Component({...})
|
|
1118
|
+
* export class VersionHistoryComponent {
|
|
1119
|
+
* private readonly versionService = inject(VersionService);
|
|
1120
|
+
*
|
|
1121
|
+
* readonly versions = signal<DocumentVersionParsed[]>([]);
|
|
1122
|
+
* readonly isLoading = signal(false);
|
|
1123
|
+
*
|
|
1124
|
+
* async loadVersions(collection: string, docId: string): Promise<void> {
|
|
1125
|
+
* this.isLoading.set(true);
|
|
1126
|
+
* const result = await this.versionService.findVersions(collection, docId);
|
|
1127
|
+
* this.versions.set(result.docs);
|
|
1128
|
+
* this.isLoading.set(false);
|
|
1129
|
+
* }
|
|
1130
|
+
* }
|
|
1131
|
+
* ```
|
|
1132
|
+
*/
|
|
1133
|
+
declare class VersionService {
|
|
1134
|
+
private readonly http;
|
|
1135
|
+
/** Loading state signal */
|
|
1136
|
+
readonly isLoading: _angular_core.WritableSignal<boolean>;
|
|
1137
|
+
/** Error state signal */
|
|
1138
|
+
readonly error: _angular_core.WritableSignal<string | null>;
|
|
1139
|
+
/** Last saved timestamp signal */
|
|
1140
|
+
readonly lastSaved: _angular_core.WritableSignal<Date | null>;
|
|
1141
|
+
/**
|
|
1142
|
+
* Find all versions for a document.
|
|
1143
|
+
*/
|
|
1144
|
+
findVersions$<T = Record<string, unknown>>(collection: string, docId: string, options?: VersionFindOptions): Observable<VersionQueryResult<T>>;
|
|
1145
|
+
/**
|
|
1146
|
+
* Find all versions for a document (Promise version).
|
|
1147
|
+
*/
|
|
1148
|
+
findVersions<T = Record<string, unknown>>(collection: string, docId: string, options?: VersionFindOptions): Promise<VersionQueryResult<T>>;
|
|
1149
|
+
/**
|
|
1150
|
+
* Find a specific version by ID.
|
|
1151
|
+
*/
|
|
1152
|
+
findVersionById$<T = Record<string, unknown>>(collection: string, docId: string, versionId: string): Observable<DocumentVersionParsed<T>>;
|
|
1153
|
+
/**
|
|
1154
|
+
* Find a specific version by ID (Promise version).
|
|
1155
|
+
*/
|
|
1156
|
+
findVersionById<T = Record<string, unknown>>(collection: string, docId: string, versionId: string): Promise<DocumentVersionParsed<T>>;
|
|
1157
|
+
/**
|
|
1158
|
+
* Restore a document to a previous version.
|
|
1159
|
+
*/
|
|
1160
|
+
restore$<T = Record<string, unknown>>(collection: string, docId: string, options: RestoreVersionOptions): Observable<RestoreResult<T>>;
|
|
1161
|
+
/**
|
|
1162
|
+
* Restore a document to a previous version (Promise version).
|
|
1163
|
+
*/
|
|
1164
|
+
restore<T = Record<string, unknown>>(collection: string, docId: string, options: RestoreVersionOptions): Promise<RestoreResult<T>>;
|
|
1165
|
+
/**
|
|
1166
|
+
* Publish a document.
|
|
1167
|
+
*/
|
|
1168
|
+
publish$<T = Record<string, unknown>>(collection: string, docId: string): Observable<PublishResult<T>>;
|
|
1169
|
+
/**
|
|
1170
|
+
* Publish a document (Promise version).
|
|
1171
|
+
*/
|
|
1172
|
+
publish<T = Record<string, unknown>>(collection: string, docId: string): Promise<PublishResult<T>>;
|
|
1173
|
+
/**
|
|
1174
|
+
* Unpublish a document.
|
|
1175
|
+
*/
|
|
1176
|
+
unpublish$<T = Record<string, unknown>>(collection: string, docId: string): Observable<PublishResult<T>>;
|
|
1177
|
+
/**
|
|
1178
|
+
* Unpublish a document (Promise version).
|
|
1179
|
+
*/
|
|
1180
|
+
unpublish<T = Record<string, unknown>>(collection: string, docId: string): Promise<PublishResult<T>>;
|
|
1181
|
+
/**
|
|
1182
|
+
* Save a draft version (autosave).
|
|
1183
|
+
*/
|
|
1184
|
+
saveDraft$<T = Record<string, unknown>>(collection: string, docId: string, data: Partial<T>): Observable<DraftSaveResult<T>>;
|
|
1185
|
+
/**
|
|
1186
|
+
* Save a draft version (Promise version).
|
|
1187
|
+
*/
|
|
1188
|
+
saveDraft<T = Record<string, unknown>>(collection: string, docId: string, data: Partial<T>): Promise<DraftSaveResult<T>>;
|
|
1189
|
+
/**
|
|
1190
|
+
* Get the current status of a document.
|
|
1191
|
+
*/
|
|
1192
|
+
getStatus$(collection: string, docId: string): Observable<DocumentStatus>;
|
|
1193
|
+
/**
|
|
1194
|
+
* Get the current status of a document (Promise version).
|
|
1195
|
+
*/
|
|
1196
|
+
getStatus(collection: string, docId: string): Promise<DocumentStatus>;
|
|
1197
|
+
/**
|
|
1198
|
+
* Compare two versions and return field-level differences.
|
|
1199
|
+
*/
|
|
1200
|
+
compareVersions$(collection: string, docId: string, versionId1: string, versionId2: string): Observable<VersionFieldDiff[]>;
|
|
1201
|
+
/**
|
|
1202
|
+
* Compare two versions and return field-level differences (Promise version).
|
|
1203
|
+
*/
|
|
1204
|
+
compareVersions(collection: string, docId: string, versionId1: string, versionId2: string): Promise<VersionFieldDiff[]>;
|
|
1205
|
+
/**
|
|
1206
|
+
* Build the API URL for version operations.
|
|
1207
|
+
*/
|
|
1208
|
+
private buildUrl;
|
|
1209
|
+
/**
|
|
1210
|
+
* Build query params from options.
|
|
1211
|
+
*/
|
|
1212
|
+
private buildParams;
|
|
1213
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<VersionService, never>;
|
|
1214
|
+
static ɵprov: _angular_core.ɵɵInjectableDeclaration<VersionService>;
|
|
1215
|
+
}
|
|
1216
|
+
/**
|
|
1217
|
+
* Inject the Version Service.
|
|
1218
|
+
*
|
|
1219
|
+
* @example
|
|
1220
|
+
* ```typescript
|
|
1221
|
+
* @Component({...})
|
|
1222
|
+
* export class VersionHistoryComponent {
|
|
1223
|
+
* private readonly versionService = injectVersionService();
|
|
1224
|
+
*
|
|
1225
|
+
* async restoreVersion(versionId: string): Promise<void> {
|
|
1226
|
+
* await this.versionService.restore(this.collection, this.docId, {
|
|
1227
|
+
* versionId,
|
|
1228
|
+
* publish: false,
|
|
1229
|
+
* });
|
|
1230
|
+
* }
|
|
1231
|
+
* }
|
|
1232
|
+
* ```
|
|
1233
|
+
*/
|
|
1234
|
+
declare function injectVersionService(): VersionService;
|
|
1235
|
+
|
|
1236
|
+
/**
|
|
1237
|
+
* Auth guard that protects routes requiring authentication.
|
|
1238
|
+
*
|
|
1239
|
+
* Redirects to:
|
|
1240
|
+
* - /admin/setup if no users exist (first-time setup)
|
|
1241
|
+
* - /admin/login if user is not authenticated
|
|
1242
|
+
*
|
|
1243
|
+
* During SSR, the guard allows access and defers redirect decisions to
|
|
1244
|
+
* client-side hydration. The session resolver middleware provides user
|
|
1245
|
+
* context for SSR rendering via MOMENTUM_API_CONTEXT/injectUser(), but
|
|
1246
|
+
* guards return true during SSR to avoid hydration conflicts.
|
|
1247
|
+
*
|
|
1248
|
+
* @example
|
|
1249
|
+
* ```typescript
|
|
1250
|
+
* const routes: Routes = [
|
|
1251
|
+
* {
|
|
1252
|
+
* path: 'dashboard',
|
|
1253
|
+
* component: DashboardPage,
|
|
1254
|
+
* canActivate: [authGuard],
|
|
1255
|
+
* },
|
|
1256
|
+
* ];
|
|
1257
|
+
* ```
|
|
1258
|
+
*/
|
|
1259
|
+
declare const authGuard: CanActivateFn;
|
|
1260
|
+
/**
|
|
1261
|
+
* Admin guard that requires admin role.
|
|
1262
|
+
*
|
|
1263
|
+
* Redirects to:
|
|
1264
|
+
* - /admin/setup if no users exist
|
|
1265
|
+
* - /admin/login if user is not authenticated
|
|
1266
|
+
* - /admin if user is not an admin
|
|
1267
|
+
*
|
|
1268
|
+
* During SSR, the guard allows access and defers redirect decisions to
|
|
1269
|
+
* client-side hydration to avoid hydration conflicts.
|
|
1270
|
+
*
|
|
1271
|
+
* @example
|
|
1272
|
+
* ```typescript
|
|
1273
|
+
* const routes: Routes = [
|
|
1274
|
+
* {
|
|
1275
|
+
* path: 'settings',
|
|
1276
|
+
* component: SettingsPage,
|
|
1277
|
+
* canActivate: [adminGuard],
|
|
1278
|
+
* },
|
|
1279
|
+
* ];
|
|
1280
|
+
* ```
|
|
1281
|
+
*/
|
|
1282
|
+
declare const adminGuard: CanActivateFn;
|
|
1283
|
+
|
|
1284
|
+
/**
|
|
1285
|
+
* Guest guard that allows access only to unauthenticated users.
|
|
1286
|
+
*
|
|
1287
|
+
* Redirects to:
|
|
1288
|
+
* - /admin/setup if no users exist (first-time setup)
|
|
1289
|
+
* - /admin if user is already authenticated
|
|
1290
|
+
*
|
|
1291
|
+
* During SSR, the guard allows access and defers redirect decisions to
|
|
1292
|
+
* client-side hydration to avoid hydration conflicts.
|
|
1293
|
+
*
|
|
1294
|
+
* Use this guard for login/register pages.
|
|
1295
|
+
*
|
|
1296
|
+
* @example
|
|
1297
|
+
* ```typescript
|
|
1298
|
+
* const routes: Routes = [
|
|
1299
|
+
* {
|
|
1300
|
+
* path: 'login',
|
|
1301
|
+
* component: LoginPage,
|
|
1302
|
+
* canActivate: [guestGuard],
|
|
1303
|
+
* },
|
|
1304
|
+
* ];
|
|
1305
|
+
* ```
|
|
1306
|
+
*/
|
|
1307
|
+
declare const guestGuard: CanActivateFn;
|
|
1308
|
+
|
|
1309
|
+
/**
|
|
1310
|
+
* Setup guard that allows access only when no users exist.
|
|
1311
|
+
*
|
|
1312
|
+
* Redirects to:
|
|
1313
|
+
* - /admin/login if users already exist
|
|
1314
|
+
*
|
|
1315
|
+
* During SSR, the guard allows access and defers redirect decisions to
|
|
1316
|
+
* client-side hydration to avoid hydration conflicts.
|
|
1317
|
+
*
|
|
1318
|
+
* Use this guard for the initial setup/first user creation page.
|
|
1319
|
+
*
|
|
1320
|
+
* @example
|
|
1321
|
+
* ```typescript
|
|
1322
|
+
* const routes: Routes = [
|
|
1323
|
+
* {
|
|
1324
|
+
* path: 'setup',
|
|
1325
|
+
* component: SetupPage,
|
|
1326
|
+
* canActivate: [setupGuard],
|
|
1327
|
+
* },
|
|
1328
|
+
* ];
|
|
1329
|
+
* ```
|
|
1330
|
+
*/
|
|
1331
|
+
declare const setupGuard: CanActivateFn;
|
|
1332
|
+
|
|
1333
|
+
/**
|
|
1334
|
+
* Collection access guard that checks if the user can access a specific collection.
|
|
1335
|
+
*
|
|
1336
|
+
* This guard should be used on routes that have a :slug parameter in the path.
|
|
1337
|
+
* It checks the collection-level `admin` access function to determine if the
|
|
1338
|
+
* user is allowed to view the collection in the admin panel.
|
|
1339
|
+
*
|
|
1340
|
+
* Redirects to:
|
|
1341
|
+
* - /admin/setup if no users exist
|
|
1342
|
+
* - /admin/login if user is not authenticated
|
|
1343
|
+
* - /admin if user cannot access the collection
|
|
1344
|
+
*
|
|
1345
|
+
* Note: During SSR, the guard allows access and defers checks to client-side
|
|
1346
|
+
* hydration. This is necessary because SSR doesn't have access to browser cookies.
|
|
1347
|
+
*
|
|
1348
|
+
* @example
|
|
1349
|
+
* ```typescript
|
|
1350
|
+
* const routes: Routes = [
|
|
1351
|
+
* {
|
|
1352
|
+
* path: 'collections/:slug',
|
|
1353
|
+
* component: CollectionListPage,
|
|
1354
|
+
* canActivate: [authGuard, collectionAccessGuard],
|
|
1355
|
+
* },
|
|
1356
|
+
* ];
|
|
1357
|
+
* ```
|
|
1358
|
+
*/
|
|
1359
|
+
declare const collectionAccessGuard: CanActivateFn;
|
|
1360
|
+
|
|
1361
|
+
/**
|
|
1362
|
+
* Interface for components that track unsaved changes.
|
|
1363
|
+
*/
|
|
1364
|
+
interface HasUnsavedChanges {
|
|
1365
|
+
hasUnsavedChanges(): boolean;
|
|
1366
|
+
}
|
|
1367
|
+
/**
|
|
1368
|
+
* Route guard that prompts the user before navigating away from a dirty form.
|
|
1369
|
+
*/
|
|
1370
|
+
declare const unsavedChangesGuard: CanDeactivateFn<HasUnsavedChanges>;
|
|
1371
|
+
|
|
1372
|
+
/**
|
|
1373
|
+
* Theme options for the admin UI.
|
|
1374
|
+
*/
|
|
1375
|
+
type McmsTheme = 'light' | 'dark' | 'system';
|
|
1376
|
+
/**
|
|
1377
|
+
* Theme service for Momentum CMS Admin UI.
|
|
1378
|
+
*
|
|
1379
|
+
* Provides reactive theme state with system preference detection,
|
|
1380
|
+
* localStorage persistence, and dark mode toggle.
|
|
1381
|
+
*
|
|
1382
|
+
* Usage:
|
|
1383
|
+
* ```typescript
|
|
1384
|
+
* @Component({...})
|
|
1385
|
+
* export class MyComponent {
|
|
1386
|
+
* private readonly theme = inject(McmsThemeService);
|
|
1387
|
+
*
|
|
1388
|
+
* toggleDarkMode(): void {
|
|
1389
|
+
* this.theme.toggleTheme();
|
|
1390
|
+
* }
|
|
1391
|
+
* }
|
|
1392
|
+
* ```
|
|
1393
|
+
*/
|
|
1394
|
+
declare class McmsThemeService {
|
|
1395
|
+
private readonly platformId;
|
|
1396
|
+
private readonly document;
|
|
1397
|
+
private readonly isBrowser;
|
|
1398
|
+
/**
|
|
1399
|
+
* Current theme setting (light, dark, or system).
|
|
1400
|
+
*/
|
|
1401
|
+
readonly theme: _angular_core.WritableSignal<McmsTheme>;
|
|
1402
|
+
/**
|
|
1403
|
+
* Resolved theme after applying system preference.
|
|
1404
|
+
* Always returns either 'light' or 'dark'.
|
|
1405
|
+
*/
|
|
1406
|
+
readonly resolvedTheme: _angular_core.Signal<"light" | "dark">;
|
|
1407
|
+
/**
|
|
1408
|
+
* Whether the resolved theme is dark.
|
|
1409
|
+
*/
|
|
1410
|
+
readonly isDark: _angular_core.Signal<boolean>;
|
|
1411
|
+
constructor();
|
|
1412
|
+
/**
|
|
1413
|
+
* Sets the theme and persists to localStorage.
|
|
1414
|
+
*/
|
|
1415
|
+
setTheme(theme: McmsTheme): void;
|
|
1416
|
+
/**
|
|
1417
|
+
* Toggles between light and dark themes.
|
|
1418
|
+
* If currently on system, uses the resolved preference as starting point.
|
|
1419
|
+
*/
|
|
1420
|
+
toggleTheme(): void;
|
|
1421
|
+
/**
|
|
1422
|
+
* Loads theme from localStorage or defaults to 'system'.
|
|
1423
|
+
*/
|
|
1424
|
+
private loadTheme;
|
|
1425
|
+
/**
|
|
1426
|
+
* Gets the system color scheme preference.
|
|
1427
|
+
*/
|
|
1428
|
+
private getSystemPreference;
|
|
1429
|
+
/**
|
|
1430
|
+
* Applies theme class to document element.
|
|
1431
|
+
*/
|
|
1432
|
+
private applyThemeToDOM;
|
|
1433
|
+
/**
|
|
1434
|
+
* Listens for system preference changes when theme is set to 'system'.
|
|
1435
|
+
*/
|
|
1436
|
+
private listenForSystemPreferenceChanges;
|
|
1437
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<McmsThemeService, never>;
|
|
1438
|
+
static ɵprov: _angular_core.ɵɵInjectableDeclaration<McmsThemeService>;
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
/**
|
|
1442
|
+
* User Injection Utilities for Momentum CMS
|
|
1443
|
+
*
|
|
1444
|
+
* Provides type-safe access to the current authenticated user in both
|
|
1445
|
+
* SSR and browser contexts using Angular signals.
|
|
1446
|
+
*
|
|
1447
|
+
* @example
|
|
1448
|
+
* ```typescript
|
|
1449
|
+
* import type { Users } from './types/momentum.generated';
|
|
1450
|
+
* import { injectUser } from '@momentumcms/admin';
|
|
1451
|
+
*
|
|
1452
|
+
* @Component({...})
|
|
1453
|
+
* export class MyComponent {
|
|
1454
|
+
* // Typed user from your generated types
|
|
1455
|
+
* readonly user = injectUser<Users>();
|
|
1456
|
+
*
|
|
1457
|
+
* // Or use convenience functions
|
|
1458
|
+
* readonly isAdmin = injectIsAdmin();
|
|
1459
|
+
* readonly isAuthenticated = injectIsAuthenticated();
|
|
1460
|
+
* }
|
|
1461
|
+
* ```
|
|
1462
|
+
*/
|
|
1463
|
+
|
|
1464
|
+
/**
|
|
1465
|
+
* Base user type that all user types must extend.
|
|
1466
|
+
* Matches the common fields between Better Auth users and Momentum collection users.
|
|
1467
|
+
*/
|
|
1468
|
+
interface BaseUser {
|
|
1469
|
+
id: string | number;
|
|
1470
|
+
email?: string;
|
|
1471
|
+
role?: string;
|
|
1472
|
+
}
|
|
1473
|
+
/**
|
|
1474
|
+
* Inject the current authenticated user with proper typing.
|
|
1475
|
+
*
|
|
1476
|
+
* Works seamlessly in both SSR and browser contexts:
|
|
1477
|
+
* - **SSR**: Returns user from `MOMENTUM_API_CONTEXT` (set by session resolver middleware)
|
|
1478
|
+
* - **Browser**: Returns user from `MomentumAuthService` (fetched via /api/auth/get-session)
|
|
1479
|
+
*
|
|
1480
|
+
* The generic type parameter allows you to use your generated collection types
|
|
1481
|
+
* for full type safety with your user schema.
|
|
1482
|
+
*
|
|
1483
|
+
* @typeParam T - User type extending UserContext (use your generated Users type)
|
|
1484
|
+
* @returns Signal containing the current user or null if not authenticated
|
|
1485
|
+
*
|
|
1486
|
+
* @example
|
|
1487
|
+
* ```typescript
|
|
1488
|
+
* import type { Users } from './types/momentum.generated';
|
|
1489
|
+
*
|
|
1490
|
+
* @Component({...})
|
|
1491
|
+
* export class ProfileComponent {
|
|
1492
|
+
* readonly user = injectUser<Users>();
|
|
1493
|
+
*
|
|
1494
|
+
* constructor() {
|
|
1495
|
+
* effect(() => {
|
|
1496
|
+
* const currentUser = this.user();
|
|
1497
|
+
* if (currentUser) {
|
|
1498
|
+
* console.log('Role:', currentUser.role); // Typed!
|
|
1499
|
+
* }
|
|
1500
|
+
* });
|
|
1501
|
+
* }
|
|
1502
|
+
* }
|
|
1503
|
+
* ```
|
|
1504
|
+
*/
|
|
1505
|
+
declare function injectUser<T extends BaseUser = AuthUser>(): Signal<T | null>;
|
|
1506
|
+
/**
|
|
1507
|
+
* Inject the current user's role.
|
|
1508
|
+
*
|
|
1509
|
+
* Convenience wrapper that returns just the role string.
|
|
1510
|
+
*
|
|
1511
|
+
* @returns Signal containing the user's role or null if not authenticated
|
|
1512
|
+
*
|
|
1513
|
+
* @example
|
|
1514
|
+
* ```typescript
|
|
1515
|
+
* @Component({...})
|
|
1516
|
+
* export class NavComponent {
|
|
1517
|
+
* readonly role = injectUserRole();
|
|
1518
|
+
*
|
|
1519
|
+
* canEdit = computed(() => {
|
|
1520
|
+
* const r = this.role();
|
|
1521
|
+
* return r === 'admin' || r === 'editor';
|
|
1522
|
+
* });
|
|
1523
|
+
* }
|
|
1524
|
+
* ```
|
|
1525
|
+
*/
|
|
1526
|
+
declare function injectUserRole(): Signal<string | null>;
|
|
1527
|
+
/**
|
|
1528
|
+
* Inject whether the current user is authenticated.
|
|
1529
|
+
*
|
|
1530
|
+
* @returns Signal containing true if user is authenticated, false otherwise
|
|
1531
|
+
*
|
|
1532
|
+
* @example
|
|
1533
|
+
* ```typescript
|
|
1534
|
+
* @Component({
|
|
1535
|
+
* template: `
|
|
1536
|
+
* @if (isAuthenticated()) {
|
|
1537
|
+
* <user-menu />
|
|
1538
|
+
* } @else {
|
|
1539
|
+
* <login-button />
|
|
1540
|
+
* }
|
|
1541
|
+
* `
|
|
1542
|
+
* })
|
|
1543
|
+
* export class HeaderComponent {
|
|
1544
|
+
* readonly isAuthenticated = injectIsAuthenticated();
|
|
1545
|
+
* }
|
|
1546
|
+
* ```
|
|
1547
|
+
*/
|
|
1548
|
+
declare function injectIsAuthenticated(): Signal<boolean>;
|
|
1549
|
+
/**
|
|
1550
|
+
* Inject whether the current user is an admin.
|
|
1551
|
+
*
|
|
1552
|
+
* @returns Signal containing true if user has admin role, false otherwise
|
|
1553
|
+
*
|
|
1554
|
+
* @example
|
|
1555
|
+
* ```typescript
|
|
1556
|
+
* @Component({
|
|
1557
|
+
* template: `
|
|
1558
|
+
* @if (isAdmin()) {
|
|
1559
|
+
* <admin-settings />
|
|
1560
|
+
* }
|
|
1561
|
+
* `
|
|
1562
|
+
* })
|
|
1563
|
+
* export class SettingsComponent {
|
|
1564
|
+
* readonly isAdmin = injectIsAdmin();
|
|
1565
|
+
* }
|
|
1566
|
+
* ```
|
|
1567
|
+
*/
|
|
1568
|
+
declare function injectIsAdmin(): Signal<boolean>;
|
|
1569
|
+
/**
|
|
1570
|
+
* Inject whether the current user has a specific role.
|
|
1571
|
+
*
|
|
1572
|
+
* @param role - The role to check for
|
|
1573
|
+
* @returns Signal containing true if user has the specified role
|
|
1574
|
+
*
|
|
1575
|
+
* @example
|
|
1576
|
+
* ```typescript
|
|
1577
|
+
* @Component({...})
|
|
1578
|
+
* export class EditorComponent {
|
|
1579
|
+
* readonly canEdit = injectHasRole('editor');
|
|
1580
|
+
* }
|
|
1581
|
+
* ```
|
|
1582
|
+
*/
|
|
1583
|
+
declare function injectHasRole(role: string): Signal<boolean>;
|
|
1584
|
+
/**
|
|
1585
|
+
* Inject whether the current user has any of the specified roles.
|
|
1586
|
+
*
|
|
1587
|
+
* @param roles - Array of roles to check
|
|
1588
|
+
* @returns Signal containing true if user has any of the specified roles
|
|
1589
|
+
*
|
|
1590
|
+
* @example
|
|
1591
|
+
* ```typescript
|
|
1592
|
+
* @Component({...})
|
|
1593
|
+
* export class ContentComponent {
|
|
1594
|
+
* readonly canManageContent = injectHasAnyRole(['admin', 'editor']);
|
|
1595
|
+
* }
|
|
1596
|
+
* ```
|
|
1597
|
+
*/
|
|
1598
|
+
declare function injectHasAnyRole(roles: string[]): Signal<boolean>;
|
|
1599
|
+
|
|
1600
|
+
/**
|
|
1601
|
+
* Admin Shell Component
|
|
1602
|
+
*
|
|
1603
|
+
* The main layout component for the admin UI.
|
|
1604
|
+
* Provides navigation sidebar and content area.
|
|
1605
|
+
*
|
|
1606
|
+
* Features:
|
|
1607
|
+
* - Responsive sidebar (drawer on mobile, static on desktop)
|
|
1608
|
+
* - Keyboard shortcuts (Cmd+B / Ctrl+B to toggle sidebar)
|
|
1609
|
+
* - Mobile header with hamburger menu
|
|
1610
|
+
* - Entity sheet for inline entity create/edit/view (driven by query params)
|
|
1611
|
+
*/
|
|
1612
|
+
declare class AdminShellComponent implements OnInit {
|
|
1613
|
+
private readonly route;
|
|
1614
|
+
private readonly router;
|
|
1615
|
+
private readonly platformId;
|
|
1616
|
+
private readonly auth;
|
|
1617
|
+
private readonly collectionAccess;
|
|
1618
|
+
private readonly sidebar;
|
|
1619
|
+
readonly entitySheet: EntitySheetService;
|
|
1620
|
+
/** All collections from route data */
|
|
1621
|
+
private readonly allCollections;
|
|
1622
|
+
/** Collections filtered by access permissions and visibility */
|
|
1623
|
+
readonly collections: _angular_core.Signal<CollectionConfig[]>;
|
|
1624
|
+
/** Globals from route data */
|
|
1625
|
+
readonly globals: _angular_core.Signal<GlobalConfig[]>;
|
|
1626
|
+
/** Plugin routes from route data */
|
|
1627
|
+
readonly pluginRoutes: _angular_core.Signal<AdminPluginRoute[]>;
|
|
1628
|
+
/** Branding for sidebar */
|
|
1629
|
+
readonly sidebarBranding: _angular_core.Signal<AdminBranding | undefined>;
|
|
1630
|
+
/** SSR-aware user signal (reads from MOMENTUM_API_CONTEXT during SSR, auth service in browser) */
|
|
1631
|
+
private readonly currentUser;
|
|
1632
|
+
/** User for sidebar */
|
|
1633
|
+
readonly sidebarUser: _angular_core.Signal<AdminUser | null>;
|
|
1634
|
+
ngOnInit(): void;
|
|
1635
|
+
/** Close the sheet when the Escape key is pressed */
|
|
1636
|
+
onEscapeKey(): void;
|
|
1637
|
+
/** Close the sheet when the backdrop is clicked */
|
|
1638
|
+
onSheetBackdropClick(): void;
|
|
1639
|
+
private initializeAuth;
|
|
1640
|
+
onSignOut(): Promise<void>;
|
|
1641
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<AdminShellComponent, never>;
|
|
1642
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<AdminShellComponent, "mcms-admin-shell", never, {}, {}, never, never, true, never>;
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
/**
|
|
1646
|
+
* Forgot Password Form Component
|
|
1647
|
+
*
|
|
1648
|
+
* A reusable form for requesting password reset emails.
|
|
1649
|
+
* Can be embedded in any layout or used with the ForgotPasswordPage wrapper.
|
|
1650
|
+
*
|
|
1651
|
+
* @example
|
|
1652
|
+
* ```html
|
|
1653
|
+
* <mcms-forgot-password-form
|
|
1654
|
+
* (resetRequested)="onResetRequested($event)"
|
|
1655
|
+
* (backToLogin)="navigateToLogin()"
|
|
1656
|
+
* />
|
|
1657
|
+
* ```
|
|
1658
|
+
*/
|
|
1659
|
+
declare class ForgotPasswordFormComponent {
|
|
1660
|
+
private readonly auth;
|
|
1661
|
+
/** Emitted when reset email has been requested */
|
|
1662
|
+
readonly resetRequested: _angular_core.OutputEmitterRef<string>;
|
|
1663
|
+
/** Emitted when user wants to go back to login */
|
|
1664
|
+
readonly backToLogin: _angular_core.OutputEmitterRef<void>;
|
|
1665
|
+
/** Whether the component has been hydrated (SSR → client transition complete) */
|
|
1666
|
+
readonly hydrated: _angular_core.WritableSignal<boolean>;
|
|
1667
|
+
constructor();
|
|
1668
|
+
readonly email: _angular_core.WritableSignal<string>;
|
|
1669
|
+
readonly error: _angular_core.WritableSignal<string | null>;
|
|
1670
|
+
readonly isSubmitting: _angular_core.WritableSignal<boolean>;
|
|
1671
|
+
readonly submitted: _angular_core.WritableSignal<boolean>;
|
|
1672
|
+
readonly touched: _angular_core.WritableSignal<boolean>;
|
|
1673
|
+
readonly emailErrors: _angular_core.Signal<{
|
|
1674
|
+
kind: string;
|
|
1675
|
+
message: string;
|
|
1676
|
+
}[]>;
|
|
1677
|
+
readonly isValid: _angular_core.Signal<boolean>;
|
|
1678
|
+
onSubmit(event: Event): Promise<void>;
|
|
1679
|
+
onBackToLogin(): void;
|
|
1680
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<ForgotPasswordFormComponent, never>;
|
|
1681
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<ForgotPasswordFormComponent, "mcms-forgot-password-form", never, {}, { "resetRequested": "resetRequested"; "backToLogin": "backToLogin"; }, never, never, true, never>;
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
/**
|
|
1685
|
+
* Reset Password Form Component
|
|
1686
|
+
*
|
|
1687
|
+
* A reusable form for resetting password with a token.
|
|
1688
|
+
* Can be embedded in any layout or used with the ResetPasswordPage wrapper.
|
|
1689
|
+
*
|
|
1690
|
+
* @example
|
|
1691
|
+
* ```html
|
|
1692
|
+
* <mcms-reset-password-form
|
|
1693
|
+
* [token]="tokenFromUrl"
|
|
1694
|
+
* (resetComplete)="onResetComplete()"
|
|
1695
|
+
* (resetFailed)="onResetFailed($event)"
|
|
1696
|
+
* />
|
|
1697
|
+
* ```
|
|
1698
|
+
*/
|
|
1699
|
+
declare class ResetPasswordFormComponent {
|
|
1700
|
+
private readonly auth;
|
|
1701
|
+
/** Whether the component has been hydrated (SSR → client transition complete) */
|
|
1702
|
+
readonly hydrated: _angular_core.WritableSignal<boolean>;
|
|
1703
|
+
constructor();
|
|
1704
|
+
/** The reset token from the URL */
|
|
1705
|
+
readonly token: _angular_core.InputSignal<string>;
|
|
1706
|
+
/** Emitted when password reset is complete */
|
|
1707
|
+
readonly resetComplete: _angular_core.OutputEmitterRef<void>;
|
|
1708
|
+
/** Emitted when password reset fails */
|
|
1709
|
+
readonly resetFailed: _angular_core.OutputEmitterRef<string>;
|
|
1710
|
+
/** Emitted when user wants to go to login */
|
|
1711
|
+
readonly goToLogin: _angular_core.OutputEmitterRef<void>;
|
|
1712
|
+
/** Emitted when user wants to request a new reset link */
|
|
1713
|
+
readonly goToForgotPassword: _angular_core.OutputEmitterRef<void>;
|
|
1714
|
+
readonly password: _angular_core.WritableSignal<string>;
|
|
1715
|
+
readonly confirmPassword: _angular_core.WritableSignal<string>;
|
|
1716
|
+
readonly error: _angular_core.WritableSignal<string | null>;
|
|
1717
|
+
readonly isSubmitting: _angular_core.WritableSignal<boolean>;
|
|
1718
|
+
readonly resetSuccess: _angular_core.WritableSignal<boolean>;
|
|
1719
|
+
readonly touched: _angular_core.WritableSignal<boolean>;
|
|
1720
|
+
readonly passwordErrors: _angular_core.Signal<{
|
|
1721
|
+
kind: string;
|
|
1722
|
+
message: string;
|
|
1723
|
+
}[]>;
|
|
1724
|
+
readonly confirmPasswordErrors: _angular_core.Signal<{
|
|
1725
|
+
kind: string;
|
|
1726
|
+
message: string;
|
|
1727
|
+
}[]>;
|
|
1728
|
+
readonly isValid: _angular_core.Signal<boolean>;
|
|
1729
|
+
onSubmit(event: Event): Promise<void>;
|
|
1730
|
+
onGoToLogin(): void;
|
|
1731
|
+
onGoToForgotPassword(): void;
|
|
1732
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<ResetPasswordFormComponent, never>;
|
|
1733
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<ResetPasswordFormComponent, "mcms-reset-password-form", never, { "token": { "alias": "token"; "required": false; "isSignal": true; }; }, { "resetComplete": "resetComplete"; "resetFailed": "resetFailed"; "goToLogin": "goToLogin"; "goToForgotPassword": "goToForgotPassword"; }, never, never, true, never>;
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
/**
|
|
1737
|
+
* Dashboard Page Component
|
|
1738
|
+
*
|
|
1739
|
+
* The main dashboard showing an overview of all collections.
|
|
1740
|
+
*/
|
|
1741
|
+
declare class DashboardPage {
|
|
1742
|
+
private readonly route;
|
|
1743
|
+
private readonly collectionAccess;
|
|
1744
|
+
readonly basePath = "/admin/collections";
|
|
1745
|
+
/** All collections from route data (unfiltered) */
|
|
1746
|
+
private readonly allCollections;
|
|
1747
|
+
/** Collections filtered by visibility and access permissions */
|
|
1748
|
+
readonly collections: _angular_core.Signal<CollectionConfig[]>;
|
|
1749
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<DashboardPage, never>;
|
|
1750
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<DashboardPage, "mcms-dashboard", never, {}, {}, never, never, true, never>;
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
/**
|
|
1754
|
+
* Entity List Widget Types
|
|
1755
|
+
*
|
|
1756
|
+
* Types for the entity list widget that displays collection data.
|
|
1757
|
+
*/
|
|
1758
|
+
|
|
1759
|
+
/**
|
|
1760
|
+
* Column configuration for entity list.
|
|
1761
|
+
* Extends DataTableColumn with collection-specific options.
|
|
1762
|
+
*/
|
|
1763
|
+
interface EntityListColumn<T = Entity> extends DataTableColumn<T> {
|
|
1764
|
+
/** Field type for automatic formatting */
|
|
1765
|
+
type?: 'text' | 'number' | 'date' | 'datetime' | 'boolean' | 'badge' | 'relationship' | 'array' | 'group' | 'json';
|
|
1766
|
+
/** Badge variant for 'badge' type columns */
|
|
1767
|
+
badgeVariant?: 'default' | 'secondary' | 'destructive' | 'outline';
|
|
1768
|
+
/** Badge color mapping for 'badge' type columns */
|
|
1769
|
+
badgeMap?: Record<string, {
|
|
1770
|
+
label: string;
|
|
1771
|
+
variant: 'default' | 'secondary' | 'destructive' | 'outline';
|
|
1772
|
+
}>;
|
|
1773
|
+
}
|
|
1774
|
+
/**
|
|
1775
|
+
* Event emitted when an entity action is triggered.
|
|
1776
|
+
*/
|
|
1777
|
+
interface EntityListActionEvent<T = Entity> {
|
|
1778
|
+
/** The action that was triggered */
|
|
1779
|
+
action: EntityAction;
|
|
1780
|
+
/** The entity the action was triggered on */
|
|
1781
|
+
entity: T;
|
|
1782
|
+
}
|
|
1783
|
+
/**
|
|
1784
|
+
* Event emitted when a bulk action is triggered.
|
|
1785
|
+
*/
|
|
1786
|
+
interface EntityListBulkActionEvent<T = Entity> {
|
|
1787
|
+
/** The action that was triggered */
|
|
1788
|
+
action: EntityAction;
|
|
1789
|
+
/** The entities the action was triggered on */
|
|
1790
|
+
entities: T[];
|
|
1791
|
+
}
|
|
1792
|
+
/**
|
|
1793
|
+
* Result of a find operation.
|
|
1794
|
+
*/
|
|
1795
|
+
interface EntityListFindResult<T = Entity> {
|
|
1796
|
+
docs: T[];
|
|
1797
|
+
totalDocs: number;
|
|
1798
|
+
totalPages: number;
|
|
1799
|
+
page: number;
|
|
1800
|
+
limit: number;
|
|
1801
|
+
hasNextPage: boolean;
|
|
1802
|
+
hasPrevPage: boolean;
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
/**
|
|
1806
|
+
* Entity List Widget
|
|
1807
|
+
*
|
|
1808
|
+
* Displays a collection's data in a data table with search, sorting,
|
|
1809
|
+
* pagination, and actions. Connects to Momentum API for data fetching.
|
|
1810
|
+
*
|
|
1811
|
+
* @example
|
|
1812
|
+
* ```html
|
|
1813
|
+
* <mcms-entity-list
|
|
1814
|
+
* [collection]="postsCollection"
|
|
1815
|
+
* basePath="/admin/collections"
|
|
1816
|
+
* (entityClick)="onEntityClick($event)"
|
|
1817
|
+
* />
|
|
1818
|
+
* ```
|
|
1819
|
+
*/
|
|
1820
|
+
declare class EntityListWidget<T extends Entity = Entity> {
|
|
1821
|
+
private readonly api;
|
|
1822
|
+
private readonly collectionAccess;
|
|
1823
|
+
private readonly feedback;
|
|
1824
|
+
private readonly dialog;
|
|
1825
|
+
private readonly router;
|
|
1826
|
+
private readonly route;
|
|
1827
|
+
/** Template ref for complex cell rendering (group, array, json). */
|
|
1828
|
+
private readonly complexCellTemplate;
|
|
1829
|
+
/** The collection configuration */
|
|
1830
|
+
readonly collection: _angular_core.InputSignal<CollectionConfig>;
|
|
1831
|
+
/** Base path for entity routes */
|
|
1832
|
+
readonly basePath: _angular_core.InputSignal<string>;
|
|
1833
|
+
/** Whether to show the header with title and create button */
|
|
1834
|
+
readonly showHeader: _angular_core.InputSignal<boolean>;
|
|
1835
|
+
/** Whether to show breadcrumbs */
|
|
1836
|
+
readonly showBreadcrumbs: _angular_core.InputSignal<boolean>;
|
|
1837
|
+
/** Custom columns (auto-derived from collection if not provided) */
|
|
1838
|
+
readonly columns: _angular_core.InputSignal<EntityListColumn<T>[]>;
|
|
1839
|
+
/** Row actions for each entity */
|
|
1840
|
+
readonly rowActions: _angular_core.InputSignal<EntityAction[]>;
|
|
1841
|
+
/** Bulk actions for selected entities */
|
|
1842
|
+
readonly bulkActions: _angular_core.InputSignal<EntityAction[]>;
|
|
1843
|
+
/** Custom action buttons displayed in the collection list header */
|
|
1844
|
+
readonly headerActions: _angular_core.InputSignal<{
|
|
1845
|
+
id: string;
|
|
1846
|
+
label: string;
|
|
1847
|
+
endpoint?: string;
|
|
1848
|
+
}[]>;
|
|
1849
|
+
/** Emitted when a header action button is clicked */
|
|
1850
|
+
readonly headerActionClick: _angular_core.OutputEmitterRef<{
|
|
1851
|
+
id: string;
|
|
1852
|
+
label: string;
|
|
1853
|
+
endpoint?: string;
|
|
1854
|
+
}>;
|
|
1855
|
+
/** Whether the table is searchable */
|
|
1856
|
+
readonly searchable: _angular_core.InputSignal<boolean>;
|
|
1857
|
+
/** Search placeholder */
|
|
1858
|
+
readonly searchPlaceholder: _angular_core.InputSignal<string>;
|
|
1859
|
+
/** Fields to search in (auto-derived if not provided) */
|
|
1860
|
+
readonly searchFields: _angular_core.InputSignal<string[]>;
|
|
1861
|
+
/** Whether the table is sortable */
|
|
1862
|
+
readonly sortable: _angular_core.InputSignal<boolean>;
|
|
1863
|
+
/** Whether the table is selectable */
|
|
1864
|
+
readonly selectable: _angular_core.InputSignal<boolean>;
|
|
1865
|
+
/** Whether the table is paginated */
|
|
1866
|
+
readonly paginated: _angular_core.InputSignal<boolean>;
|
|
1867
|
+
/** Default page size */
|
|
1868
|
+
readonly pageSize: _angular_core.InputSignal<number>;
|
|
1869
|
+
/** Empty state title */
|
|
1870
|
+
readonly emptyTitle: _angular_core.InputSignal<string>;
|
|
1871
|
+
/** Empty state description */
|
|
1872
|
+
readonly emptyDescription: _angular_core.InputSignal<string>;
|
|
1873
|
+
/** Outputs */
|
|
1874
|
+
readonly entityClick: _angular_core.OutputEmitterRef<T>;
|
|
1875
|
+
readonly entityAction: _angular_core.OutputEmitterRef<EntityListActionEvent<T>>;
|
|
1876
|
+
readonly bulkAction: _angular_core.OutputEmitterRef<EntityListBulkActionEvent<T>>;
|
|
1877
|
+
readonly dataLoaded: _angular_core.OutputEmitterRef<FindResult<T>>;
|
|
1878
|
+
/** Internal state */
|
|
1879
|
+
readonly entities: _angular_core.WritableSignal<T[]>;
|
|
1880
|
+
readonly totalItems: _angular_core.WritableSignal<number>;
|
|
1881
|
+
readonly loading: _angular_core.WritableSignal<boolean>;
|
|
1882
|
+
readonly error: _angular_core.WritableSignal<string | null>;
|
|
1883
|
+
readonly currentPage: _angular_core.WritableSignal<number>;
|
|
1884
|
+
readonly sort: _angular_core.WritableSignal<DataTableSort<T> | undefined>;
|
|
1885
|
+
readonly selectedEntities: _angular_core.WritableSignal<T[]>;
|
|
1886
|
+
readonly searchQuery: _angular_core.ModelSignal<string>;
|
|
1887
|
+
readonly viewingTrash: _angular_core.WritableSignal<boolean>;
|
|
1888
|
+
/** Whether the collection has soft delete enabled */
|
|
1889
|
+
readonly hasSoftDelete: _angular_core.Signal<boolean>;
|
|
1890
|
+
/** Computed collection label */
|
|
1891
|
+
readonly collectionLabel: _angular_core.Signal<string>;
|
|
1892
|
+
/** Computed collection label singular */
|
|
1893
|
+
readonly collectionLabelSingular: _angular_core.Signal<string>;
|
|
1894
|
+
/** Dashboard path (remove /collections from base path) */
|
|
1895
|
+
readonly dashboardPath: _angular_core.Signal<string>;
|
|
1896
|
+
/** Auto-derive columns from collection fields if not provided */
|
|
1897
|
+
readonly tableColumns: _angular_core.Signal<EntityListColumn<T>[]>;
|
|
1898
|
+
/** Convert row actions to DataTable format */
|
|
1899
|
+
readonly tableRowActions: _angular_core.Signal<DataTableRowAction<T>[]>;
|
|
1900
|
+
/** Track entities by ID */
|
|
1901
|
+
readonly trackById: (item: T) => string | number;
|
|
1902
|
+
/** Can user create entities */
|
|
1903
|
+
readonly canCreate: _angular_core.Signal<boolean>;
|
|
1904
|
+
/** Can user delete entities */
|
|
1905
|
+
readonly canDelete: _angular_core.Signal<boolean>;
|
|
1906
|
+
private previousCollectionSlug;
|
|
1907
|
+
constructor();
|
|
1908
|
+
/**
|
|
1909
|
+
* Load data from API.
|
|
1910
|
+
*/
|
|
1911
|
+
private loadData;
|
|
1912
|
+
/**
|
|
1913
|
+
* Get default search fields from collection.
|
|
1914
|
+
*/
|
|
1915
|
+
private getDefaultSearchFields;
|
|
1916
|
+
/**
|
|
1917
|
+
* Determine if a field should be shown in the list.
|
|
1918
|
+
*/
|
|
1919
|
+
private shouldShowFieldInList;
|
|
1920
|
+
/**
|
|
1921
|
+
* Convert a collection field to a table column.
|
|
1922
|
+
*/
|
|
1923
|
+
private fieldToColumn;
|
|
1924
|
+
/**
|
|
1925
|
+
* Format a value based on its type.
|
|
1926
|
+
*/
|
|
1927
|
+
private formatValue;
|
|
1928
|
+
/**
|
|
1929
|
+
* Generate a brief summary string for complex field values (group, array, json).
|
|
1930
|
+
*/
|
|
1931
|
+
getComplexSummary(value: unknown, type?: string): string;
|
|
1932
|
+
/**
|
|
1933
|
+
* Open a dialog to preview the full data for a complex field.
|
|
1934
|
+
*/
|
|
1935
|
+
openDataPreview(value: unknown, fieldName: string | number | symbol): void;
|
|
1936
|
+
/**
|
|
1937
|
+
* Handle row click.
|
|
1938
|
+
*/
|
|
1939
|
+
onRowClick(entity: T): void;
|
|
1940
|
+
/**
|
|
1941
|
+
* Handle row action.
|
|
1942
|
+
*/
|
|
1943
|
+
onRowAction(event: DataTableRowActionEvent<T>): void;
|
|
1944
|
+
/**
|
|
1945
|
+
* Handle bulk action.
|
|
1946
|
+
*/
|
|
1947
|
+
onBulkAction(action: EntityAction): Promise<void>;
|
|
1948
|
+
/**
|
|
1949
|
+
* Handle search change.
|
|
1950
|
+
*/
|
|
1951
|
+
onSearchChange(query: string): void;
|
|
1952
|
+
/**
|
|
1953
|
+
* Handle page change.
|
|
1954
|
+
*/
|
|
1955
|
+
onPageChange(page: number): void;
|
|
1956
|
+
/**
|
|
1957
|
+
* Handle sort change.
|
|
1958
|
+
*/
|
|
1959
|
+
onSortChange(sortState: DataTableSort<T> | undefined): void;
|
|
1960
|
+
/**
|
|
1961
|
+
* Reload data.
|
|
1962
|
+
*/
|
|
1963
|
+
reload(): void;
|
|
1964
|
+
/**
|
|
1965
|
+
* Toggle between active and trash views.
|
|
1966
|
+
*/
|
|
1967
|
+
toggleTrashView(): void;
|
|
1968
|
+
/**
|
|
1969
|
+
* Handle create button click.
|
|
1970
|
+
*/
|
|
1971
|
+
onCreateClick(): void;
|
|
1972
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<EntityListWidget<any>, never>;
|
|
1973
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<EntityListWidget<any>, "mcms-entity-list", never, { "collection": { "alias": "collection"; "required": true; "isSignal": true; }; "basePath": { "alias": "basePath"; "required": false; "isSignal": true; }; "showHeader": { "alias": "showHeader"; "required": false; "isSignal": true; }; "showBreadcrumbs": { "alias": "showBreadcrumbs"; "required": false; "isSignal": true; }; "columns": { "alias": "columns"; "required": false; "isSignal": true; }; "rowActions": { "alias": "rowActions"; "required": false; "isSignal": true; }; "bulkActions": { "alias": "bulkActions"; "required": false; "isSignal": true; }; "headerActions": { "alias": "headerActions"; "required": false; "isSignal": true; }; "searchable": { "alias": "searchable"; "required": false; "isSignal": true; }; "searchPlaceholder": { "alias": "searchPlaceholder"; "required": false; "isSignal": true; }; "searchFields": { "alias": "searchFields"; "required": false; "isSignal": true; }; "sortable": { "alias": "sortable"; "required": false; "isSignal": true; }; "selectable": { "alias": "selectable"; "required": false; "isSignal": true; }; "paginated": { "alias": "paginated"; "required": false; "isSignal": true; }; "pageSize": { "alias": "pageSize"; "required": false; "isSignal": true; }; "emptyTitle": { "alias": "emptyTitle"; "required": false; "isSignal": true; }; "emptyDescription": { "alias": "emptyDescription"; "required": false; "isSignal": true; }; "searchQuery": { "alias": "searchQuery"; "required": false; "isSignal": true; }; }, { "headerActionClick": "headerActionClick"; "entityClick": "entityClick"; "entityAction": "entityAction"; "bulkAction": "bulkAction"; "dataLoaded": "dataLoaded"; "searchQuery": "searchQueryChange"; }, never, never, true, never>;
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
/**
|
|
1977
|
+
* Collection List Page Component
|
|
1978
|
+
*
|
|
1979
|
+
* Displays a list of documents in a collection using the EntityListWidget.
|
|
1980
|
+
* Supports bulk actions (e.g., bulk delete) via selection checkboxes.
|
|
1981
|
+
*/
|
|
1982
|
+
declare class CollectionListPage {
|
|
1983
|
+
private readonly route;
|
|
1984
|
+
private readonly router;
|
|
1985
|
+
private readonly api;
|
|
1986
|
+
private readonly feedback;
|
|
1987
|
+
private readonly dialog;
|
|
1988
|
+
readonly entityList: _angular_core.Signal<EntityListWidget<Entity> | undefined>;
|
|
1989
|
+
readonly basePath = "/admin/collections";
|
|
1990
|
+
private readonly slug;
|
|
1991
|
+
readonly collection: _angular_core.Signal<CollectionConfig | undefined>;
|
|
1992
|
+
readonly headerActions: _angular_core.Signal<{
|
|
1993
|
+
id: string;
|
|
1994
|
+
label: string;
|
|
1995
|
+
endpoint?: string;
|
|
1996
|
+
}[]>;
|
|
1997
|
+
readonly bulkActions: _angular_core.Signal<EntityAction[]>;
|
|
1998
|
+
onEntityClick(entity: Entity): void;
|
|
1999
|
+
onHeaderAction(action: NonNullable<AdminConfig['headerActions']>[number]): void;
|
|
2000
|
+
onBulkAction(event: EntityListBulkActionEvent): Promise<void>;
|
|
2001
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<CollectionListPage, never>;
|
|
2002
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<CollectionListPage, "mcms-collection-list", never, {}, {}, never, never, true, never>;
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
/**
|
|
2006
|
+
* Collection View Page Component
|
|
2007
|
+
*
|
|
2008
|
+
* Displays a read-only view of a document using the EntityViewWidget.
|
|
2009
|
+
* When preview is enabled, shows a toggleable live preview panel.
|
|
2010
|
+
*/
|
|
2011
|
+
declare class CollectionViewPage {
|
|
2012
|
+
private readonly route;
|
|
2013
|
+
private readonly router;
|
|
2014
|
+
readonly basePath = "/admin/collections";
|
|
2015
|
+
/** Whether the live preview panel is visible */
|
|
2016
|
+
readonly showPreview: _angular_core.WritableSignal<boolean>;
|
|
2017
|
+
/** Reference to the entity view widget to read its entity data */
|
|
2018
|
+
private readonly entityViewRef;
|
|
2019
|
+
private readonly slug;
|
|
2020
|
+
readonly entityId: _angular_core.Signal<string>;
|
|
2021
|
+
readonly collection: _angular_core.Signal<CollectionConfig | undefined>;
|
|
2022
|
+
/** Preview config from collection admin settings */
|
|
2023
|
+
readonly previewConfig: _angular_core.Signal<boolean | ((doc: Record<string, unknown>) => string) | undefined>;
|
|
2024
|
+
/** Entity data from the entity view widget (for live preview) */
|
|
2025
|
+
readonly viewEntityData: _angular_core.Signal<Record<string, unknown>>;
|
|
2026
|
+
onEdit(entity: Entity): void;
|
|
2027
|
+
onDelete(_entity: Entity): void;
|
|
2028
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<CollectionViewPage, never>;
|
|
2029
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<CollectionViewPage, "mcms-collection-view", never, {}, {}, never, never, true, never>;
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
/**
|
|
2033
|
+
* Entity Form Widget Types
|
|
2034
|
+
*
|
|
2035
|
+
* Types for the entity form widget that handles create/edit operations.
|
|
2036
|
+
*/
|
|
2037
|
+
|
|
2038
|
+
/**
|
|
2039
|
+
* Form mode - create, edit, or view.
|
|
2040
|
+
*/
|
|
2041
|
+
type EntityFormMode = 'create' | 'edit' | 'view';
|
|
2042
|
+
/**
|
|
2043
|
+
* Minimal interface for accessing a FieldState from a signal forms FieldTree node.
|
|
2044
|
+
* FieldTree nodes are callable — calling one returns its FieldState.
|
|
2045
|
+
*/
|
|
2046
|
+
interface FieldNodeState {
|
|
2047
|
+
readonly value: WritableSignal<unknown>;
|
|
2048
|
+
readonly errors: Signal<ReadonlyArray<{
|
|
2049
|
+
kind: string;
|
|
2050
|
+
message?: string;
|
|
2051
|
+
}>>;
|
|
2052
|
+
readonly touched: Signal<boolean>;
|
|
2053
|
+
readonly dirty: Signal<boolean>;
|
|
2054
|
+
readonly invalid: Signal<boolean>;
|
|
2055
|
+
markAsTouched(): void;
|
|
2056
|
+
reset(value?: unknown): void;
|
|
2057
|
+
}
|
|
2058
|
+
/**
|
|
2059
|
+
* Safely extract FieldState from an unknown FieldTree node.
|
|
2060
|
+
* FieldTree nodes are callable functions — invoking returns the FieldState.
|
|
2061
|
+
*/
|
|
2062
|
+
declare function getFieldNodeState(formNode: unknown): FieldNodeState | null;
|
|
2063
|
+
/**
|
|
2064
|
+
* Get a sub-node from a FieldTree node by key (field name or array index).
|
|
2065
|
+
*/
|
|
2066
|
+
declare function getSubNode(parentNode: unknown, key: string | number): unknown;
|
|
2067
|
+
/**
|
|
2068
|
+
* Get the best title field name from a collection config.
|
|
2069
|
+
* Checks admin.useAsTitle first, then falls back to 'title' or 'name' fields.
|
|
2070
|
+
*/
|
|
2071
|
+
declare function getTitleField(config: Record<string, unknown>): string;
|
|
2072
|
+
|
|
2073
|
+
/**
|
|
2074
|
+
* Collection Edit Page Component
|
|
2075
|
+
*
|
|
2076
|
+
* Form for creating or editing a document in a collection using EntityFormWidget.
|
|
2077
|
+
* When preview is enabled on the collection, shows a live preview panel alongside the form.
|
|
2078
|
+
*/
|
|
2079
|
+
declare class CollectionEditPage implements HasUnsavedChanges {
|
|
2080
|
+
private readonly route;
|
|
2081
|
+
private readonly dialogService;
|
|
2082
|
+
private readonly collectionAccess;
|
|
2083
|
+
readonly basePath = "/admin/collections";
|
|
2084
|
+
/** Whether the live preview panel is visible */
|
|
2085
|
+
readonly showPreview: _angular_core.WritableSignal<boolean>;
|
|
2086
|
+
/** Reference to the entity form widget to read its formData signal */
|
|
2087
|
+
private readonly entityFormRef;
|
|
2088
|
+
readonly entityId: _angular_core.Signal<string | undefined>;
|
|
2089
|
+
readonly mode: _angular_core.Signal<EntityFormMode>;
|
|
2090
|
+
readonly collection: _angular_core.Signal<CollectionConfig | undefined>;
|
|
2091
|
+
/** Preview config from collection admin settings (false-y when not configured) */
|
|
2092
|
+
readonly previewConfig: _angular_core.Signal<boolean | ((doc: Record<string, unknown>) => string) | undefined>;
|
|
2093
|
+
/** Reactive form data from the entity form widget */
|
|
2094
|
+
readonly formData: _angular_core.Signal<Record<string, unknown>>;
|
|
2095
|
+
/** HasUnsavedChanges implementation for the route guard */
|
|
2096
|
+
hasUnsavedChanges(): boolean;
|
|
2097
|
+
/** Handle edit block request from preview iframe overlay */
|
|
2098
|
+
onEditBlockRequest(blockIndex: number): void;
|
|
2099
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<CollectionEditPage, never>;
|
|
2100
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<CollectionEditPage, "mcms-collection-edit", never, {}, {}, never, never, true, never>;
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
/**
|
|
2104
|
+
* Login Page Component
|
|
2105
|
+
*
|
|
2106
|
+
* Allows users to sign in with email and password, or via OAuth providers.
|
|
2107
|
+
*/
|
|
2108
|
+
declare class LoginPage implements OnInit {
|
|
2109
|
+
private readonly auth;
|
|
2110
|
+
private readonly router;
|
|
2111
|
+
/** Whether the component has been hydrated (SSR → client transition complete) */
|
|
2112
|
+
readonly hydrated: _angular_core.WritableSignal<boolean>;
|
|
2113
|
+
constructor();
|
|
2114
|
+
readonly email: _angular_core.WritableSignal<string>;
|
|
2115
|
+
readonly password: _angular_core.WritableSignal<string>;
|
|
2116
|
+
readonly error: _angular_core.WritableSignal<string | null>;
|
|
2117
|
+
readonly isSubmitting: _angular_core.WritableSignal<boolean>;
|
|
2118
|
+
readonly touched: _angular_core.WritableSignal<boolean>;
|
|
2119
|
+
readonly oauthProviders: _angular_core.WritableSignal<string[]>;
|
|
2120
|
+
readonly emailErrors: _angular_core.Signal<{
|
|
2121
|
+
kind: string;
|
|
2122
|
+
message: string;
|
|
2123
|
+
}[]>;
|
|
2124
|
+
readonly passwordErrors: _angular_core.Signal<{
|
|
2125
|
+
kind: string;
|
|
2126
|
+
message: string;
|
|
2127
|
+
}[]>;
|
|
2128
|
+
readonly isValid: _angular_core.Signal<boolean>;
|
|
2129
|
+
ngOnInit(): Promise<void>;
|
|
2130
|
+
signInWithOAuth(provider: string): void;
|
|
2131
|
+
onSubmit(event: Event): Promise<void>;
|
|
2132
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<LoginPage, never>;
|
|
2133
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<LoginPage, "mcms-login-page", never, {}, {}, never, never, true, never>;
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
/**
|
|
2137
|
+
* Setup Page Component
|
|
2138
|
+
*
|
|
2139
|
+
* First-time setup page for creating the initial admin user.
|
|
2140
|
+
* Only accessible when no users exist in the database.
|
|
2141
|
+
*/
|
|
2142
|
+
declare class SetupPage {
|
|
2143
|
+
private readonly auth;
|
|
2144
|
+
private readonly router;
|
|
2145
|
+
/** Whether the component has been hydrated (SSR → client transition complete) */
|
|
2146
|
+
readonly hydrated: _angular_core.WritableSignal<boolean>;
|
|
2147
|
+
constructor();
|
|
2148
|
+
readonly name: _angular_core.WritableSignal<string>;
|
|
2149
|
+
readonly email: _angular_core.WritableSignal<string>;
|
|
2150
|
+
readonly password: _angular_core.WritableSignal<string>;
|
|
2151
|
+
readonly confirmPassword: _angular_core.WritableSignal<string>;
|
|
2152
|
+
readonly error: _angular_core.WritableSignal<string | null>;
|
|
2153
|
+
readonly isSubmitting: _angular_core.WritableSignal<boolean>;
|
|
2154
|
+
readonly touched: _angular_core.WritableSignal<boolean>;
|
|
2155
|
+
readonly nameErrors: _angular_core.Signal<{
|
|
2156
|
+
kind: string;
|
|
2157
|
+
message: string;
|
|
2158
|
+
}[]>;
|
|
2159
|
+
readonly emailErrors: _angular_core.Signal<{
|
|
2160
|
+
kind: string;
|
|
2161
|
+
message: string;
|
|
2162
|
+
}[]>;
|
|
2163
|
+
readonly passwordErrors: _angular_core.Signal<{
|
|
2164
|
+
kind: string;
|
|
2165
|
+
message: string;
|
|
2166
|
+
}[]>;
|
|
2167
|
+
readonly confirmPasswordErrors: _angular_core.Signal<{
|
|
2168
|
+
kind: string;
|
|
2169
|
+
message: string;
|
|
2170
|
+
}[]>;
|
|
2171
|
+
readonly isValid: _angular_core.Signal<boolean>;
|
|
2172
|
+
onSubmit(event: Event): Promise<void>;
|
|
2173
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<SetupPage, never>;
|
|
2174
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<SetupPage, "mcms-setup-page", never, {}, {}, never, never, true, never>;
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
/** Helper type to represent media document from API */
|
|
2178
|
+
interface MediaItem$1 {
|
|
2179
|
+
id: string;
|
|
2180
|
+
filename: string;
|
|
2181
|
+
mimeType: string;
|
|
2182
|
+
path: string;
|
|
2183
|
+
url?: string;
|
|
2184
|
+
filesize?: number;
|
|
2185
|
+
alt?: string;
|
|
2186
|
+
width?: number;
|
|
2187
|
+
height?: number;
|
|
2188
|
+
}
|
|
2189
|
+
/**
|
|
2190
|
+
* Media Library Page
|
|
2191
|
+
*
|
|
2192
|
+
* Full-page view for managing media files: browse, upload, and delete.
|
|
2193
|
+
*/
|
|
2194
|
+
declare class MediaLibraryPage {
|
|
2195
|
+
private readonly document;
|
|
2196
|
+
private readonly api;
|
|
2197
|
+
private readonly uploadService;
|
|
2198
|
+
private readonly feedback;
|
|
2199
|
+
private readonly toast;
|
|
2200
|
+
private readonly dialog;
|
|
2201
|
+
private readonly destroyRef;
|
|
2202
|
+
private readonly uploadSubscriptions;
|
|
2203
|
+
/** Internal state */
|
|
2204
|
+
readonly isLoading: _angular_core.WritableSignal<boolean>;
|
|
2205
|
+
readonly mediaItems: _angular_core.WritableSignal<MediaItem$1[]>;
|
|
2206
|
+
readonly searchQuery: _angular_core.WritableSignal<string>;
|
|
2207
|
+
readonly currentPage: _angular_core.WritableSignal<number>;
|
|
2208
|
+
readonly totalPages: _angular_core.WritableSignal<number>;
|
|
2209
|
+
readonly totalDocs: _angular_core.WritableSignal<number>;
|
|
2210
|
+
readonly limit: _angular_core.WritableSignal<number>;
|
|
2211
|
+
readonly selectedItems: _angular_core.WritableSignal<Set<string>>;
|
|
2212
|
+
readonly activeUploads: _angular_core.WritableSignal<UploadProgress[]>;
|
|
2213
|
+
constructor();
|
|
2214
|
+
/**
|
|
2215
|
+
* Load media from the API.
|
|
2216
|
+
*/
|
|
2217
|
+
private loadMedia;
|
|
2218
|
+
/**
|
|
2219
|
+
* Handle search query change.
|
|
2220
|
+
*/
|
|
2221
|
+
onSearchChange(query: string): void;
|
|
2222
|
+
/**
|
|
2223
|
+
* Handle page change.
|
|
2224
|
+
*/
|
|
2225
|
+
onPageChange(page: number): void;
|
|
2226
|
+
/**
|
|
2227
|
+
* Handle file selection from input.
|
|
2228
|
+
*/
|
|
2229
|
+
onFilesSelected(event: Event): void;
|
|
2230
|
+
/**
|
|
2231
|
+
* Update upload progress in the active uploads list.
|
|
2232
|
+
*/
|
|
2233
|
+
private updateUploadProgress;
|
|
2234
|
+
/**
|
|
2235
|
+
* Remove upload from active uploads list.
|
|
2236
|
+
*/
|
|
2237
|
+
private removeUpload;
|
|
2238
|
+
/**
|
|
2239
|
+
* Toggle selection of a media item.
|
|
2240
|
+
*/
|
|
2241
|
+
toggleSelection(media: MediaItem$1): void;
|
|
2242
|
+
/**
|
|
2243
|
+
* View media file in a new tab.
|
|
2244
|
+
*/
|
|
2245
|
+
viewMedia(media: MediaItem$1): void;
|
|
2246
|
+
/**
|
|
2247
|
+
* Open the edit dialog for a media item.
|
|
2248
|
+
*/
|
|
2249
|
+
editMedia(media: MediaItem$1): void;
|
|
2250
|
+
/**
|
|
2251
|
+
* Delete a single media item.
|
|
2252
|
+
*/
|
|
2253
|
+
deleteMedia(media: MediaItem$1): Promise<void>;
|
|
2254
|
+
/**
|
|
2255
|
+
* Delete all selected media items.
|
|
2256
|
+
*/
|
|
2257
|
+
deleteSelected(): Promise<void>;
|
|
2258
|
+
/**
|
|
2259
|
+
* Get the URL for a media document.
|
|
2260
|
+
*/
|
|
2261
|
+
getMediaUrl(media: MediaItem$1): string;
|
|
2262
|
+
/**
|
|
2263
|
+
* Format file size for display.
|
|
2264
|
+
*/
|
|
2265
|
+
formatFileSize(bytes?: number): string;
|
|
2266
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<MediaLibraryPage, never>;
|
|
2267
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<MediaLibraryPage, "mcms-media-library", never, {}, {}, never, never, true, never>;
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
/**
|
|
2271
|
+
* Forgot Password Page
|
|
2272
|
+
*
|
|
2273
|
+
* Full-page wrapper for the forgot password form.
|
|
2274
|
+
* Uses a centered card layout matching the login page.
|
|
2275
|
+
*/
|
|
2276
|
+
declare class ForgotPasswordPage {
|
|
2277
|
+
private readonly router;
|
|
2278
|
+
navigateToLogin(): void;
|
|
2279
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<ForgotPasswordPage, never>;
|
|
2280
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<ForgotPasswordPage, "mcms-forgot-password-page", never, {}, {}, never, never, true, never>;
|
|
2281
|
+
}
|
|
2282
|
+
|
|
2283
|
+
/**
|
|
2284
|
+
* Reset Password Page
|
|
2285
|
+
*
|
|
2286
|
+
* Full-page wrapper for the reset password form.
|
|
2287
|
+
* Reads the token from query parameters and passes it to the form.
|
|
2288
|
+
*/
|
|
2289
|
+
declare class ResetPasswordPage implements OnInit {
|
|
2290
|
+
private readonly router;
|
|
2291
|
+
private readonly route;
|
|
2292
|
+
readonly token: _angular_core.WritableSignal<string>;
|
|
2293
|
+
ngOnInit(): void;
|
|
2294
|
+
navigateToLogin(): void;
|
|
2295
|
+
navigateToForgotPassword(): void;
|
|
2296
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<ResetPasswordPage, never>;
|
|
2297
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<ResetPasswordPage, "mcms-reset-password-page", never, {}, {}, never, never, true, never>;
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2300
|
+
interface CollectionGroup {
|
|
2301
|
+
name: string;
|
|
2302
|
+
collections: CollectionConfig[];
|
|
2303
|
+
}
|
|
2304
|
+
interface GlobalGroup {
|
|
2305
|
+
name: string;
|
|
2306
|
+
globals: GlobalConfig[];
|
|
2307
|
+
}
|
|
2308
|
+
interface PluginRouteGroup {
|
|
2309
|
+
name: string;
|
|
2310
|
+
routes: AdminPluginRoute[];
|
|
2311
|
+
}
|
|
2312
|
+
/**
|
|
2313
|
+
* Admin Sidebar Widget
|
|
2314
|
+
*
|
|
2315
|
+
* CMS-specific sidebar with collection navigation and user section.
|
|
2316
|
+
* Composes UI library Sidebar components with Momentum CMS logic.
|
|
2317
|
+
*
|
|
2318
|
+
* Features:
|
|
2319
|
+
* - Collection grouping via admin.group field
|
|
2320
|
+
* - Plugin-registered route sections
|
|
2321
|
+
*
|
|
2322
|
+
* @example
|
|
2323
|
+
* ```html
|
|
2324
|
+
* <mcms-admin-sidebar
|
|
2325
|
+
* [branding]="{ title: 'My CMS', logo: '/logo.svg' }"
|
|
2326
|
+
* [collections]="collections"
|
|
2327
|
+
* [pluginRoutes]="pluginRoutes"
|
|
2328
|
+
* [user]="currentUser"
|
|
2329
|
+
* basePath="/admin"
|
|
2330
|
+
* (signOut)="onSignOut()"
|
|
2331
|
+
* />
|
|
2332
|
+
* ```
|
|
2333
|
+
*/
|
|
2334
|
+
declare class AdminSidebarWidget {
|
|
2335
|
+
readonly theme: McmsThemeService;
|
|
2336
|
+
/** Branding configuration (logo, title) */
|
|
2337
|
+
readonly branding: _angular_core.InputSignal<AdminBranding | undefined>;
|
|
2338
|
+
/** Collections to display in navigation */
|
|
2339
|
+
readonly collections: _angular_core.InputSignal<CollectionConfig[]>;
|
|
2340
|
+
/** Globals to display in navigation */
|
|
2341
|
+
readonly globals: _angular_core.InputSignal<GlobalConfig[]>;
|
|
2342
|
+
/** Plugin-registered admin routes */
|
|
2343
|
+
readonly pluginRoutes: _angular_core.InputSignal<AdminPluginRoute[]>;
|
|
2344
|
+
/** Current authenticated user */
|
|
2345
|
+
readonly user: _angular_core.InputSignal<AdminUser | null>;
|
|
2346
|
+
/** Base path for admin routes */
|
|
2347
|
+
readonly basePath: _angular_core.InputSignal<string>;
|
|
2348
|
+
/** Whether the sidebar is collapsed */
|
|
2349
|
+
readonly collapsed: _angular_core.InputSignal<boolean>;
|
|
2350
|
+
/** Sidebar width */
|
|
2351
|
+
readonly width: _angular_core.InputSignal<string>;
|
|
2352
|
+
/** Emitted when user clicks sign out */
|
|
2353
|
+
readonly signOut: _angular_core.OutputEmitterRef<void>;
|
|
2354
|
+
/** Computed collections base path */
|
|
2355
|
+
readonly collectionsBasePath: _angular_core.Signal<string>;
|
|
2356
|
+
/** Collections grouped by admin.group field */
|
|
2357
|
+
readonly collectionGroups: _angular_core.Signal<CollectionGroup[]>;
|
|
2358
|
+
/** Globals grouped by admin.group field */
|
|
2359
|
+
readonly globalGroups: _angular_core.Signal<GlobalGroup[]>;
|
|
2360
|
+
/** Plugin routes grouped by group field */
|
|
2361
|
+
readonly pluginRouteGroups: _angular_core.Signal<PluginRouteGroup[]>;
|
|
2362
|
+
/** Collection icon names by slug */
|
|
2363
|
+
private readonly collectionIcons;
|
|
2364
|
+
/**
|
|
2365
|
+
* Get display label for a collection.
|
|
2366
|
+
*/
|
|
2367
|
+
getCollectionLabel(collection: CollectionConfig): string;
|
|
2368
|
+
/**
|
|
2369
|
+
* Get router path for a collection.
|
|
2370
|
+
*/
|
|
2371
|
+
getCollectionPath(collection: CollectionConfig): string;
|
|
2372
|
+
/**
|
|
2373
|
+
* Get icon name for a collection based on its slug.
|
|
2374
|
+
*/
|
|
2375
|
+
getCollectionIcon(collection: CollectionConfig): string;
|
|
2376
|
+
/**
|
|
2377
|
+
* Get display label for a global.
|
|
2378
|
+
*/
|
|
2379
|
+
getGlobalLabel(global: GlobalConfig): string;
|
|
2380
|
+
/**
|
|
2381
|
+
* Get initials from user name.
|
|
2382
|
+
*/
|
|
2383
|
+
getUserInitials(name: string): string;
|
|
2384
|
+
/**
|
|
2385
|
+
* Toggle theme between light and dark.
|
|
2386
|
+
*/
|
|
2387
|
+
toggleTheme(): void;
|
|
2388
|
+
/**
|
|
2389
|
+
* Handle sign out click.
|
|
2390
|
+
*/
|
|
2391
|
+
onSignOutClick(): void;
|
|
2392
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<AdminSidebarWidget, never>;
|
|
2393
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<AdminSidebarWidget, "mcms-admin-sidebar", never, { "branding": { "alias": "branding"; "required": false; "isSignal": true; }; "collections": { "alias": "collections"; "required": false; "isSignal": true; }; "globals": { "alias": "globals"; "required": false; "isSignal": true; }; "pluginRoutes": { "alias": "pluginRoutes"; "required": false; "isSignal": true; }; "user": { "alias": "user"; "required": false; "isSignal": true; }; "basePath": { "alias": "basePath"; "required": false; "isSignal": true; }; "collapsed": { "alias": "collapsed"; "required": false; "isSignal": true; }; "width": { "alias": "width"; "required": false; "isSignal": true; }; }, { "signOut": "signOut"; }, never, never, true, never>;
|
|
2394
|
+
}
|
|
2395
|
+
|
|
2396
|
+
/**
|
|
2397
|
+
* Collection Card Widget
|
|
2398
|
+
*
|
|
2399
|
+
* Displays a collection card with document count fetched from API.
|
|
2400
|
+
* Useful for dashboard overviews and collection navigation.
|
|
2401
|
+
*
|
|
2402
|
+
* @example
|
|
2403
|
+
* ```html
|
|
2404
|
+
* <mcms-collection-card
|
|
2405
|
+
* [collection]="postsCollection"
|
|
2406
|
+
* basePath="/admin/collections"
|
|
2407
|
+
* (viewAll)="navigateToCollection($event)"
|
|
2408
|
+
* />
|
|
2409
|
+
* ```
|
|
2410
|
+
*/
|
|
2411
|
+
declare class CollectionCardWidget {
|
|
2412
|
+
private readonly api;
|
|
2413
|
+
private readonly collectionAccess;
|
|
2414
|
+
/** The collection to display */
|
|
2415
|
+
readonly collection: _angular_core.InputSignal<CollectionConfig>;
|
|
2416
|
+
/** Base path for collection routes */
|
|
2417
|
+
readonly basePath: _angular_core.InputSignal<string>;
|
|
2418
|
+
/** Whether to show document count */
|
|
2419
|
+
readonly showDocumentCount: _angular_core.InputSignal<boolean>;
|
|
2420
|
+
/** Emitted when view all is clicked */
|
|
2421
|
+
readonly viewAll: _angular_core.OutputEmitterRef<CollectionConfig>;
|
|
2422
|
+
/** Document count from API */
|
|
2423
|
+
readonly count: _angular_core.WritableSignal<number>;
|
|
2424
|
+
/** Whether count is loading */
|
|
2425
|
+
readonly loading: _angular_core.WritableSignal<boolean>;
|
|
2426
|
+
/** Error message if count fetch failed */
|
|
2427
|
+
readonly error: _angular_core.WritableSignal<string | null>;
|
|
2428
|
+
/** Display label for the collection */
|
|
2429
|
+
readonly collectionLabel: _angular_core.Signal<string>;
|
|
2430
|
+
/** Lowercase label for descriptions */
|
|
2431
|
+
readonly collectionLabelLower: _angular_core.Signal<string>;
|
|
2432
|
+
/** Path to view all documents */
|
|
2433
|
+
readonly viewPath: _angular_core.Signal<string>;
|
|
2434
|
+
/** Path to create new document */
|
|
2435
|
+
readonly createPath: _angular_core.Signal<string>;
|
|
2436
|
+
/** Whether user can create documents */
|
|
2437
|
+
readonly canCreate: _angular_core.Signal<boolean>;
|
|
2438
|
+
constructor();
|
|
2439
|
+
/**
|
|
2440
|
+
* Fetch document count for the collection.
|
|
2441
|
+
*/
|
|
2442
|
+
private fetchCount;
|
|
2443
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<CollectionCardWidget, never>;
|
|
2444
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<CollectionCardWidget, "mcms-collection-card", never, { "collection": { "alias": "collection"; "required": true; "isSignal": true; }; "basePath": { "alias": "basePath"; "required": false; "isSignal": true; }; "showDocumentCount": { "alias": "showDocumentCount"; "required": false; "isSignal": true; }; }, { "viewAll": "viewAll"; }, never, never, true, never>;
|
|
2445
|
+
}
|
|
2446
|
+
|
|
2447
|
+
/**
|
|
2448
|
+
* Entity Form Widget
|
|
2449
|
+
*
|
|
2450
|
+
* Dynamic form for create/edit operations using Angular Signal Forms.
|
|
2451
|
+
*
|
|
2452
|
+
* @example
|
|
2453
|
+
* ```html
|
|
2454
|
+
* <mcms-entity-form
|
|
2455
|
+
* [collection]="postsCollection"
|
|
2456
|
+
* entityId="123"
|
|
2457
|
+
* mode="edit"
|
|
2458
|
+
* (saved)="onSaved($event)"
|
|
2459
|
+
* (cancelled)="onCancel()"
|
|
2460
|
+
* />
|
|
2461
|
+
* ```
|
|
2462
|
+
*/
|
|
2463
|
+
declare class EntityFormWidget<T extends Entity = Entity> {
|
|
2464
|
+
private readonly api;
|
|
2465
|
+
private readonly injector;
|
|
2466
|
+
private readonly versionService;
|
|
2467
|
+
private readonly collectionAccess;
|
|
2468
|
+
private readonly feedback;
|
|
2469
|
+
private readonly router;
|
|
2470
|
+
private readonly liveAnnouncer;
|
|
2471
|
+
/** The collection configuration */
|
|
2472
|
+
readonly collection: _angular_core.InputSignal<CollectionConfig>;
|
|
2473
|
+
/** Entity ID for edit mode (undefined for create) */
|
|
2474
|
+
readonly entityId: _angular_core.InputSignal<string | undefined>;
|
|
2475
|
+
/** Form mode */
|
|
2476
|
+
readonly mode: _angular_core.InputSignal<EntityFormMode>;
|
|
2477
|
+
/** Base path for navigation */
|
|
2478
|
+
readonly basePath: _angular_core.InputSignal<string>;
|
|
2479
|
+
/** Whether to show breadcrumbs */
|
|
2480
|
+
readonly showBreadcrumbs: _angular_core.InputSignal<boolean>;
|
|
2481
|
+
/** When true, prevents router navigation after save/cancel (used in entity sheet) */
|
|
2482
|
+
readonly suppressNavigation: _angular_core.InputSignal<boolean>;
|
|
2483
|
+
/** When true, uses the global API instead of collection API (singleton mode) */
|
|
2484
|
+
readonly isGlobal: _angular_core.InputSignal<boolean>;
|
|
2485
|
+
/** The global slug (used when isGlobal is true) */
|
|
2486
|
+
readonly globalSlug: _angular_core.InputSignal<string | undefined>;
|
|
2487
|
+
/** Outputs */
|
|
2488
|
+
readonly saved: _angular_core.OutputEmitterRef<T>;
|
|
2489
|
+
readonly cancelled: _angular_core.OutputEmitterRef<void>;
|
|
2490
|
+
readonly saveError: _angular_core.OutputEmitterRef<Error>;
|
|
2491
|
+
readonly modeChange: _angular_core.OutputEmitterRef<EntityFormMode>;
|
|
2492
|
+
readonly draftSaved: _angular_core.OutputEmitterRef<void>;
|
|
2493
|
+
/** Model signal — the single source of truth for form data */
|
|
2494
|
+
readonly formModel: _angular_core.WritableSignal<Record<string, unknown>>;
|
|
2495
|
+
/** Alias for backward compatibility (CollectionEditPage reads formData) */
|
|
2496
|
+
readonly formData: _angular_core.WritableSignal<Record<string, unknown>>;
|
|
2497
|
+
/** Signal forms tree — created once when collection is available */
|
|
2498
|
+
readonly entityForm: _angular_core.WritableSignal<_angular_forms_signals.FieldTree<Record<string, unknown>, string | number> | null>;
|
|
2499
|
+
/** Original data for edit mode */
|
|
2500
|
+
readonly originalData: _angular_core.WritableSignal<T | null>;
|
|
2501
|
+
/** UI state */
|
|
2502
|
+
readonly isLoading: _angular_core.WritableSignal<boolean>;
|
|
2503
|
+
readonly isSubmitting: _angular_core.WritableSignal<boolean>;
|
|
2504
|
+
readonly isSavingDraft: _angular_core.WritableSignal<boolean>;
|
|
2505
|
+
readonly formError: _angular_core.WritableSignal<string | null>;
|
|
2506
|
+
/** Whether the form has been set up */
|
|
2507
|
+
private formCreated;
|
|
2508
|
+
/** Whether the form has unsaved changes (from signal forms dirty tracking) */
|
|
2509
|
+
readonly isDirty: _angular_core.Signal<boolean>;
|
|
2510
|
+
/** Computed collection label */
|
|
2511
|
+
readonly collectionLabel: _angular_core.Signal<string>;
|
|
2512
|
+
/** Computed collection label singular */
|
|
2513
|
+
readonly collectionLabelSingular: _angular_core.Signal<string>;
|
|
2514
|
+
/** Dashboard path (remove /collections from base path) */
|
|
2515
|
+
readonly dashboardPath: _angular_core.Signal<string>;
|
|
2516
|
+
/** Collection list path */
|
|
2517
|
+
readonly collectionListPath: _angular_core.Signal<string>;
|
|
2518
|
+
/** Page title for breadcrumb */
|
|
2519
|
+
readonly pageTitle: _angular_core.Signal<string>;
|
|
2520
|
+
/** Visible fields (excluding hidden ones and those failing admin.condition) */
|
|
2521
|
+
readonly visibleFields: _angular_core.Signal<Field[]>;
|
|
2522
|
+
/** Whether user can edit */
|
|
2523
|
+
readonly canEdit: _angular_core.Signal<boolean>;
|
|
2524
|
+
/** Whether collection has versioning with drafts enabled */
|
|
2525
|
+
readonly hasVersioning: _angular_core.Signal<boolean>;
|
|
2526
|
+
/** Whether draft save is available (edit mode with existing entity) */
|
|
2527
|
+
readonly canSaveDraft: _angular_core.Signal<boolean>;
|
|
2528
|
+
constructor();
|
|
2529
|
+
/**
|
|
2530
|
+
* Get a FieldTree node for a top-level field by name.
|
|
2531
|
+
*/
|
|
2532
|
+
getFormNode(fieldName: string): unknown;
|
|
2533
|
+
/**
|
|
2534
|
+
* Load entity for edit mode.
|
|
2535
|
+
*/
|
|
2536
|
+
private loadEntity;
|
|
2537
|
+
/**
|
|
2538
|
+
* Load global singleton document.
|
|
2539
|
+
*/
|
|
2540
|
+
private loadGlobal;
|
|
2541
|
+
/**
|
|
2542
|
+
* Handle form submission using Angular Signal Forms submit().
|
|
2543
|
+
* submit() marks all fields as touched, then only calls the callback if valid.
|
|
2544
|
+
*/
|
|
2545
|
+
onSubmit(): Promise<void>;
|
|
2546
|
+
/**
|
|
2547
|
+
* Handle cancel.
|
|
2548
|
+
*/
|
|
2549
|
+
onCancel(): void;
|
|
2550
|
+
/**
|
|
2551
|
+
* Switch from view to edit mode.
|
|
2552
|
+
*/
|
|
2553
|
+
switchToEdit(): void;
|
|
2554
|
+
/**
|
|
2555
|
+
* Handle version restore — reload the entity data.
|
|
2556
|
+
*/
|
|
2557
|
+
onVersionRestored(): void;
|
|
2558
|
+
/**
|
|
2559
|
+
* Save form data as a draft.
|
|
2560
|
+
*/
|
|
2561
|
+
onSaveDraft(): Promise<void>;
|
|
2562
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<EntityFormWidget<any>, never>;
|
|
2563
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<EntityFormWidget<any>, "mcms-entity-form", never, { "collection": { "alias": "collection"; "required": true; "isSignal": true; }; "entityId": { "alias": "entityId"; "required": false; "isSignal": true; }; "mode": { "alias": "mode"; "required": false; "isSignal": true; }; "basePath": { "alias": "basePath"; "required": false; "isSignal": true; }; "showBreadcrumbs": { "alias": "showBreadcrumbs"; "required": false; "isSignal": true; }; "suppressNavigation": { "alias": "suppressNavigation"; "required": false; "isSignal": true; }; "isGlobal": { "alias": "isGlobal"; "required": false; "isSignal": true; }; "globalSlug": { "alias": "globalSlug"; "required": false; "isSignal": true; }; }, { "saved": "saved"; "cancelled": "cancelled"; "saveError": "saveError"; "modeChange": "modeChange"; "draftSaved": "draftSaved"; }, never, ["[entityFormHeaderExtra]"], true, never>;
|
|
2564
|
+
}
|
|
2565
|
+
|
|
2566
|
+
/**
|
|
2567
|
+
* Field display configuration for the entity view.
|
|
2568
|
+
*/
|
|
2569
|
+
interface EntityViewFieldConfig {
|
|
2570
|
+
/** Field name */
|
|
2571
|
+
field: string;
|
|
2572
|
+
/** Display label (defaults to field name) */
|
|
2573
|
+
label?: string;
|
|
2574
|
+
/** Display type override */
|
|
2575
|
+
type?: 'text' | 'number' | 'date' | 'datetime' | 'boolean' | 'badge' | 'link' | 'email' | 'list' | 'json' | 'html' | 'group' | 'array-table';
|
|
2576
|
+
/** Custom formatter function */
|
|
2577
|
+
formatter?: (value: unknown, entity: Entity) => string;
|
|
2578
|
+
/** Whether to hide this field */
|
|
2579
|
+
hidden?: boolean;
|
|
2580
|
+
}
|
|
2581
|
+
/**
|
|
2582
|
+
* Breadcrumb item for navigation.
|
|
2583
|
+
*/
|
|
2584
|
+
interface BreadcrumbItem {
|
|
2585
|
+
label: string;
|
|
2586
|
+
href?: string;
|
|
2587
|
+
}
|
|
2588
|
+
/**
|
|
2589
|
+
* Entity view action event.
|
|
2590
|
+
*/
|
|
2591
|
+
interface EntityViewActionEvent {
|
|
2592
|
+
action: EntityAction;
|
|
2593
|
+
entity: Entity;
|
|
2594
|
+
}
|
|
2595
|
+
|
|
2596
|
+
/**
|
|
2597
|
+
* Entity View Widget
|
|
2598
|
+
*
|
|
2599
|
+
* Read-only entity display connected to Momentum API.
|
|
2600
|
+
*
|
|
2601
|
+
* @example
|
|
2602
|
+
* ```html
|
|
2603
|
+
* <mcms-entity-view
|
|
2604
|
+
* [collection]="postsCollection"
|
|
2605
|
+
* entityId="123"
|
|
2606
|
+
* (edit)="onEdit($event)"
|
|
2607
|
+
* (delete)="onDelete($event)"
|
|
2608
|
+
* />
|
|
2609
|
+
* ```
|
|
2610
|
+
*/
|
|
2611
|
+
declare class EntityViewWidget<T extends Entity = Entity> {
|
|
2612
|
+
private readonly api;
|
|
2613
|
+
private readonly collectionAccess;
|
|
2614
|
+
private readonly feedback;
|
|
2615
|
+
private readonly router;
|
|
2616
|
+
/** The collection configuration */
|
|
2617
|
+
readonly collection: _angular_core.InputSignal<CollectionConfig>;
|
|
2618
|
+
/** Entity ID to view */
|
|
2619
|
+
readonly entityId: _angular_core.InputSignal<string>;
|
|
2620
|
+
/** Base path for navigation */
|
|
2621
|
+
readonly basePath: _angular_core.InputSignal<string>;
|
|
2622
|
+
/** Whether to show breadcrumbs */
|
|
2623
|
+
readonly showBreadcrumbs: _angular_core.InputSignal<boolean>;
|
|
2624
|
+
/** Custom field configurations */
|
|
2625
|
+
readonly fieldConfigs: _angular_core.InputSignal<EntityViewFieldConfig[]>;
|
|
2626
|
+
/** Additional actions to show */
|
|
2627
|
+
readonly actions: _angular_core.InputSignal<EntityAction[]>;
|
|
2628
|
+
/** Whether to show version history (only shown if versioning is enabled) */
|
|
2629
|
+
readonly showVersionHistory: _angular_core.InputSignal<boolean>;
|
|
2630
|
+
/** When true, prevents router navigation (used in entity sheet) */
|
|
2631
|
+
readonly suppressNavigation: _angular_core.InputSignal<boolean>;
|
|
2632
|
+
/** Outputs */
|
|
2633
|
+
readonly edit: _angular_core.OutputEmitterRef<T>;
|
|
2634
|
+
readonly statusChanged: _angular_core.OutputEmitterRef<DocumentStatus$1>;
|
|
2635
|
+
readonly delete_: _angular_core.OutputEmitterRef<T>;
|
|
2636
|
+
readonly actionClick: _angular_core.OutputEmitterRef<{
|
|
2637
|
+
action: EntityAction;
|
|
2638
|
+
entity: T;
|
|
2639
|
+
}>;
|
|
2640
|
+
/** Internal state */
|
|
2641
|
+
readonly entity: _angular_core.WritableSignal<T | null>;
|
|
2642
|
+
readonly isLoading: _angular_core.WritableSignal<boolean>;
|
|
2643
|
+
readonly loadError: _angular_core.WritableSignal<string | null>;
|
|
2644
|
+
readonly resolvedRelationships: _angular_core.WritableSignal<Map<string, string>>;
|
|
2645
|
+
/** Computed collection label */
|
|
2646
|
+
readonly collectionLabel: _angular_core.Signal<string>;
|
|
2647
|
+
/** Computed collection label singular */
|
|
2648
|
+
readonly collectionLabelSingular: _angular_core.Signal<string>;
|
|
2649
|
+
/** Entity title (uses title field or ID) */
|
|
2650
|
+
readonly entityTitle: _angular_core.Signal<string>;
|
|
2651
|
+
/** Visible fields (excluding hidden ones) */
|
|
2652
|
+
readonly visibleFields: _angular_core.Signal<Field[]>;
|
|
2653
|
+
/** Whether collection has timestamps */
|
|
2654
|
+
readonly hasTimestamps: _angular_core.Signal<boolean>;
|
|
2655
|
+
/** Dashboard path */
|
|
2656
|
+
readonly dashboardPath: _angular_core.Signal<string>;
|
|
2657
|
+
/** Collection list path */
|
|
2658
|
+
readonly collectionListPath: _angular_core.Signal<string>;
|
|
2659
|
+
/** Whether user can edit */
|
|
2660
|
+
readonly canEdit: _angular_core.Signal<boolean>;
|
|
2661
|
+
/** Whether user can delete */
|
|
2662
|
+
readonly canDelete: _angular_core.Signal<boolean>;
|
|
2663
|
+
/** Whether collection has soft delete enabled */
|
|
2664
|
+
readonly hasSoftDelete: _angular_core.Signal<boolean>;
|
|
2665
|
+
/** Whether the current entity is soft-deleted */
|
|
2666
|
+
readonly isDeleted: _angular_core.Signal<boolean>;
|
|
2667
|
+
/** Whether collection has versioning enabled */
|
|
2668
|
+
readonly hasVersioning: _angular_core.Signal<boolean>;
|
|
2669
|
+
/** Current document status (from entity or default to 'draft') */
|
|
2670
|
+
readonly documentStatus: _angular_core.Signal<DocumentStatus$1>;
|
|
2671
|
+
/** Preview URL derived from collection's admin.preview config */
|
|
2672
|
+
readonly previewUrl: _angular_core.Signal<string | null>;
|
|
2673
|
+
constructor();
|
|
2674
|
+
/**
|
|
2675
|
+
* Load entity from API.
|
|
2676
|
+
*/
|
|
2677
|
+
private loadEntity;
|
|
2678
|
+
/**
|
|
2679
|
+
* Get field value from entity, resolving relationship labels.
|
|
2680
|
+
*/
|
|
2681
|
+
getFieldValue(fieldName: string): unknown;
|
|
2682
|
+
/**
|
|
2683
|
+
* Get field display type from field definition.
|
|
2684
|
+
*/
|
|
2685
|
+
getFieldDisplayType(field: Field): FieldDisplayType;
|
|
2686
|
+
/**
|
|
2687
|
+
* Get sub-field metadata for group and array field types.
|
|
2688
|
+
*/
|
|
2689
|
+
getFieldMeta(field: Field): FieldDisplayFieldMeta[];
|
|
2690
|
+
/**
|
|
2691
|
+
* Get number format config from field definition.
|
|
2692
|
+
*/
|
|
2693
|
+
getNumberFormat(field: Field): FieldDisplayNumberFormat | undefined;
|
|
2694
|
+
/**
|
|
2695
|
+
* Get date format config from field definition.
|
|
2696
|
+
*/
|
|
2697
|
+
getDateFormat(field: Field): FieldDisplayDateFormat | undefined;
|
|
2698
|
+
/**
|
|
2699
|
+
* Handle edit button click.
|
|
2700
|
+
*/
|
|
2701
|
+
onEditClick(): void;
|
|
2702
|
+
/**
|
|
2703
|
+
* Handle delete button click.
|
|
2704
|
+
*/
|
|
2705
|
+
onDeleteClick(): Promise<void>;
|
|
2706
|
+
onRestoreClick(): Promise<void>;
|
|
2707
|
+
onForceDeleteClick(): Promise<void>;
|
|
2708
|
+
/**
|
|
2709
|
+
* Handle custom action click.
|
|
2710
|
+
*/
|
|
2711
|
+
onActionClick(action: EntityAction): void;
|
|
2712
|
+
/**
|
|
2713
|
+
* Navigate back to collection list.
|
|
2714
|
+
*/
|
|
2715
|
+
navigateBack(): void;
|
|
2716
|
+
/**
|
|
2717
|
+
* Handle status change from publish controls.
|
|
2718
|
+
*/
|
|
2719
|
+
onStatusChanged(status: DocumentStatus$1): void;
|
|
2720
|
+
/**
|
|
2721
|
+
* Handle version restoration.
|
|
2722
|
+
*/
|
|
2723
|
+
onVersionRestored(): void;
|
|
2724
|
+
/**
|
|
2725
|
+
* Resolve relationship field values from IDs to display labels.
|
|
2726
|
+
*/
|
|
2727
|
+
private resolveRelationships;
|
|
2728
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<EntityViewWidget<any>, never>;
|
|
2729
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<EntityViewWidget<any>, "mcms-entity-view", never, { "collection": { "alias": "collection"; "required": true; "isSignal": true; }; "entityId": { "alias": "entityId"; "required": true; "isSignal": true; }; "basePath": { "alias": "basePath"; "required": false; "isSignal": true; }; "showBreadcrumbs": { "alias": "showBreadcrumbs"; "required": false; "isSignal": true; }; "fieldConfigs": { "alias": "fieldConfigs"; "required": false; "isSignal": true; }; "actions": { "alias": "actions"; "required": false; "isSignal": true; }; "showVersionHistory": { "alias": "showVersionHistory"; "required": false; "isSignal": true; }; "suppressNavigation": { "alias": "suppressNavigation"; "required": false; "isSignal": true; }; }, { "edit": "edit"; "statusChanged": "statusChanged"; "delete_": "delete_"; "actionClick": "actionClick"; }, never, ["[entityViewHeaderExtra]"], true, never>;
|
|
2730
|
+
}
|
|
2731
|
+
|
|
2732
|
+
/**
|
|
2733
|
+
* Feedback Service
|
|
2734
|
+
*
|
|
2735
|
+
* CMS-specific toast messages and confirmations for entity operations.
|
|
2736
|
+
* Wraps the UI library's ToastService and ConfirmationService with
|
|
2737
|
+
* convenience methods for common CMS operations.
|
|
2738
|
+
*
|
|
2739
|
+
* @example
|
|
2740
|
+
* ```typescript
|
|
2741
|
+
* const feedback = inject(FeedbackService);
|
|
2742
|
+
*
|
|
2743
|
+
* // Success messages
|
|
2744
|
+
* feedback.entityCreated('Post');
|
|
2745
|
+
* feedback.entityUpdated('Post');
|
|
2746
|
+
* feedback.entityDeleted('Post');
|
|
2747
|
+
*
|
|
2748
|
+
* // Error messages
|
|
2749
|
+
* feedback.operationFailed('Failed to save', error);
|
|
2750
|
+
*
|
|
2751
|
+
* // Confirmations
|
|
2752
|
+
* const confirmed = await feedback.confirmDelete('Post', 'My Blog Post');
|
|
2753
|
+
* if (confirmed) {
|
|
2754
|
+
* // Proceed with deletion
|
|
2755
|
+
* }
|
|
2756
|
+
* ```
|
|
2757
|
+
*/
|
|
2758
|
+
declare class FeedbackService {
|
|
2759
|
+
private readonly toast;
|
|
2760
|
+
private readonly confirmation;
|
|
2761
|
+
/**
|
|
2762
|
+
* Show success message when an entity is created.
|
|
2763
|
+
*/
|
|
2764
|
+
entityCreated(collectionLabel: string): void;
|
|
2765
|
+
/**
|
|
2766
|
+
* Show success message when an entity is updated.
|
|
2767
|
+
*/
|
|
2768
|
+
entityUpdated(collectionLabel: string): void;
|
|
2769
|
+
/**
|
|
2770
|
+
* Show success message when an entity is deleted.
|
|
2771
|
+
*/
|
|
2772
|
+
entityDeleted(collectionLabel: string): void;
|
|
2773
|
+
/**
|
|
2774
|
+
* Show success message when multiple entities are deleted.
|
|
2775
|
+
*/
|
|
2776
|
+
entitiesDeleted(collectionLabel: string, count: number): void;
|
|
2777
|
+
/**
|
|
2778
|
+
* Show success message when an entity is published.
|
|
2779
|
+
*/
|
|
2780
|
+
entityPublished(collectionLabel: string): void;
|
|
2781
|
+
/**
|
|
2782
|
+
* Show success message when an entity is unpublished.
|
|
2783
|
+
*/
|
|
2784
|
+
entityUnpublished(collectionLabel: string): void;
|
|
2785
|
+
/**
|
|
2786
|
+
* Show error message when an operation fails.
|
|
2787
|
+
*/
|
|
2788
|
+
operationFailed(message: string, error?: Error): void;
|
|
2789
|
+
/**
|
|
2790
|
+
* Show error message when validation fails.
|
|
2791
|
+
*/
|
|
2792
|
+
validationFailed(fieldCount: number): void;
|
|
2793
|
+
/**
|
|
2794
|
+
* Show error message when not authorized.
|
|
2795
|
+
*/
|
|
2796
|
+
notAuthorized(action: string): void;
|
|
2797
|
+
/**
|
|
2798
|
+
* Show error message when entity not found.
|
|
2799
|
+
*/
|
|
2800
|
+
entityNotFound(collectionLabel: string): void;
|
|
2801
|
+
/**
|
|
2802
|
+
* Show warning about unsaved changes.
|
|
2803
|
+
*/
|
|
2804
|
+
unsavedChanges(): void;
|
|
2805
|
+
/**
|
|
2806
|
+
* Confirm delete operation for a single entity.
|
|
2807
|
+
* @param collectionLabel The collection label (e.g., "Post")
|
|
2808
|
+
* @param entityTitle The entity title to display (optional)
|
|
2809
|
+
* @returns Promise resolving to true if confirmed
|
|
2810
|
+
*/
|
|
2811
|
+
confirmDelete(collectionLabel: string, entityTitle?: string): Promise<boolean>;
|
|
2812
|
+
/**
|
|
2813
|
+
* Confirm bulk delete operation.
|
|
2814
|
+
* @param collectionLabel The collection label (e.g., "Posts")
|
|
2815
|
+
* @param count Number of items to delete
|
|
2816
|
+
* @returns Promise resolving to true if confirmed
|
|
2817
|
+
*/
|
|
2818
|
+
confirmBulkDelete(collectionLabel: string, count: number): Promise<boolean>;
|
|
2819
|
+
/**
|
|
2820
|
+
* Confirm discard changes.
|
|
2821
|
+
* @returns Promise resolving to true if confirmed
|
|
2822
|
+
*/
|
|
2823
|
+
confirmDiscard(): Promise<boolean>;
|
|
2824
|
+
/**
|
|
2825
|
+
* Confirm unpublish operation.
|
|
2826
|
+
* @param collectionLabel The collection label
|
|
2827
|
+
* @returns Promise resolving to true if confirmed
|
|
2828
|
+
*/
|
|
2829
|
+
confirmUnpublish(collectionLabel: string): Promise<boolean>;
|
|
2830
|
+
/**
|
|
2831
|
+
* Confirm restore version operation.
|
|
2832
|
+
* @param collectionLabel The collection label
|
|
2833
|
+
* @returns Promise resolving to true if confirmed
|
|
2834
|
+
*/
|
|
2835
|
+
confirmRestore(collectionLabel: string): Promise<boolean>;
|
|
2836
|
+
/**
|
|
2837
|
+
* Show success message when a version is restored.
|
|
2838
|
+
*/
|
|
2839
|
+
versionRestored(collectionLabel: string): void;
|
|
2840
|
+
/**
|
|
2841
|
+
* Show success message when a draft is saved.
|
|
2842
|
+
*/
|
|
2843
|
+
draftSaved(): void;
|
|
2844
|
+
/**
|
|
2845
|
+
* Show confirmation with custom config.
|
|
2846
|
+
*/
|
|
2847
|
+
confirm(config: {
|
|
2848
|
+
title: string;
|
|
2849
|
+
description: string;
|
|
2850
|
+
confirmText?: string;
|
|
2851
|
+
cancelText?: string;
|
|
2852
|
+
variant?: 'default' | 'destructive';
|
|
2853
|
+
}): Promise<boolean>;
|
|
2854
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<FeedbackService, never>;
|
|
2855
|
+
static ɵprov: _angular_core.ɵɵInjectableDeclaration<FeedbackService>;
|
|
2856
|
+
}
|
|
2857
|
+
|
|
2858
|
+
/**
|
|
2859
|
+
* Version History Widget
|
|
2860
|
+
*
|
|
2861
|
+
* Displays a list of document versions with the ability to restore previous versions.
|
|
2862
|
+
*
|
|
2863
|
+
* @example
|
|
2864
|
+
* ```html
|
|
2865
|
+
* <mcms-version-history
|
|
2866
|
+
* [collection]="'posts'"
|
|
2867
|
+
* [documentId]="'abc123'"
|
|
2868
|
+
* [documentLabel]="'Post'"
|
|
2869
|
+
* (restored)="onVersionRestored($event)"
|
|
2870
|
+
* />
|
|
2871
|
+
* ```
|
|
2872
|
+
*/
|
|
2873
|
+
declare class VersionHistoryWidget {
|
|
2874
|
+
private readonly versionService;
|
|
2875
|
+
private readonly feedback;
|
|
2876
|
+
private readonly dialogService;
|
|
2877
|
+
/** Collection slug */
|
|
2878
|
+
readonly collection: _angular_core.InputSignal<string>;
|
|
2879
|
+
/** Document ID */
|
|
2880
|
+
readonly documentId: _angular_core.InputSignal<string>;
|
|
2881
|
+
/** Document label for feedback messages */
|
|
2882
|
+
readonly documentLabel: _angular_core.InputSignal<string>;
|
|
2883
|
+
/** Emitted when a version is restored */
|
|
2884
|
+
readonly restored: _angular_core.OutputEmitterRef<DocumentVersionParsed<Record<string, unknown>>>;
|
|
2885
|
+
/** Versions list */
|
|
2886
|
+
readonly versions: _angular_core.WritableSignal<DocumentVersionParsed<Record<string, unknown>>[]>;
|
|
2887
|
+
/** Whether versions are loading */
|
|
2888
|
+
readonly isLoading: _angular_core.WritableSignal<boolean>;
|
|
2889
|
+
/** Whether more versions are loading */
|
|
2890
|
+
readonly isLoadingMore: _angular_core.WritableSignal<boolean>;
|
|
2891
|
+
/** Whether a restore is in progress */
|
|
2892
|
+
readonly isRestoring: _angular_core.WritableSignal<boolean>;
|
|
2893
|
+
/** ID of the version being restored */
|
|
2894
|
+
readonly restoringVersionId: _angular_core.WritableSignal<string | null>;
|
|
2895
|
+
/** Error message */
|
|
2896
|
+
readonly error: _angular_core.WritableSignal<string | null>;
|
|
2897
|
+
/** Current page */
|
|
2898
|
+
readonly currentPage: _angular_core.WritableSignal<number>;
|
|
2899
|
+
/** Whether there are more versions */
|
|
2900
|
+
readonly hasNextPage: _angular_core.WritableSignal<boolean>;
|
|
2901
|
+
constructor();
|
|
2902
|
+
/**
|
|
2903
|
+
* Load versions from the API.
|
|
2904
|
+
*/
|
|
2905
|
+
private loadVersions;
|
|
2906
|
+
/**
|
|
2907
|
+
* Load more versions.
|
|
2908
|
+
*/
|
|
2909
|
+
loadMore(): void;
|
|
2910
|
+
/**
|
|
2911
|
+
* Restore a version.
|
|
2912
|
+
*/
|
|
2913
|
+
onRestore(version: DocumentVersionParsed): Promise<void>;
|
|
2914
|
+
/**
|
|
2915
|
+
* Compare a version with the current (most recent) version.
|
|
2916
|
+
*/
|
|
2917
|
+
onCompare(version: DocumentVersionParsed): void;
|
|
2918
|
+
/**
|
|
2919
|
+
* Get badge variant for status.
|
|
2920
|
+
*/
|
|
2921
|
+
getStatusVariant(status: DocumentStatus): 'default' | 'secondary' | 'destructive' | 'outline';
|
|
2922
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<VersionHistoryWidget, never>;
|
|
2923
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<VersionHistoryWidget, "mcms-version-history", never, { "collection": { "alias": "collection"; "required": true; "isSignal": true; }; "documentId": { "alias": "documentId"; "required": true; "isSignal": true; }; "documentLabel": { "alias": "documentLabel"; "required": false; "isSignal": true; }; }, { "restored": "restored"; }, never, never, true, never>;
|
|
2924
|
+
}
|
|
2925
|
+
|
|
2926
|
+
/**
|
|
2927
|
+
* Publish Controls Widget
|
|
2928
|
+
*
|
|
2929
|
+
* Displays the current document status and provides publish/unpublish actions.
|
|
2930
|
+
*
|
|
2931
|
+
* @example
|
|
2932
|
+
* ```html
|
|
2933
|
+
* <mcms-publish-controls
|
|
2934
|
+
* [collection]="'posts'"
|
|
2935
|
+
* [documentId]="'abc123'"
|
|
2936
|
+
* [documentLabel]="'Post'"
|
|
2937
|
+
* (statusChanged)="onStatusChanged($event)"
|
|
2938
|
+
* />
|
|
2939
|
+
* ```
|
|
2940
|
+
*/
|
|
2941
|
+
declare class PublishControlsWidget {
|
|
2942
|
+
private readonly versionService;
|
|
2943
|
+
private readonly feedback;
|
|
2944
|
+
/** Collection slug */
|
|
2945
|
+
readonly collection: _angular_core.InputSignal<string>;
|
|
2946
|
+
/** Document ID */
|
|
2947
|
+
readonly documentId: _angular_core.InputSignal<string>;
|
|
2948
|
+
/** Document label for feedback messages */
|
|
2949
|
+
readonly documentLabel: _angular_core.InputSignal<string>;
|
|
2950
|
+
/** Initial status (optional, will be fetched if not provided) */
|
|
2951
|
+
readonly initialStatus: _angular_core.InputSignal<DocumentStatus | undefined>;
|
|
2952
|
+
/** Emitted when the status changes */
|
|
2953
|
+
readonly statusChanged: _angular_core.OutputEmitterRef<DocumentStatus>;
|
|
2954
|
+
/** Current status */
|
|
2955
|
+
readonly status: _angular_core.WritableSignal<DocumentStatus>;
|
|
2956
|
+
/** Whether a status update is in progress */
|
|
2957
|
+
readonly isUpdating: _angular_core.WritableSignal<boolean>;
|
|
2958
|
+
/** Whether status is loading */
|
|
2959
|
+
readonly isLoading: _angular_core.WritableSignal<boolean>;
|
|
2960
|
+
/** Badge variant based on status (derived from status signal) */
|
|
2961
|
+
readonly statusVariant: _angular_core.Signal<"default" | "secondary" | "outline">;
|
|
2962
|
+
/** Status label (derived from status signal) */
|
|
2963
|
+
readonly statusLabel: _angular_core.Signal<"Published" | "Draft">;
|
|
2964
|
+
constructor();
|
|
2965
|
+
/**
|
|
2966
|
+
* Load the current status from the API.
|
|
2967
|
+
*/
|
|
2968
|
+
private loadStatus;
|
|
2969
|
+
/**
|
|
2970
|
+
* Update the status display.
|
|
2971
|
+
* statusVariant and statusLabel are computed signals that automatically update.
|
|
2972
|
+
*/
|
|
2973
|
+
private updateStatusDisplay;
|
|
2974
|
+
/**
|
|
2975
|
+
* Publish the document.
|
|
2976
|
+
*/
|
|
2977
|
+
onPublish(): Promise<void>;
|
|
2978
|
+
/**
|
|
2979
|
+
* Unpublish the document.
|
|
2980
|
+
*/
|
|
2981
|
+
onUnpublish(): Promise<void>;
|
|
2982
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<PublishControlsWidget, never>;
|
|
2983
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<PublishControlsWidget, "mcms-publish-controls", never, { "collection": { "alias": "collection"; "required": true; "isSignal": true; }; "documentId": { "alias": "documentId"; "required": true; "isSignal": true; }; "documentLabel": { "alias": "documentLabel"; "required": false; "isSignal": true; }; "initialStatus": { "alias": "initialStatus"; "required": false; "isSignal": true; }; }, { "statusChanged": "statusChanged"; }, never, never, true, never>;
|
|
2984
|
+
}
|
|
2985
|
+
|
|
2986
|
+
/**
|
|
2987
|
+
* Media document data for preview.
|
|
2988
|
+
*/
|
|
2989
|
+
interface MediaPreviewData {
|
|
2990
|
+
url?: string;
|
|
2991
|
+
path?: string;
|
|
2992
|
+
mimeType?: string;
|
|
2993
|
+
filename?: string;
|
|
2994
|
+
alt?: string;
|
|
2995
|
+
}
|
|
2996
|
+
/**
|
|
2997
|
+
* Media Preview Component
|
|
2998
|
+
*
|
|
2999
|
+
* Displays a preview of media based on its type:
|
|
3000
|
+
* - Images: Thumbnail preview
|
|
3001
|
+
* - Videos: Video icon with optional poster
|
|
3002
|
+
* - Audio: Audio icon
|
|
3003
|
+
* - Documents: Document icon
|
|
3004
|
+
* - Other: Generic file icon
|
|
3005
|
+
*
|
|
3006
|
+
* @example
|
|
3007
|
+
* ```html
|
|
3008
|
+
* <mcms-media-preview
|
|
3009
|
+
* [media]="mediaDocument"
|
|
3010
|
+
* [size]="'md'"
|
|
3011
|
+
* />
|
|
3012
|
+
* ```
|
|
3013
|
+
*/
|
|
3014
|
+
declare class MediaPreviewComponent {
|
|
3015
|
+
/** Media data to preview */
|
|
3016
|
+
readonly media: _angular_core.InputSignal<MediaPreviewData | null>;
|
|
3017
|
+
/** Size of the preview */
|
|
3018
|
+
readonly size: _angular_core.InputSignal<"xs" | "sm" | "md" | "lg" | "xl">;
|
|
3019
|
+
/** Custom class override */
|
|
3020
|
+
readonly class: _angular_core.InputSignal<string>;
|
|
3021
|
+
/** Whether to show rounded corners */
|
|
3022
|
+
readonly rounded: _angular_core.InputSignal<boolean>;
|
|
3023
|
+
/** Host classes */
|
|
3024
|
+
readonly hostClasses: _angular_core.Signal<string>;
|
|
3025
|
+
/** Size classes map */
|
|
3026
|
+
private readonly sizeClasses;
|
|
3027
|
+
/** Icon size classes */
|
|
3028
|
+
readonly iconClasses: _angular_core.Signal<string>;
|
|
3029
|
+
/** Whether the media is an image */
|
|
3030
|
+
readonly isImage: _angular_core.Signal<boolean>;
|
|
3031
|
+
/** Whether the media is a video */
|
|
3032
|
+
readonly isVideo: _angular_core.Signal<boolean>;
|
|
3033
|
+
/** Whether the media is audio */
|
|
3034
|
+
readonly isAudio: _angular_core.Signal<boolean>;
|
|
3035
|
+
/** Image URL for preview */
|
|
3036
|
+
readonly imageUrl: _angular_core.Signal<string>;
|
|
3037
|
+
/** Icon name based on media type */
|
|
3038
|
+
readonly iconName: _angular_core.Signal<"<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3.375 19.5h17.25m-17.25 0a1.125 1.125 0 0 1-1.125-1.125M3.375 19.5h1.5C5.496 19.5 6 18.996 6 18.375m-3.75 0V5.625m0 12.75v-1.5c0-.621.504-1.125 1.125-1.125m18.375 2.625V5.625m0 12.75c0 .621-.504 1.125-1.125 1.125m1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125m0 3.75h-1.5A1.125 1.125 0 0 1 18 18.375M20.625 4.5H3.375m17.25 0c.621 0 1.125.504 1.125 1.125M20.625 4.5h-1.5C18.504 4.5 18 5.004 18 5.625m3.75 0v1.5c0 .621-.504 1.125-1.125 1.125M3.375 4.5c-.621 0-1.125.504-1.125 1.125M3.375 4.5h1.5C5.496 4.5 6 5.004 6 5.625m-3.75 0v1.5c0 .621.504 1.125 1.125 1.125m0 0h1.5m-1.5 0c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125m1.5-3.75C5.496 8.25 6 7.746 6 7.125v-1.5M4.875 8.25C5.496 8.25 6 8.754 6 9.375v1.5m0-5.25v5.25m0-5.25C6 5.004 6.504 4.5 7.125 4.5h9.75c.621 0 1.125.504 1.125 1.125m1.125 2.625h1.5m-1.5 0A1.125 1.125 0 0 1 18 7.125v-1.5m1.125 2.625c-.621 0-1.125.504-1.125 1.125v1.5m2.625-2.625c.621 0 1.125.504 1.125 1.125v1.5c0 .621-.504 1.125-1.125 1.125M18 5.625v5.25M7.125 12h9.75m-9.75 0A1.125 1.125 0 0 1 6 10.875M7.125 12C6.504 12 6 12.504 6 13.125m0-2.25C6 11.496 5.496 12 4.875 12M18 10.875c0 .621-.504 1.125-1.125 1.125M18 10.875c0 .621.504 1.125 1.125 1.125m-2.25 0c.621 0 1.125.504 1.125 1.125m-12 5.25v-5.25m0 5.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125m-12 0v-1.5c0-.621-.504-1.125-1.125-1.125M18 18.375v-5.25m0 5.25v-1.5c0-.621.504-1.125 1.125-1.125M18 13.125v1.5c0 .621.504 1.125 1.125 1.125M18 13.125c0-.621.504-1.125 1.125-1.125M6 13.125v1.5c0 .621-.504 1.125-1.125 1.125M6 13.125C6 12.504 5.496 12 4.875 12m-1.5 0h1.5m-1.5 0c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125M19.125 12h1.5m0 0c.621 0 1.125.504 1.125 1.125v1.5c0 .621-.504 1.125-1.125 1.125m-17.25 0h1.5m14.25 0h1.5\"></path></svg>" | "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m9 9 10.5-3m0 6.553v3.75a2.25 2.25 0 0 1-1.632 2.163l-1.32.377a1.803 1.803 0 1 1-.99-3.467l2.31-.66a2.25 2.25 0 0 0 1.632-2.163Zm0 0V2.25L9 5.25v10.303m0 0v3.75a2.25 2.25 0 0 1-1.632 2.163l-1.32.377a1.803 1.803 0 0 1-.99-3.467l2.31-.66A2.25 2.25 0 0 0 9 15.553Z\"></path></svg>" | "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z\"></path></svg>" | "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m20.25 7.5-.625 10.632a2.25 2.25 0 0 1-2.247 2.118H6.622a2.25 2.25 0 0 1-2.247-2.118L3.75 7.5M10 11.25h4M3.375 7.5h17.25c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125Z\"></path></svg>" | "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 0 0 1.5-1.5V6a1.5 1.5 0 0 0-1.5-1.5H3.75A1.5 1.5 0 0 0 2.25 6v12a1.5 1.5 0 0 0 1.5 1.5Zm10.5-11.25h.008v.008h-.008V8.25Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z\"></path></svg>" | "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z\"></path></svg>">;
|
|
3039
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<MediaPreviewComponent, never>;
|
|
3040
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<MediaPreviewComponent, "mcms-media-preview", never, { "media": { "alias": "media"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "class": { "alias": "class"; "required": false; "isSignal": true; }; "rounded": { "alias": "rounded"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
3041
|
+
}
|
|
3042
|
+
|
|
3043
|
+
/** Device size preset for the preview iframe. */
|
|
3044
|
+
type DeviceSize = 'desktop' | 'tablet' | 'mobile';
|
|
3045
|
+
/**
|
|
3046
|
+
* Live Preview Widget
|
|
3047
|
+
*
|
|
3048
|
+
* Displays an iframe that shows a live preview of the document being edited.
|
|
3049
|
+
* Sends form data to the iframe via postMessage on each change.
|
|
3050
|
+
*/
|
|
3051
|
+
declare class LivePreviewComponent {
|
|
3052
|
+
private readonly document;
|
|
3053
|
+
private readonly sanitizer;
|
|
3054
|
+
private readonly destroyRef;
|
|
3055
|
+
/** Preview configuration from collection admin config */
|
|
3056
|
+
readonly preview: _angular_core.InputSignal<boolean | ((doc: Record<string, unknown>) => string)>;
|
|
3057
|
+
/** Current document data from the form */
|
|
3058
|
+
readonly documentData: _angular_core.InputSignal<Record<string, unknown>>;
|
|
3059
|
+
/** Collection slug */
|
|
3060
|
+
readonly collectionSlug: _angular_core.InputSignal<string>;
|
|
3061
|
+
/** Document ID (undefined for create mode) */
|
|
3062
|
+
readonly entityId: _angular_core.InputSignal<string | undefined>;
|
|
3063
|
+
/** Emitted when the preview iframe requests editing a block */
|
|
3064
|
+
readonly editBlockRequest: _angular_core.OutputEmitterRef<number>;
|
|
3065
|
+
/** Current device size */
|
|
3066
|
+
readonly deviceSize: _angular_core.WritableSignal<DeviceSize>;
|
|
3067
|
+
/** Refresh counter to force iframe reload */
|
|
3068
|
+
private readonly refreshCounter;
|
|
3069
|
+
/** Reference to the iframe element */
|
|
3070
|
+
readonly previewFrame: _angular_core.Signal<ElementRef<HTMLIFrameElement> | undefined>;
|
|
3071
|
+
/** Compute the raw preview URL */
|
|
3072
|
+
readonly previewUrl: _angular_core.Signal<string | null>;
|
|
3073
|
+
/** Sanitized preview URL for iframe binding */
|
|
3074
|
+
readonly safePreviewUrl: _angular_core.Signal<SafeResourceUrl | null>;
|
|
3075
|
+
/** Computed iframe width based on device size */
|
|
3076
|
+
readonly iframeWidth: _angular_core.Signal<string>;
|
|
3077
|
+
/** Debounce timer for postMessage updates */
|
|
3078
|
+
private debounceTimer;
|
|
3079
|
+
constructor();
|
|
3080
|
+
/** Force iframe to reload */
|
|
3081
|
+
refreshPreview(): void;
|
|
3082
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<LivePreviewComponent, never>;
|
|
3083
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<LivePreviewComponent, "mcms-live-preview", never, { "preview": { "alias": "preview"; "required": true; "isSignal": true; }; "documentData": { "alias": "documentData"; "required": true; "isSignal": true; }; "collectionSlug": { "alias": "collectionSlug"; "required": true; "isSignal": true; }; "entityId": { "alias": "entityId"; "required": false; "isSignal": true; }; }, { "editBlockRequest": "editBlockRequest"; }, never, never, true, never>;
|
|
3084
|
+
}
|
|
3085
|
+
|
|
3086
|
+
/**
|
|
3087
|
+
* Visual Block Editor - Shared Types
|
|
3088
|
+
*
|
|
3089
|
+
* Types, interfaces, and utilities for the WYSIWYG visual block editor.
|
|
3090
|
+
*/
|
|
3091
|
+
|
|
3092
|
+
/** Shape of a block item in the stored data */
|
|
3093
|
+
interface BlockItem {
|
|
3094
|
+
blockType: string;
|
|
3095
|
+
[key: string]: unknown;
|
|
3096
|
+
}
|
|
3097
|
+
/** State for the visual block editor */
|
|
3098
|
+
interface VisualEditorState {
|
|
3099
|
+
readonly selectedBlockIndex: WritableSignal<number | null>;
|
|
3100
|
+
readonly hoveredBlockIndex: WritableSignal<number | null>;
|
|
3101
|
+
readonly inserterOpen: WritableSignal<{
|
|
3102
|
+
index: number;
|
|
3103
|
+
} | null>;
|
|
3104
|
+
readonly collapsedBlocks: WritableSignal<Set<number>>;
|
|
3105
|
+
}
|
|
3106
|
+
|
|
3107
|
+
declare class VisualBlockEditorComponent {
|
|
3108
|
+
/** Field definition (must be a BlocksField) */
|
|
3109
|
+
readonly field: _angular_core.InputSignal<Field>;
|
|
3110
|
+
/** Signal forms FieldTree node for this blocks array */
|
|
3111
|
+
readonly formNode: _angular_core.InputSignal<unknown>;
|
|
3112
|
+
/** Root signal forms FieldTree */
|
|
3113
|
+
readonly formTree: _angular_core.InputSignal<unknown>;
|
|
3114
|
+
/** Form model data */
|
|
3115
|
+
readonly formModel: _angular_core.InputSignal<Record<string, unknown>>;
|
|
3116
|
+
/** Form mode */
|
|
3117
|
+
readonly mode: _angular_core.InputSignal<EntityFormMode>;
|
|
3118
|
+
/** Field path */
|
|
3119
|
+
readonly path: _angular_core.InputSignal<string>;
|
|
3120
|
+
/** Editor state */
|
|
3121
|
+
readonly editorState: VisualEditorState;
|
|
3122
|
+
/** Bridge: extract FieldState from formNode */
|
|
3123
|
+
private readonly nodeState;
|
|
3124
|
+
/** Computed label */
|
|
3125
|
+
readonly label: _angular_core.Signal<string>;
|
|
3126
|
+
/** Computed description */
|
|
3127
|
+
readonly description: _angular_core.Signal<string>;
|
|
3128
|
+
/** Block type definitions from the field */
|
|
3129
|
+
readonly blockDefinitions: _angular_core.Signal<BlockConfig[]>;
|
|
3130
|
+
readonly minRows: _angular_core.Signal<number>;
|
|
3131
|
+
readonly maxRows: _angular_core.Signal<number | undefined>;
|
|
3132
|
+
/** Current blocks as typed items */
|
|
3133
|
+
readonly blocks: _angular_core.Signal<BlockItem[]>;
|
|
3134
|
+
readonly isDisabled: _angular_core.Signal<boolean>;
|
|
3135
|
+
/**
|
|
3136
|
+
* Normalize loaded blocks: ensure every block has defaults for all fields
|
|
3137
|
+
* defined in its block definition. This is needed because blocks saved before
|
|
3138
|
+
* a new field was added (e.g. _analytics) won't have those keys, and the
|
|
3139
|
+
* signal-forms tree only creates controls for keys present in the model.
|
|
3140
|
+
*/
|
|
3141
|
+
private readonly _normalizeBlocks;
|
|
3142
|
+
readonly canRemoveBlock: _angular_core.Signal<boolean>;
|
|
3143
|
+
/** Block definition lookup cache */
|
|
3144
|
+
private readonly blockDefMap;
|
|
3145
|
+
/** Fallback empty block config for unknown types */
|
|
3146
|
+
private readonly emptyBlockConfig;
|
|
3147
|
+
getBlockLabel(blockType: string): string;
|
|
3148
|
+
getBlockConfig(blockType: string): BlockConfig;
|
|
3149
|
+
isBlockCollapsed(index: number): boolean;
|
|
3150
|
+
toggleBlockCollapse(index: number): void;
|
|
3151
|
+
/** Shift collapsed indices after a block is added at atIndex */
|
|
3152
|
+
private shiftCollapsedUp;
|
|
3153
|
+
/** Shift collapsed indices after a block is removed at removedIndex */
|
|
3154
|
+
private shiftCollapsedDown;
|
|
3155
|
+
/** Swap collapsed state between two indices */
|
|
3156
|
+
private swapCollapsed;
|
|
3157
|
+
addBlock(blockType: string, atIndex: number): void;
|
|
3158
|
+
removeBlock(index: number): void;
|
|
3159
|
+
moveBlock(from: number, to: number): void;
|
|
3160
|
+
selectBlock(index: number): void;
|
|
3161
|
+
onDrop(event: CdkDragDrop<unknown>): void;
|
|
3162
|
+
onKeydown(event: KeyboardEvent): void;
|
|
3163
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<VisualBlockEditorComponent, never>;
|
|
3164
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<VisualBlockEditorComponent, "mcms-visual-block-editor", never, { "field": { "alias": "field"; "required": true; "isSignal": true; }; "formNode": { "alias": "formNode"; "required": false; "isSignal": true; }; "formTree": { "alias": "formTree"; "required": false; "isSignal": true; }; "formModel": { "alias": "formModel"; "required": false; "isSignal": true; }; "mode": { "alias": "mode"; "required": false; "isSignal": true; }; "path": { "alias": "path"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
3165
|
+
}
|
|
3166
|
+
|
|
3167
|
+
declare class BlockWrapperComponent {
|
|
3168
|
+
/** The block data */
|
|
3169
|
+
readonly block: _angular_core.InputSignal<BlockItem>;
|
|
3170
|
+
/** Block index in the blocks array */
|
|
3171
|
+
readonly blockIndex: _angular_core.InputSignal<number>;
|
|
3172
|
+
/** Block type configuration */
|
|
3173
|
+
readonly blockConfig: _angular_core.InputSignal<BlockConfig>;
|
|
3174
|
+
/** Human-readable block type label */
|
|
3175
|
+
readonly blockLabel: _angular_core.InputSignal<string>;
|
|
3176
|
+
/** Whether this block is currently selected */
|
|
3177
|
+
readonly isSelected: _angular_core.InputSignal<boolean>;
|
|
3178
|
+
/** Whether this block is currently hovered */
|
|
3179
|
+
readonly isHovered: _angular_core.InputSignal<boolean>;
|
|
3180
|
+
/** Whether the editor is in view/disabled mode */
|
|
3181
|
+
readonly isDisabled: _angular_core.InputSignal<boolean>;
|
|
3182
|
+
/** Whether this block is collapsed (fields hidden) */
|
|
3183
|
+
readonly isCollapsed: _angular_core.InputSignal<boolean>;
|
|
3184
|
+
/** Whether the block can be moved up */
|
|
3185
|
+
readonly canMoveUp: _angular_core.InputSignal<boolean>;
|
|
3186
|
+
/** Whether the block can be moved down */
|
|
3187
|
+
readonly canMoveDown: _angular_core.InputSignal<boolean>;
|
|
3188
|
+
/** Whether the block can be deleted */
|
|
3189
|
+
readonly canDelete: _angular_core.InputSignal<boolean>;
|
|
3190
|
+
/** Signal forms node for the blocks array */
|
|
3191
|
+
readonly formNode: _angular_core.InputSignal<unknown>;
|
|
3192
|
+
/** Root signal forms FieldTree */
|
|
3193
|
+
readonly formTree: _angular_core.InputSignal<unknown>;
|
|
3194
|
+
/** Form model data */
|
|
3195
|
+
readonly formModel: _angular_core.InputSignal<Record<string, unknown>>;
|
|
3196
|
+
/** Form mode */
|
|
3197
|
+
readonly mode: _angular_core.InputSignal<EntityFormMode>;
|
|
3198
|
+
/** Field path prefix (e.g., "content") */
|
|
3199
|
+
readonly path: _angular_core.InputSignal<string>;
|
|
3200
|
+
/** Emitted when the block is clicked/focused */
|
|
3201
|
+
readonly selected: _angular_core.OutputEmitterRef<void>;
|
|
3202
|
+
/** Emitted when the mouse enters */
|
|
3203
|
+
readonly hoverStart: _angular_core.OutputEmitterRef<void>;
|
|
3204
|
+
/** Emitted when the mouse leaves */
|
|
3205
|
+
readonly hoverEnd: _angular_core.OutputEmitterRef<void>;
|
|
3206
|
+
/** Emitted when the move up button is clicked */
|
|
3207
|
+
readonly moveUp: _angular_core.OutputEmitterRef<void>;
|
|
3208
|
+
/** Emitted when the move down button is clicked */
|
|
3209
|
+
readonly moveDown: _angular_core.OutputEmitterRef<void>;
|
|
3210
|
+
/** Emitted when the delete button is clicked */
|
|
3211
|
+
readonly deleteBlock: _angular_core.OutputEmitterRef<void>;
|
|
3212
|
+
/** Emitted when the collapse toggle is clicked */
|
|
3213
|
+
readonly toggleCollapse: _angular_core.OutputEmitterRef<void>;
|
|
3214
|
+
/** Visible fields (not hidden by admin config) */
|
|
3215
|
+
readonly visibleFields: _angular_core.Signal<Field[]>;
|
|
3216
|
+
readonly blockAriaLabel: _angular_core.Signal<string>;
|
|
3217
|
+
/** Get a FieldTree sub-node for a block's sub-field */
|
|
3218
|
+
getBlockSubNode(fieldName: string): unknown;
|
|
3219
|
+
/** Get the full path for a block's sub-field */
|
|
3220
|
+
getBlockSubFieldPath(fieldName: string): string;
|
|
3221
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<BlockWrapperComponent, never>;
|
|
3222
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<BlockWrapperComponent, "mcms-block-wrapper", never, { "block": { "alias": "block"; "required": true; "isSignal": true; }; "blockIndex": { "alias": "blockIndex"; "required": true; "isSignal": true; }; "blockConfig": { "alias": "blockConfig"; "required": true; "isSignal": true; }; "blockLabel": { "alias": "blockLabel"; "required": true; "isSignal": true; }; "isSelected": { "alias": "isSelected"; "required": false; "isSignal": true; }; "isHovered": { "alias": "isHovered"; "required": false; "isSignal": true; }; "isDisabled": { "alias": "isDisabled"; "required": false; "isSignal": true; }; "isCollapsed": { "alias": "isCollapsed"; "required": false; "isSignal": true; }; "canMoveUp": { "alias": "canMoveUp"; "required": false; "isSignal": true; }; "canMoveDown": { "alias": "canMoveDown"; "required": false; "isSignal": true; }; "canDelete": { "alias": "canDelete"; "required": false; "isSignal": true; }; "formNode": { "alias": "formNode"; "required": false; "isSignal": true; }; "formTree": { "alias": "formTree"; "required": false; "isSignal": true; }; "formModel": { "alias": "formModel"; "required": false; "isSignal": true; }; "mode": { "alias": "mode"; "required": false; "isSignal": true; }; "path": { "alias": "path"; "required": false; "isSignal": true; }; }, { "selected": "selected"; "hoverStart": "hoverStart"; "hoverEnd": "hoverEnd"; "moveUp": "moveUp"; "moveDown": "moveDown"; "deleteBlock": "deleteBlock"; "toggleCollapse": "toggleCollapse"; }, never, never, true, never>;
|
|
3223
|
+
}
|
|
3224
|
+
|
|
3225
|
+
declare class BlockInserterComponent {
|
|
3226
|
+
/** Position where the new block will be inserted */
|
|
3227
|
+
readonly insertIndex: _angular_core.InputSignal<number>;
|
|
3228
|
+
/** Available block type definitions */
|
|
3229
|
+
readonly blockDefinitions: _angular_core.InputSignal<BlockConfig[]>;
|
|
3230
|
+
/** Whether the inserter is disabled */
|
|
3231
|
+
readonly disabled: _angular_core.InputSignal<boolean>;
|
|
3232
|
+
/** Emitted when a block type is selected */
|
|
3233
|
+
readonly blockTypeSelected: _angular_core.OutputEmitterRef<{
|
|
3234
|
+
blockType: string;
|
|
3235
|
+
atIndex: number;
|
|
3236
|
+
}>;
|
|
3237
|
+
readonly popover: _angular_core.Signal<PopoverTrigger | undefined>;
|
|
3238
|
+
readonly searchQuery: _angular_core.WritableSignal<string>;
|
|
3239
|
+
readonly filteredBlocks: _angular_core.Signal<BlockConfig[]>;
|
|
3240
|
+
selectBlockType(slug: string): void;
|
|
3241
|
+
onPopoverOpened(): void;
|
|
3242
|
+
onPopoverClosed(): void;
|
|
3243
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<BlockInserterComponent, never>;
|
|
3244
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<BlockInserterComponent, "mcms-block-inserter", never, { "insertIndex": { "alias": "insertIndex"; "required": true; "isSignal": true; }; "blockDefinitions": { "alias": "blockDefinitions"; "required": true; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; }, { "blockTypeSelected": "blockTypeSelected"; }, never, never, true, never>;
|
|
3245
|
+
}
|
|
3246
|
+
|
|
3247
|
+
/** Data passed to the BlockEditDialog via DIALOG_DATA. */
|
|
3248
|
+
interface BlockEditDialogData {
|
|
3249
|
+
blockConfig: BlockConfig;
|
|
3250
|
+
formNode: unknown;
|
|
3251
|
+
blockIndex: number;
|
|
3252
|
+
formTree: unknown;
|
|
3253
|
+
formModel: Record<string, unknown>;
|
|
3254
|
+
mode: EntityFormMode;
|
|
3255
|
+
path: string;
|
|
3256
|
+
}
|
|
3257
|
+
declare class BlockEditDialog {
|
|
3258
|
+
readonly data: BlockEditDialogData;
|
|
3259
|
+
private readonly dialogRef;
|
|
3260
|
+
readonly blockLabel: string;
|
|
3261
|
+
readonly visibleFields: Field[];
|
|
3262
|
+
getFieldNode(fieldName: string): unknown;
|
|
3263
|
+
getFieldPath(fieldName: string): string;
|
|
3264
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<BlockEditDialog, never>;
|
|
3265
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<BlockEditDialog, "mcms-block-edit-dialog", never, {}, {}, never, never, true, never>;
|
|
3266
|
+
}
|
|
3267
|
+
|
|
3268
|
+
/**
|
|
3269
|
+
* Dynamic field renderer that switches based on field type.
|
|
3270
|
+
*
|
|
3271
|
+
* Uses Angular Signal Forms bridge pattern: each renderer receives
|
|
3272
|
+
* its formNode (FieldTree) and reads/writes values via FieldState.
|
|
3273
|
+
*/
|
|
3274
|
+
declare class FieldRenderer {
|
|
3275
|
+
/** Field definition */
|
|
3276
|
+
readonly field: _angular_core.InputSignal<Field>;
|
|
3277
|
+
/** Signal forms FieldTree node for this field */
|
|
3278
|
+
readonly formNode: _angular_core.InputSignal<unknown>;
|
|
3279
|
+
/** Root signal forms FieldTree (for layout fields that look up child nodes) */
|
|
3280
|
+
readonly formTree: _angular_core.InputSignal<unknown>;
|
|
3281
|
+
/** Form model data (for condition evaluation and relationship filterOptions) */
|
|
3282
|
+
readonly formModel: _angular_core.InputSignal<Record<string, unknown>>;
|
|
3283
|
+
/** Form mode */
|
|
3284
|
+
readonly mode: _angular_core.InputSignal<EntityFormMode>;
|
|
3285
|
+
/** Field path (for nested fields) */
|
|
3286
|
+
readonly path: _angular_core.InputSignal<string>;
|
|
3287
|
+
/** Field type for template switching */
|
|
3288
|
+
readonly fieldType: _angular_core.Signal<"number" | "slug" | "email" | "text" | "textarea" | "richText" | "date" | "checkbox" | "select" | "radio" | "password" | "upload" | "relationship" | "array" | "group" | "blocks" | "json" | "point" | "tabs" | "collapsible" | "row">;
|
|
3289
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<FieldRenderer, never>;
|
|
3290
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<FieldRenderer, "mcms-field-renderer", never, { "field": { "alias": "field"; "required": true; "isSignal": true; }; "formNode": { "alias": "formNode"; "required": false; "isSignal": true; }; "formTree": { "alias": "formTree"; "required": false; "isSignal": true; }; "formModel": { "alias": "formModel"; "required": false; "isSignal": true; }; "mode": { "alias": "mode"; "required": false; "isSignal": true; }; "path": { "alias": "path"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
3291
|
+
}
|
|
3292
|
+
|
|
3293
|
+
/**
|
|
3294
|
+
* Text field renderer for text and textarea field types.
|
|
3295
|
+
*
|
|
3296
|
+
* Uses Angular Signal Forms bridge pattern: reads/writes value via
|
|
3297
|
+
* a FieldTree node's FieldState rather than event-based I/O.
|
|
3298
|
+
*/
|
|
3299
|
+
declare class TextFieldRenderer {
|
|
3300
|
+
/** Field definition */
|
|
3301
|
+
readonly field: _angular_core.InputSignal<Field>;
|
|
3302
|
+
/** Signal forms FieldTree node for this field */
|
|
3303
|
+
readonly formNode: _angular_core.InputSignal<unknown>;
|
|
3304
|
+
/** Form mode */
|
|
3305
|
+
readonly mode: _angular_core.InputSignal<EntityFormMode>;
|
|
3306
|
+
/** Field path */
|
|
3307
|
+
readonly path: _angular_core.InputSignal<string>;
|
|
3308
|
+
/** Bridge: extract FieldState from formNode */
|
|
3309
|
+
private readonly nodeState;
|
|
3310
|
+
/** Unique field ID */
|
|
3311
|
+
readonly fieldId: _angular_core.Signal<string>;
|
|
3312
|
+
/** Computed label */
|
|
3313
|
+
readonly label: _angular_core.Signal<string>;
|
|
3314
|
+
/** Whether the field is required */
|
|
3315
|
+
readonly required: _angular_core.Signal<boolean>;
|
|
3316
|
+
/** Placeholder text */
|
|
3317
|
+
readonly placeholder: _angular_core.Signal<string>;
|
|
3318
|
+
/** Whether the field is disabled */
|
|
3319
|
+
readonly isDisabled: _angular_core.Signal<boolean>;
|
|
3320
|
+
/** Whether to use textarea */
|
|
3321
|
+
readonly isTextarea: _angular_core.Signal<boolean>;
|
|
3322
|
+
/** Input type (text, email, etc.) */
|
|
3323
|
+
readonly inputType: _angular_core.Signal<"email" | "text">;
|
|
3324
|
+
/** Number of rows for textarea */
|
|
3325
|
+
readonly rows: _angular_core.Signal<number>;
|
|
3326
|
+
/** String value from FieldState */
|
|
3327
|
+
readonly stringValue: _angular_core.Signal<string>;
|
|
3328
|
+
/** Field description */
|
|
3329
|
+
readonly description: _angular_core.Signal<string>;
|
|
3330
|
+
/** Max length from field constraints */
|
|
3331
|
+
readonly maxLength: _angular_core.Signal<number | undefined>;
|
|
3332
|
+
/** Current character count */
|
|
3333
|
+
readonly charCount: _angular_core.Signal<number>;
|
|
3334
|
+
/** Whether to show character counter */
|
|
3335
|
+
readonly showCharCount: _angular_core.Signal<boolean>;
|
|
3336
|
+
/** Whether character count exceeds max */
|
|
3337
|
+
readonly charCountExceeded: _angular_core.Signal<boolean>;
|
|
3338
|
+
/** Validation errors shown only when field is touched */
|
|
3339
|
+
readonly touchedErrors: _angular_core.Signal<readonly ValidationError[]>;
|
|
3340
|
+
/**
|
|
3341
|
+
* Handle value change from input/textarea.
|
|
3342
|
+
*/
|
|
3343
|
+
onValueChange(value: string): void;
|
|
3344
|
+
/**
|
|
3345
|
+
* Handle blur from input/textarea.
|
|
3346
|
+
*/
|
|
3347
|
+
onBlur(): void;
|
|
3348
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<TextFieldRenderer, never>;
|
|
3349
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<TextFieldRenderer, "mcms-text-field-renderer", never, { "field": { "alias": "field"; "required": true; "isSignal": true; }; "formNode": { "alias": "formNode"; "required": false; "isSignal": true; }; "mode": { "alias": "mode"; "required": false; "isSignal": true; }; "path": { "alias": "path"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
3350
|
+
}
|
|
3351
|
+
|
|
3352
|
+
/**
|
|
3353
|
+
* Number field renderer.
|
|
3354
|
+
*
|
|
3355
|
+
* Uses Angular Signal Forms bridge pattern: reads/writes value via
|
|
3356
|
+
* a FieldTree node's FieldState rather than event-based I/O.
|
|
3357
|
+
*
|
|
3358
|
+
* NOTE: No clamping is applied on value change. Signal forms validators
|
|
3359
|
+
* handle min/max constraint errors. HTML min/max/step attrs still set
|
|
3360
|
+
* for browser-native spinner behavior.
|
|
3361
|
+
*/
|
|
3362
|
+
declare class NumberFieldRenderer {
|
|
3363
|
+
/** Field definition */
|
|
3364
|
+
readonly field: _angular_core.InputSignal<Field>;
|
|
3365
|
+
/** Signal forms FieldTree node for this field */
|
|
3366
|
+
readonly formNode: _angular_core.InputSignal<unknown>;
|
|
3367
|
+
/** Form mode */
|
|
3368
|
+
readonly mode: _angular_core.InputSignal<EntityFormMode>;
|
|
3369
|
+
/** Field path */
|
|
3370
|
+
readonly path: _angular_core.InputSignal<string>;
|
|
3371
|
+
/** Bridge: extract FieldState from formNode */
|
|
3372
|
+
private readonly nodeState;
|
|
3373
|
+
/** Unique field ID */
|
|
3374
|
+
readonly fieldId: _angular_core.Signal<string>;
|
|
3375
|
+
/** Computed label */
|
|
3376
|
+
readonly label: _angular_core.Signal<string>;
|
|
3377
|
+
/** Whether the field is required */
|
|
3378
|
+
readonly required: _angular_core.Signal<boolean>;
|
|
3379
|
+
/** Placeholder text */
|
|
3380
|
+
readonly placeholder: _angular_core.Signal<string>;
|
|
3381
|
+
/** Whether the field is disabled */
|
|
3382
|
+
readonly isDisabled: _angular_core.Signal<boolean>;
|
|
3383
|
+
/** String value from FieldState */
|
|
3384
|
+
readonly stringValue: _angular_core.Signal<string>;
|
|
3385
|
+
/** Min constraint from field definition */
|
|
3386
|
+
readonly minValue: _angular_core.Signal<number | undefined>;
|
|
3387
|
+
/** Max constraint from field definition */
|
|
3388
|
+
readonly maxValue: _angular_core.Signal<number | undefined>;
|
|
3389
|
+
/** Step constraint from field definition */
|
|
3390
|
+
readonly stepValue: _angular_core.Signal<number | undefined>;
|
|
3391
|
+
/** Range hint text based on min/max/step constraints */
|
|
3392
|
+
readonly rangeHint: _angular_core.Signal<string>;
|
|
3393
|
+
/** Validation errors shown only when field is touched */
|
|
3394
|
+
readonly touchedErrors: _angular_core.Signal<readonly ValidationError[]>;
|
|
3395
|
+
/**
|
|
3396
|
+
* Handle value change from input.
|
|
3397
|
+
* No clamping — let signal forms validators show min/max errors.
|
|
3398
|
+
*/
|
|
3399
|
+
onValueChange(value: string): void;
|
|
3400
|
+
/**
|
|
3401
|
+
* Handle blur from input.
|
|
3402
|
+
*/
|
|
3403
|
+
onBlur(): void;
|
|
3404
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<NumberFieldRenderer, never>;
|
|
3405
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<NumberFieldRenderer, "mcms-number-field-renderer", never, { "field": { "alias": "field"; "required": true; "isSignal": true; }; "formNode": { "alias": "formNode"; "required": false; "isSignal": true; }; "mode": { "alias": "mode"; "required": false; "isSignal": true; }; "path": { "alias": "path"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
3406
|
+
}
|
|
3407
|
+
|
|
3408
|
+
/**
|
|
3409
|
+
* Select field renderer.
|
|
3410
|
+
*
|
|
3411
|
+
* Uses Angular Signal Forms bridge pattern: reads/writes value via
|
|
3412
|
+
* a FieldTree node's FieldState rather than event-based I/O.
|
|
3413
|
+
*/
|
|
3414
|
+
declare class SelectFieldRenderer {
|
|
3415
|
+
/** Field definition */
|
|
3416
|
+
readonly field: _angular_core.InputSignal<Field>;
|
|
3417
|
+
/** Signal forms FieldTree node for this field */
|
|
3418
|
+
readonly formNode: _angular_core.InputSignal<unknown>;
|
|
3419
|
+
/** Form mode */
|
|
3420
|
+
readonly mode: _angular_core.InputSignal<EntityFormMode>;
|
|
3421
|
+
/** Field path */
|
|
3422
|
+
readonly path: _angular_core.InputSignal<string>;
|
|
3423
|
+
/** Bridge: extract FieldState from formNode */
|
|
3424
|
+
private readonly nodeState;
|
|
3425
|
+
/** Unique field ID */
|
|
3426
|
+
readonly fieldId: _angular_core.Signal<string>;
|
|
3427
|
+
/** Computed label */
|
|
3428
|
+
readonly label: _angular_core.Signal<string>;
|
|
3429
|
+
/** Whether the field is required */
|
|
3430
|
+
readonly required: _angular_core.Signal<boolean>;
|
|
3431
|
+
/** Placeholder text */
|
|
3432
|
+
readonly placeholder: _angular_core.Signal<string>;
|
|
3433
|
+
/** Whether the field is disabled */
|
|
3434
|
+
readonly isDisabled: _angular_core.Signal<boolean>;
|
|
3435
|
+
/** Select options (converted to UI SelectOption format with string values) */
|
|
3436
|
+
readonly selectOptions: _angular_core.Signal<SelectOption[]>;
|
|
3437
|
+
/** String value from FieldState */
|
|
3438
|
+
readonly stringValue: _angular_core.Signal<string>;
|
|
3439
|
+
/** Validation errors shown only when field is touched */
|
|
3440
|
+
readonly touchedErrors: _angular_core.Signal<readonly ValidationError[]>;
|
|
3441
|
+
/**
|
|
3442
|
+
* Handle value change from select.
|
|
3443
|
+
* Marks touched immediately since select has no native blur flow.
|
|
3444
|
+
*/
|
|
3445
|
+
onValueChange(value: string): void;
|
|
3446
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<SelectFieldRenderer, never>;
|
|
3447
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<SelectFieldRenderer, "mcms-select-field-renderer", never, { "field": { "alias": "field"; "required": true; "isSignal": true; }; "formNode": { "alias": "formNode"; "required": false; "isSignal": true; }; "mode": { "alias": "mode"; "required": false; "isSignal": true; }; "path": { "alias": "path"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
3448
|
+
}
|
|
3449
|
+
|
|
3450
|
+
/**
|
|
3451
|
+
* Checkbox field renderer.
|
|
3452
|
+
*
|
|
3453
|
+
* Uses Angular Signal Forms bridge pattern: reads/writes value via
|
|
3454
|
+
* a FieldTree node's FieldState rather than event-based I/O.
|
|
3455
|
+
*/
|
|
3456
|
+
declare class CheckboxFieldRenderer {
|
|
3457
|
+
/** Field definition */
|
|
3458
|
+
readonly field: _angular_core.InputSignal<Field>;
|
|
3459
|
+
/** Signal forms FieldTree node for this field */
|
|
3460
|
+
readonly formNode: _angular_core.InputSignal<unknown>;
|
|
3461
|
+
/** Form mode */
|
|
3462
|
+
readonly mode: _angular_core.InputSignal<EntityFormMode>;
|
|
3463
|
+
/** Field path */
|
|
3464
|
+
readonly path: _angular_core.InputSignal<string>;
|
|
3465
|
+
/** Bridge: extract FieldState from formNode */
|
|
3466
|
+
private readonly nodeState;
|
|
3467
|
+
/** Unique field ID */
|
|
3468
|
+
readonly fieldId: _angular_core.Signal<string>;
|
|
3469
|
+
/** Computed label */
|
|
3470
|
+
readonly label: _angular_core.Signal<string>;
|
|
3471
|
+
/** Whether the field is required */
|
|
3472
|
+
readonly required: _angular_core.Signal<boolean>;
|
|
3473
|
+
/** Field description */
|
|
3474
|
+
readonly description: _angular_core.Signal<string>;
|
|
3475
|
+
/** Whether the field is disabled */
|
|
3476
|
+
readonly isDisabled: _angular_core.Signal<boolean>;
|
|
3477
|
+
/** Boolean value from FieldState */
|
|
3478
|
+
readonly boolValue: _angular_core.Signal<boolean>;
|
|
3479
|
+
/** Validation errors shown only when field is touched */
|
|
3480
|
+
readonly touchedErrors: _angular_core.Signal<readonly ValidationError[]>;
|
|
3481
|
+
/**
|
|
3482
|
+
* Handle value change from checkbox.
|
|
3483
|
+
* Marks touched immediately since checkboxes don't have a native blur flow.
|
|
3484
|
+
*/
|
|
3485
|
+
onValueChange(value: boolean): void;
|
|
3486
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<CheckboxFieldRenderer, never>;
|
|
3487
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<CheckboxFieldRenderer, "mcms-checkbox-field-renderer", never, { "field": { "alias": "field"; "required": true; "isSignal": true; }; "formNode": { "alias": "formNode"; "required": false; "isSignal": true; }; "mode": { "alias": "mode"; "required": false; "isSignal": true; }; "path": { "alias": "path"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
3488
|
+
}
|
|
3489
|
+
|
|
3490
|
+
/**
|
|
3491
|
+
* Date field renderer.
|
|
3492
|
+
*
|
|
3493
|
+
* Uses Angular Signal Forms bridge pattern: reads/writes value via
|
|
3494
|
+
* a FieldTree node's FieldState rather than event-based I/O.
|
|
3495
|
+
*/
|
|
3496
|
+
declare class DateFieldRenderer {
|
|
3497
|
+
/** Field definition */
|
|
3498
|
+
readonly field: _angular_core.InputSignal<Field>;
|
|
3499
|
+
/** Signal forms FieldTree node for this field */
|
|
3500
|
+
readonly formNode: _angular_core.InputSignal<unknown>;
|
|
3501
|
+
/** Form mode */
|
|
3502
|
+
readonly mode: _angular_core.InputSignal<EntityFormMode>;
|
|
3503
|
+
/** Field path */
|
|
3504
|
+
readonly path: _angular_core.InputSignal<string>;
|
|
3505
|
+
/** Bridge: extract FieldState from formNode */
|
|
3506
|
+
private readonly nodeState;
|
|
3507
|
+
/** Unique field ID */
|
|
3508
|
+
readonly fieldId: _angular_core.Signal<string>;
|
|
3509
|
+
/** Computed label */
|
|
3510
|
+
readonly label: _angular_core.Signal<string>;
|
|
3511
|
+
/** Whether the field is required */
|
|
3512
|
+
readonly required: _angular_core.Signal<boolean>;
|
|
3513
|
+
/** Placeholder text */
|
|
3514
|
+
readonly placeholder: _angular_core.Signal<string>;
|
|
3515
|
+
/** Whether the field is disabled */
|
|
3516
|
+
readonly isDisabled: _angular_core.Signal<boolean>;
|
|
3517
|
+
/** Input type (date or datetime-local) */
|
|
3518
|
+
readonly inputType: _angular_core.Signal<"text">;
|
|
3519
|
+
/** Formatted date value from FieldState */
|
|
3520
|
+
readonly dateValue: _angular_core.Signal<string>;
|
|
3521
|
+
/** Validation errors shown only when field is touched */
|
|
3522
|
+
readonly touchedErrors: _angular_core.Signal<readonly ValidationError[]>;
|
|
3523
|
+
/**
|
|
3524
|
+
* Handle value change from input.
|
|
3525
|
+
*/
|
|
3526
|
+
onValueChange(value: string): void;
|
|
3527
|
+
/**
|
|
3528
|
+
* Handle blur from input.
|
|
3529
|
+
*/
|
|
3530
|
+
onBlur(): void;
|
|
3531
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<DateFieldRenderer, never>;
|
|
3532
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<DateFieldRenderer, "mcms-date-field-renderer", never, { "field": { "alias": "field"; "required": true; "isSignal": true; }; "formNode": { "alias": "formNode"; "required": false; "isSignal": true; }; "mode": { "alias": "mode"; "required": false; "isSignal": true; }; "path": { "alias": "path"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
3533
|
+
}
|
|
3534
|
+
|
|
3535
|
+
/**
|
|
3536
|
+
* Upload field renderer for file upload fields.
|
|
3537
|
+
* Supports drag & drop, file selection, and media library picking.
|
|
3538
|
+
*
|
|
3539
|
+
* Uses Angular Signal Forms bridge pattern: reads/writes value via
|
|
3540
|
+
* a FieldTree node's FieldState rather than event-based I/O.
|
|
3541
|
+
*/
|
|
3542
|
+
declare class UploadFieldRenderer {
|
|
3543
|
+
private readonly document;
|
|
3544
|
+
private readonly uploadService;
|
|
3545
|
+
private readonly dialogService;
|
|
3546
|
+
/** Field definition */
|
|
3547
|
+
readonly field: _angular_core.InputSignal<Field>;
|
|
3548
|
+
/** Signal forms FieldTree node for this field */
|
|
3549
|
+
readonly formNode: _angular_core.InputSignal<unknown>;
|
|
3550
|
+
/** Form mode */
|
|
3551
|
+
readonly mode: _angular_core.InputSignal<EntityFormMode>;
|
|
3552
|
+
/** Field path */
|
|
3553
|
+
readonly path: _angular_core.InputSignal<string>;
|
|
3554
|
+
/** Bridge: extract FieldState from formNode */
|
|
3555
|
+
private readonly nodeState;
|
|
3556
|
+
/** Icon references */
|
|
3557
|
+
readonly uploadIcon = "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M12 16.5V9.75m0 0 3 3m-3-3-3 3M6.75 19.5a4.5 4.5 0 0 1-1.41-8.775 5.25 5.25 0 0 1 10.233-2.33 3 3 0 0 1 3.758 3.848A3.752 3.752 0 0 1 18 19.5H6.75Z\"></path></svg>";
|
|
3558
|
+
readonly xMarkIcon = "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18 18 6M6 6l12 12\"></path></svg>";
|
|
3559
|
+
readonly photoIcon = "<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" aria-hidden=\"true\" data-slot=\"icon\" style=\"stroke-width:var(--ng-icon__stroke-width, 1.5)\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 0 0 1.5-1.5V6a1.5 1.5 0 0 0-1.5-1.5H3.75A1.5 1.5 0 0 0 2.25 6v12a1.5 1.5 0 0 0 1.5 1.5Zm10.5-11.25h.008v.008h-.008V8.25Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z\"></path></svg>";
|
|
3560
|
+
/** Internal state */
|
|
3561
|
+
readonly isDragging: _angular_core.WritableSignal<boolean>;
|
|
3562
|
+
readonly uploadProgress: _angular_core.WritableSignal<number>;
|
|
3563
|
+
readonly isUploading: _angular_core.WritableSignal<boolean>;
|
|
3564
|
+
readonly uploadingFilename: _angular_core.WritableSignal<string>;
|
|
3565
|
+
readonly uploadError: _angular_core.WritableSignal<string | null>;
|
|
3566
|
+
readonly uploadingFile: _angular_core.WritableSignal<File | null>;
|
|
3567
|
+
/** Unique field ID */
|
|
3568
|
+
readonly fieldId: _angular_core.Signal<string>;
|
|
3569
|
+
/** Computed label */
|
|
3570
|
+
readonly label: _angular_core.Signal<string>;
|
|
3571
|
+
/** Whether the field is required */
|
|
3572
|
+
readonly required: _angular_core.Signal<boolean>;
|
|
3573
|
+
/** Whether the field is disabled */
|
|
3574
|
+
readonly isDisabled: _angular_core.Signal<boolean>;
|
|
3575
|
+
/** Get field as UploadField */
|
|
3576
|
+
readonly uploadField: _angular_core.Signal<UploadField>;
|
|
3577
|
+
/** Current value from FieldState */
|
|
3578
|
+
private readonly currentValue;
|
|
3579
|
+
/** Whether we have a value */
|
|
3580
|
+
readonly hasValue: _angular_core.Signal<boolean>;
|
|
3581
|
+
/** Media preview data from value */
|
|
3582
|
+
readonly mediaPreviewData: _angular_core.Signal<MediaPreviewData | null>;
|
|
3583
|
+
/** Media filename from value */
|
|
3584
|
+
readonly mediaFilename: _angular_core.Signal<string>;
|
|
3585
|
+
/** Preview data for file being uploaded */
|
|
3586
|
+
readonly uploadingFilePreview: _angular_core.Signal<MediaPreviewData | null>;
|
|
3587
|
+
/** MIME types hint for display */
|
|
3588
|
+
readonly mimeTypesHint: _angular_core.Signal<string | null>;
|
|
3589
|
+
/** Max size hint for display */
|
|
3590
|
+
readonly maxSizeHint: _angular_core.Signal<string | null>;
|
|
3591
|
+
/** Accept attribute for file input */
|
|
3592
|
+
readonly acceptAttribute: _angular_core.Signal<string>;
|
|
3593
|
+
/** Validation errors shown only when field is touched */
|
|
3594
|
+
readonly touchedErrors: _angular_core.Signal<readonly ValidationError[]>;
|
|
3595
|
+
/**
|
|
3596
|
+
* Handle drag over event.
|
|
3597
|
+
*/
|
|
3598
|
+
onDragOver(event: DragEvent): void;
|
|
3599
|
+
/**
|
|
3600
|
+
* Handle drag leave event.
|
|
3601
|
+
*/
|
|
3602
|
+
onDragLeave(event: DragEvent): void;
|
|
3603
|
+
/**
|
|
3604
|
+
* Handle file drop.
|
|
3605
|
+
*/
|
|
3606
|
+
onDrop(event: DragEvent): void;
|
|
3607
|
+
/**
|
|
3608
|
+
* Trigger hidden file input click.
|
|
3609
|
+
*/
|
|
3610
|
+
triggerFileInput(): void;
|
|
3611
|
+
/**
|
|
3612
|
+
* Handle file selection from input.
|
|
3613
|
+
*/
|
|
3614
|
+
onFileSelected(event: Event): void;
|
|
3615
|
+
/**
|
|
3616
|
+
* Upload a file.
|
|
3617
|
+
*/
|
|
3618
|
+
uploadFile(file: File): void;
|
|
3619
|
+
/**
|
|
3620
|
+
* Open media picker dialog to select existing media.
|
|
3621
|
+
*/
|
|
3622
|
+
openMediaPicker(): void;
|
|
3623
|
+
/**
|
|
3624
|
+
* Remove the current media value.
|
|
3625
|
+
*/
|
|
3626
|
+
removeMedia(): void;
|
|
3627
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<UploadFieldRenderer, never>;
|
|
3628
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<UploadFieldRenderer, "mcms-upload-field-renderer", never, { "field": { "alias": "field"; "required": true; "isSignal": true; }; "formNode": { "alias": "formNode"; "required": false; "isSignal": true; }; "mode": { "alias": "mode"; "required": false; "isSignal": true; }; "path": { "alias": "path"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
3629
|
+
}
|
|
3630
|
+
|
|
3631
|
+
/**
|
|
3632
|
+
* Media item from the API.
|
|
3633
|
+
*/
|
|
3634
|
+
interface MediaItem {
|
|
3635
|
+
id: string;
|
|
3636
|
+
filename: string;
|
|
3637
|
+
mimeType: string;
|
|
3638
|
+
path: string;
|
|
3639
|
+
url?: string;
|
|
3640
|
+
filesize?: number;
|
|
3641
|
+
alt?: string;
|
|
3642
|
+
}
|
|
3643
|
+
/**
|
|
3644
|
+
* Data passed to the MediaPickerDialog.
|
|
3645
|
+
*/
|
|
3646
|
+
interface MediaPickerDialogData {
|
|
3647
|
+
/** Allowed MIME types filter */
|
|
3648
|
+
mimeTypes?: string[];
|
|
3649
|
+
/** Collection to query (default: 'media') */
|
|
3650
|
+
relationTo?: string;
|
|
3651
|
+
}
|
|
3652
|
+
/**
|
|
3653
|
+
* Result returned from the MediaPickerDialog.
|
|
3654
|
+
*/
|
|
3655
|
+
interface MediaPickerResult {
|
|
3656
|
+
media: MediaItem | null;
|
|
3657
|
+
}
|
|
3658
|
+
/**
|
|
3659
|
+
* Dialog for selecting media from the media library.
|
|
3660
|
+
*/
|
|
3661
|
+
declare class MediaPickerDialog {
|
|
3662
|
+
private readonly dialogRef;
|
|
3663
|
+
private readonly data;
|
|
3664
|
+
private readonly api;
|
|
3665
|
+
/** Internal state */
|
|
3666
|
+
readonly isLoading: _angular_core.WritableSignal<boolean>;
|
|
3667
|
+
readonly mediaItems: _angular_core.WritableSignal<MediaItem[]>;
|
|
3668
|
+
readonly selectedMedia: _angular_core.WritableSignal<MediaItem | null>;
|
|
3669
|
+
readonly searchQuery: _angular_core.WritableSignal<string>;
|
|
3670
|
+
readonly currentPage: _angular_core.WritableSignal<number>;
|
|
3671
|
+
readonly totalPages: _angular_core.WritableSignal<number>;
|
|
3672
|
+
readonly totalDocs: _angular_core.WritableSignal<number>;
|
|
3673
|
+
readonly limit: _angular_core.WritableSignal<number>;
|
|
3674
|
+
/** Collection to query */
|
|
3675
|
+
readonly collectionSlug: _angular_core.Signal<string>;
|
|
3676
|
+
constructor();
|
|
3677
|
+
/**
|
|
3678
|
+
* Load media from the API.
|
|
3679
|
+
*/
|
|
3680
|
+
private loadMedia;
|
|
3681
|
+
/**
|
|
3682
|
+
* Handle search query change.
|
|
3683
|
+
*/
|
|
3684
|
+
onSearchChange(query: string): void;
|
|
3685
|
+
/**
|
|
3686
|
+
* Handle page change.
|
|
3687
|
+
*/
|
|
3688
|
+
onPageChange(page: number): void;
|
|
3689
|
+
/**
|
|
3690
|
+
* Select a media item.
|
|
3691
|
+
*/
|
|
3692
|
+
selectMedia(media: MediaItem): void;
|
|
3693
|
+
/**
|
|
3694
|
+
* Confirm selection on double-click.
|
|
3695
|
+
*/
|
|
3696
|
+
confirmSelection(media: MediaItem): void;
|
|
3697
|
+
/**
|
|
3698
|
+
* Confirm and close dialog.
|
|
3699
|
+
*/
|
|
3700
|
+
confirm(): void;
|
|
3701
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<MediaPickerDialog, never>;
|
|
3702
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<MediaPickerDialog, "mcms-media-picker-dialog", never, {}, {}, never, never, true, never>;
|
|
3703
|
+
}
|
|
3704
|
+
|
|
3705
|
+
export { AdminShellComponent, AdminSidebarWidget, BlockEditDialog, BlockInserterComponent, BlockWrapperComponent, CheckboxFieldRenderer, CollectionAccessService, CollectionCardWidget, CollectionEditPage, CollectionListPage, CollectionViewPage, DashboardPage, DateFieldRenderer, EntityFormWidget, EntityListWidget, EntitySheetService, EntityViewWidget, FeedbackService, FieldRenderer, ForgotPasswordFormComponent, ForgotPasswordPage, LivePreviewComponent, LoginPage, MOMENTUM_API, MOMENTUM_API_CONTEXT, McmsThemeService, MediaLibraryPage, MediaPickerDialog, MediaPreviewComponent, MomentumApiService, MomentumAuthService, NumberFieldRenderer, PublishControlsWidget, ResetPasswordFormComponent, ResetPasswordPage, SHEET_QUERY_PARAMS, SKIP_AUTO_TOAST, SelectFieldRenderer, SetupPage, TextFieldRenderer, UploadFieldRenderer, UploadService, VersionHistoryWidget, VersionService, VisualBlockEditorComponent, adminGuard, authGuard, collectionAccessGuard, crudToastInterceptor, getFieldNodeState, getSubNode, getTitleField, guestGuard, injectHasAnyRole, injectHasRole, injectIsAdmin, injectIsAuthenticated, injectMomentumAPI, injectTypedMomentumAPI, injectUser, injectUserRole, injectVersionService, momentumAdminRoutes, provideMomentumAPI, setupGuard, unsavedChangesGuard };
|
|
3706
|
+
export type { AdminBranding, AdminNavItem, AdminNavSection, AdminPluginRoute, AdminUser, AuthResult, AuthSession, AuthUser, BaseUser, BlockEditDialogData, BlockItem, BreadcrumbItem, CollectionPermissions, CollectionWithCount, DeleteResult, DeviceSize, DocumentStatus, DocumentVersionParsed, DraftSaveResult, Entity, EntityAction, EntityFormMode, EntityListActionEvent, EntityListBulkActionEvent, EntityListColumn, EntityListFindResult, EntitySheetResult, EntityViewActionEvent, EntityViewFieldConfig, FieldNodeState, FindByIdOptions, FindOptions, FindResult, HasUnsavedChanges, McmsTheme, MediaPickerDialogData, MediaPickerResult, MediaPreviewData, MomentumAPIContext, MomentumAPIServer, MomentumAdminBranding, MomentumAdminOptions, MomentumAdminRouteData, MomentumClientAPI, MomentumCollectionAPI, PublishResult, RestoreResult, RestoreVersionOptions, SetupStatus, StatusResult, TypedCollectionAPI, TypedFindByIdOptions, TypedFindOptions, TypedMomentumClientAPI, UploadProgress, UserContext, VersionFindOptions, VersionQueryResult, VisualEditorState };
|