@acorex-platform/module-content-management-client 0.0.21

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.
@@ -0,0 +1,583 @@
1
+ import { resolveMultiLanguageString } from '@acorex-platform/framework-shared/core';
2
+
3
+ const AXPContentManagementMenuKeys = {
4
+ Root: 'content-management:menu',
5
+ HelpRoot: 'app:help',
6
+ Promotions: 'content-management:menu:promotions',
7
+ TextTemplates: 'content-management:menu:text-templates',
8
+ Glossary: 'content-management:menu:glossary',
9
+ HelpCenter: 'app:help:center',
10
+ Tutorials: 'app:help:tutorials',
11
+ About: 'app:about',
12
+ };
13
+
14
+ /**
15
+ * Content & Documents capabilities used by this module.
16
+ * Domain capability keys for this module. Root groups live in `AXPCommonPermissionGroupKeys` (common contracts).
17
+ */
18
+ const AXPContentDocumentsPermissionKeys = {
19
+ Access: 'ContentDocuments:Permission:Access',
20
+ MaintainDocuments: 'ContentDocuments:Permission:MaintainDocuments',
21
+ MaintainContent: 'ContentDocuments:Permission:MaintainContent',
22
+ MaintainFormTemplates: 'ContentDocuments:Permission:MaintainFormTemplates',
23
+ Setup: 'ContentDocuments:Permission:Setup',
24
+ };
25
+ /**
26
+ * @deprecated Prefer {@link AXPContentDocumentsPermissionKeys}.
27
+ * Legacy nested shape kept so existing imports compile during migration.
28
+ */
29
+ const AXMPermissionsKeys = {
30
+ ContentManagement: {
31
+ Management: AXPContentDocumentsPermissionKeys.MaintainContent,
32
+ Promotion: {
33
+ Management: AXPContentDocumentsPermissionKeys.MaintainContent,
34
+ View: AXPContentDocumentsPermissionKeys.MaintainContent,
35
+ Edit: AXPContentDocumentsPermissionKeys.MaintainContent,
36
+ Delete: AXPContentDocumentsPermissionKeys.MaintainContent,
37
+ Create: AXPContentDocumentsPermissionKeys.MaintainContent,
38
+ },
39
+ TextTemplate: {
40
+ Management: AXPContentDocumentsPermissionKeys.MaintainContent,
41
+ View: AXPContentDocumentsPermissionKeys.MaintainContent,
42
+ Edit: AXPContentDocumentsPermissionKeys.MaintainContent,
43
+ Delete: AXPContentDocumentsPermissionKeys.MaintainContent,
44
+ Create: AXPContentDocumentsPermissionKeys.MaintainContent,
45
+ },
46
+ },
47
+ };
48
+
49
+ const config = {
50
+ i18n: 'content-management',
51
+ };
52
+ const RootConfig = {
53
+ config,
54
+ module: {
55
+ module: 'ContentManagement',
56
+ name: 'ContentManagement',
57
+ title: `@${config.i18n}:module.title`,
58
+ icon: 'fa-light fa-file-invoice',
59
+ },
60
+ entities: {
61
+ promotion: {
62
+ name: 'Promotion',
63
+ title: `@${config.i18n}:promotions.entities.promotion.title`,
64
+ titlePlural: `@${config.i18n}:promotions.entities.promotion.plural`,
65
+ icon: 'fa-light fa-megaphone',
66
+ },
67
+ glossary: {
68
+ name: 'Glossary',
69
+ title: `@${config.i18n}:glossaries.entities.glossary.title`,
70
+ titlePlural: `@${config.i18n}:glossaries.entities.glossary.plural`,
71
+ icon: 'fa-light fa-file',
72
+ },
73
+ textTemplate: {
74
+ name: 'TextTemplate',
75
+ title: `@${config.i18n}:templates.entities.text-template.title`,
76
+ titlePlural: `@${config.i18n}:templates.entities.text-template.plural`,
77
+ icon: 'fa-light fa-envelope',
78
+ },
79
+ },
80
+ };
81
+
82
+ //#region ----- Types -----
83
+ //#endregion
84
+ //#region ----- Locale defaults -----
85
+ /**
86
+ * Default short-date pattern when settings do not provide ShortDate.
87
+ * Aligns with locale-management profiles (en-US / en-AU / en-NZ / fa-IR).
88
+ */
89
+ function defaultShortDatePatternForLocale(locale) {
90
+ const code = normalizeLocale(locale);
91
+ if (code === 'en-us') {
92
+ return 'MM/DD/YYYY';
93
+ }
94
+ if (code.startsWith('fa')) {
95
+ return 'YYYY/MM/DD';
96
+ }
97
+ // en-AU, en-NZ, en-GB, and most other locales
98
+ return 'DD/MM/YYYY';
99
+ }
100
+ function defaultShortTimePatternForLocale(_locale) {
101
+ return 'HH:mm';
102
+ }
103
+ /**
104
+ * Resolves calendar: settings value, else `solar-hijri` for `fa-*`, else Gregorian.
105
+ */
106
+ function resolveTextTemplateCalendar(locale, calendarSetting) {
107
+ const setting = (calendarSetting ?? '').trim().toLowerCase();
108
+ if (setting === 'solar-hijri' ||
109
+ setting === 'jalali' ||
110
+ setting === 'persian' ||
111
+ setting === 'hijri-shamsi') {
112
+ return 'solar-hijri';
113
+ }
114
+ if (setting === 'gregory' || setting === 'gregorian') {
115
+ return 'gregory';
116
+ }
117
+ return normalizeLocale(locale).startsWith('fa') ? 'solar-hijri' : 'gregory';
118
+ }
119
+ //#endregion
120
+ //#region ----- Date / number formatting (Nest-safe) -----
121
+ /**
122
+ * Parses ISO / `YYYY-MM-DD` / Date-like merge values into a Date.
123
+ * Date-only keys are treated as UTC midnight to avoid off-by-one shifts.
124
+ */
125
+ function parseTextTemplateDateValue(value) {
126
+ if (value == null || value === '') {
127
+ return null;
128
+ }
129
+ if (value instanceof Date) {
130
+ return Number.isNaN(value.getTime()) ? null : value;
131
+ }
132
+ if (typeof value === 'number' && Number.isFinite(value)) {
133
+ const date = new Date(value);
134
+ return Number.isNaN(date.getTime()) ? null : date;
135
+ }
136
+ if (typeof value !== 'string') {
137
+ return null;
138
+ }
139
+ const trimmed = value.trim();
140
+ if (!trimmed) {
141
+ return null;
142
+ }
143
+ if (/^\d{4}-\d{2}-\d{2}$/.test(trimmed)) {
144
+ const date = new Date(`${trimmed}T00:00:00.000Z`);
145
+ return Number.isNaN(date.getTime()) ? null : date;
146
+ }
147
+ const date = new Date(trimmed);
148
+ return Number.isNaN(date.getTime()) ? null : date;
149
+ }
150
+ /**
151
+ * Formats a date with regional patterns (`DD/MM/YYYY`, `D MMMM YYYY`, `HH:mm`, …)
152
+ * using the active locale for month names, digits, and calendar (Jalali for fa-IR).
153
+ */
154
+ function formatTextTemplateDate(value, pattern, timeZone = 'UTC', locale = 'en-US', calendar) {
155
+ const date = parseTextTemplateDateValue(value);
156
+ if (!date) {
157
+ return value == null ? '' : String(value);
158
+ }
159
+ const resolvedLocale = locale?.trim() || 'en-US';
160
+ const resolvedCalendar = resolveTextTemplateCalendar(resolvedLocale, calendar);
161
+ const intlCalendar = resolvedCalendar === 'solar-hijri' ? 'persian' : 'gregory';
162
+ const parts = getZonedCalendarParts(date, timeZone || 'UTC', resolvedLocale, intlCalendar);
163
+ const monthLong = getLocalizedMonthName(date, resolvedLocale, intlCalendar, timeZone || 'UTC', 'long');
164
+ const monthShort = getLocalizedMonthName(date, resolvedLocale, intlCalendar, timeZone || 'UTC', 'short');
165
+ let out = pattern?.trim() || defaultShortDatePatternForLocale(resolvedLocale);
166
+ // Longer tokens first.
167
+ out = out.replace(/YYYY/g, formatLocaleInteger(parts.year, resolvedLocale, 4));
168
+ out = out.replace(/MMMM/g, monthLong);
169
+ out = out.replace(/MMM/g, monthShort);
170
+ out = out.replace(/MM/g, formatLocaleInteger(parts.month, resolvedLocale, 2));
171
+ out = out.replace(/\bM\b/g, formatLocaleInteger(parts.month, resolvedLocale, 0));
172
+ out = out.replace(/DD/g, formatLocaleInteger(parts.day, resolvedLocale, 2));
173
+ out = out.replace(/\bD\b/g, formatLocaleInteger(parts.day, resolvedLocale, 0));
174
+ out = out.replace(/HH/g, formatLocaleInteger(parts.hour, resolvedLocale, 2));
175
+ out = out.replace(/mm/g, formatLocaleInteger(parts.minute, resolvedLocale, 2));
176
+ out = out.replace(/ss/g, formatLocaleInteger(parts.second, resolvedLocale, 2));
177
+ return out;
178
+ }
179
+ function formatTextTemplateNumber(value, locale, options = {}) {
180
+ const num = coerceNumber(value);
181
+ if (num == null) {
182
+ return value == null ? '' : String(value);
183
+ }
184
+ const resolvedLocale = locale?.trim() || 'en-US';
185
+ const style = options.style ?? 'decimal';
186
+ try {
187
+ if (style === 'integer') {
188
+ return new Intl.NumberFormat(resolvedLocale, {
189
+ maximumFractionDigits: 0,
190
+ }).format(num);
191
+ }
192
+ if (style === 'percent') {
193
+ return new Intl.NumberFormat(resolvedLocale, {
194
+ style: 'percent',
195
+ maximumFractionDigits: 2,
196
+ }).format(num);
197
+ }
198
+ if (style === 'currency') {
199
+ return new Intl.NumberFormat(resolvedLocale, {
200
+ style: 'currency',
201
+ currency: options.currency?.trim() || defaultCurrencyForLocale(resolvedLocale),
202
+ }).format(num);
203
+ }
204
+ return new Intl.NumberFormat(resolvedLocale, {
205
+ maximumFractionDigits: 6,
206
+ }).format(num);
207
+ }
208
+ catch {
209
+ return String(num);
210
+ }
211
+ }
212
+ /**
213
+ * Formats merge values using PlatformManagement.Token `dataType` / `format` metadata.
214
+ * Unknown tokens and non-typed strings are left unchanged.
215
+ */
216
+ function formatTextTemplateMergeValues(merge, metaByName, regional) {
217
+ const out = { ...merge };
218
+ const locale = regional.locale?.trim() || 'en-US';
219
+ const shortDate = regional.shortDate?.trim() || defaultShortDatePatternForLocale(locale);
220
+ const shortTime = regional.shortTime?.trim() || defaultShortTimePatternForLocale(locale);
221
+ const timeZone = regional.timeZone?.trim() || 'UTC';
222
+ const calendar = resolveTextTemplateCalendar(locale, regional.calendar);
223
+ for (const [name, raw] of Object.entries(merge)) {
224
+ const meta = metaByName[name];
225
+ if (!meta) {
226
+ continue;
227
+ }
228
+ const dataType = (meta.dataType ?? 'String').trim();
229
+ const format = (meta.format ?? 'None').trim();
230
+ if (dataType === 'DateTime') {
231
+ const pattern = `${shortDate} ${shortTime}`.trim();
232
+ const dateOnly = looksLikeDateOnly(raw);
233
+ out[name] = formatTextTemplateDate(raw, dateOnly ? shortDate : pattern, timeZone, locale, calendar);
234
+ continue;
235
+ }
236
+ if (dataType === 'Integer') {
237
+ out[name] = formatTextTemplateNumber(raw, locale, { style: 'integer' });
238
+ continue;
239
+ }
240
+ if (dataType === 'Decimal' || format === 'Currency' || format === 'Percent') {
241
+ const style = format === 'Currency' ? 'currency' : format === 'Percent' ? 'percent' : 'decimal';
242
+ out[name] = formatTextTemplateNumber(raw, locale, { style });
243
+ }
244
+ }
245
+ return out;
246
+ }
247
+ //#endregion
248
+ //#region ----- Helpers -----
249
+ function normalizeLocale(locale) {
250
+ return (locale ?? 'en-US').trim().toLowerCase() || 'en-us';
251
+ }
252
+ function defaultCurrencyForLocale(locale) {
253
+ const code = normalizeLocale(locale);
254
+ if (code === 'en-au')
255
+ return 'AUD';
256
+ if (code === 'en-nz')
257
+ return 'NZD';
258
+ if (code.startsWith('fa') || code === 'en-ir')
259
+ return 'IRR';
260
+ if (code === 'en-gb')
261
+ return 'GBP';
262
+ return 'USD';
263
+ }
264
+ function coerceNumber(value) {
265
+ if (typeof value === 'number' && Number.isFinite(value)) {
266
+ return value;
267
+ }
268
+ if (typeof value === 'string' && value.trim()) {
269
+ const num = Number(value.trim());
270
+ return Number.isFinite(num) ? num : null;
271
+ }
272
+ return null;
273
+ }
274
+ function looksLikeDateOnly(value) {
275
+ const trimmed = value.trim();
276
+ if (/^\d{4}-\d{2}-\d{2}$/.test(trimmed)) {
277
+ return true;
278
+ }
279
+ return /^\d{4}-\d{2}-\d{2}T00:00:00(?:\.0+)?Z$/i.test(trimmed);
280
+ }
281
+ function formatLocaleInteger(value, locale, minDigits) {
282
+ const abs = Math.trunc(Math.abs(value));
283
+ const padded = minDigits > 0 ? String(abs).padStart(minDigits, '0') : String(abs);
284
+ try {
285
+ // Format digit-by-digit so padding zeros stay and locale digits apply (fa-IR → ۰۱۲…).
286
+ return padded
287
+ .split('')
288
+ .map((ch) => new Intl.NumberFormat(locale, { useGrouping: false }).format(Number(ch)))
289
+ .join('');
290
+ }
291
+ catch {
292
+ return padded;
293
+ }
294
+ }
295
+ function getLocalizedMonthName(date, locale, calendar, timeZone, width) {
296
+ try {
297
+ return new Intl.DateTimeFormat(locale, {
298
+ timeZone,
299
+ calendar,
300
+ month: width,
301
+ }).format(date);
302
+ }
303
+ catch {
304
+ try {
305
+ return new Intl.DateTimeFormat('en-US', {
306
+ timeZone,
307
+ calendar: 'gregory',
308
+ month: width,
309
+ }).format(date);
310
+ }
311
+ catch {
312
+ return String(date.getUTCMonth() + 1);
313
+ }
314
+ }
315
+ }
316
+ function getZonedCalendarParts(date, timeZone, locale, calendar) {
317
+ try {
318
+ const formatter = new Intl.DateTimeFormat(locale, {
319
+ timeZone: timeZone || 'UTC',
320
+ calendar,
321
+ year: 'numeric',
322
+ month: '2-digit',
323
+ day: '2-digit',
324
+ hour: '2-digit',
325
+ minute: '2-digit',
326
+ second: '2-digit',
327
+ hour12: false,
328
+ // Keep part extraction numeric-stable; display digits are localized later.
329
+ numberingSystem: 'latn',
330
+ });
331
+ const parts = formatter.formatToParts(date);
332
+ const read = (type) => {
333
+ const value = parts.find((part) => part.type === type)?.value;
334
+ return value ? Number(value) : 0;
335
+ };
336
+ let hour = read('hour');
337
+ // Some engines emit 24:00 for midnight.
338
+ if (hour === 24) {
339
+ hour = 0;
340
+ }
341
+ return {
342
+ year: read('year'),
343
+ month: read('month'),
344
+ day: read('day'),
345
+ hour,
346
+ minute: read('minute'),
347
+ second: read('second'),
348
+ };
349
+ }
350
+ catch {
351
+ return {
352
+ year: date.getUTCFullYear(),
353
+ month: date.getUTCMonth() + 1,
354
+ day: date.getUTCDate(),
355
+ hour: date.getUTCHours(),
356
+ minute: date.getUTCMinutes(),
357
+ second: date.getUTCSeconds(),
358
+ };
359
+ }
360
+ }
361
+ //#endregion
362
+
363
+ /** Template kind: content (leaf message) or master (layout shell with `[content]`). */
364
+ const AXM_TEXT_TEMPLATE_KIND = {
365
+ Content: 'content',
366
+ Master: 'master',
367
+ };
368
+ /** Print / PDF channel key on `channels` (rich body, no subject). */
369
+ const AXM_TEXT_TEMPLATE_PRINT_CHANNEL = 'print';
370
+ /** Reserved body slot for child template body injection (not a `{{token}}` variable). */
371
+ const AXM_TEXT_TEMPLATE_CONTENT_SLOT = '[content]';
372
+ /** Max `parentId` chain length (leaf → … → root), including the leaf. */
373
+ const AXM_TEXT_TEMPLATE_MAX_PARENT_DEPTH = 5;
374
+ //#endregion
375
+
376
+ //#region ----- Placeholder replacement -----
377
+ const TEXT_TEMPLATE_PLACEHOLDER_PATTERN = /\{\{([\w-]+)\}\}/g;
378
+ /**
379
+ * Collects unique placeholder token names from text (`{{name}}`).
380
+ */
381
+ function collectTextTemplatePlaceholderNames(text) {
382
+ return [...new Set((text.match(TEXT_TEMPLATE_PLACEHOLDER_PATTERN) ?? []).map((m) => m.slice(2, -2)))];
383
+ }
384
+ /**
385
+ * Replaces `{{token}}` when `merge[token]` is defined; otherwise leaves the token unchanged.
386
+ */
387
+ function replaceTextTemplatePlaceholders(text, merge) {
388
+ return text.replace(TEXT_TEMPLATE_PLACEHOLDER_PATTERN, (full, name) => Object.prototype.hasOwnProperty.call(merge, name) ? merge[name] : full);
389
+ }
390
+ /**
391
+ * Resolves a template channel field (plain string or per-locale map) for the active locale.
392
+ */
393
+ function resolveTextTemplateLocalizedString(value, locale) {
394
+ if (value == null || value === '') {
395
+ return '';
396
+ }
397
+ if (typeof value === 'string') {
398
+ return value;
399
+ }
400
+ return resolveMultiLanguageString(value, locale);
401
+ }
402
+ /**
403
+ * Returns true when `text` contains the reserved `[content]` slot.
404
+ */
405
+ function textTemplateContainsContentSlot(text) {
406
+ if (text == null || text === '') {
407
+ return false;
408
+ }
409
+ return text.includes(AXM_TEXT_TEMPLATE_CONTENT_SLOT);
410
+ }
411
+ /**
412
+ * Injects child body into every `[content]` occurrence in a master body.
413
+ */
414
+ function replaceTextTemplateContentSlot(masterBody, content) {
415
+ if (!masterBody.includes(AXM_TEXT_TEMPLATE_CONTENT_SLOT)) {
416
+ return masterBody;
417
+ }
418
+ return masterBody.split(AXM_TEXT_TEMPLATE_CONTENT_SLOT).join(content);
419
+ }
420
+ /**
421
+ * Composes a body chain: leaf first, then parents toward root.
422
+ * Each level applies `{{token}}` merge; parents receive the child body via `[content]`.
423
+ */
424
+ function composeTextTemplateBodyChain(bodiesLeafToRoot, merge) {
425
+ if (!bodiesLeafToRoot.length) {
426
+ return '';
427
+ }
428
+ let body = replaceTextTemplatePlaceholders(bodiesLeafToRoot[0] ?? '', merge);
429
+ for (let i = 1; i < bodiesLeafToRoot.length; i++) {
430
+ const parentMerged = replaceTextTemplatePlaceholders(bodiesLeafToRoot[i] ?? '', merge);
431
+ body = replaceTextTemplateContentSlot(parentMerged, body);
432
+ }
433
+ return body;
434
+ }
435
+ //#endregion
436
+ //#region ----- Validation helpers -----
437
+ function collectLocalizedBodyStrings(body) {
438
+ if (body == null || body === '') {
439
+ return [];
440
+ }
441
+ if (typeof body === 'string') {
442
+ return body.trim() ? [body] : [];
443
+ }
444
+ return Object.values(body).filter((v) => typeof v === 'string' && v.trim().length > 0);
445
+ }
446
+ /**
447
+ * Master templates: every non-empty channel body locale must include `[content]`.
448
+ * Content templates: no channel body may include `[content]`.
449
+ */
450
+ function validateTextTemplateContentSlots(input) {
451
+ const kind = (input.kind ?? AXM_TEXT_TEMPLATE_KIND.Content);
452
+ const channels = input.channels ?? {};
453
+ const channelEntries = Object.entries(channels);
454
+ if (kind === AXM_TEXT_TEMPLATE_KIND.Master) {
455
+ if (!channelEntries.length) {
456
+ return { ok: false, error: 'Master templates must define at least one channel with a [content] slot.' };
457
+ }
458
+ for (const [channelName, channel] of channelEntries) {
459
+ const bodies = collectLocalizedBodyStrings(channel?.body);
460
+ if (!bodies.length) {
461
+ return {
462
+ ok: false,
463
+ error: `Master template channel "${channelName}" must include a body with the [content] slot.`,
464
+ };
465
+ }
466
+ for (const body of bodies) {
467
+ if (!textTemplateContainsContentSlot(body)) {
468
+ return {
469
+ ok: false,
470
+ error: `Master template channel "${channelName}" body must include the [content] slot.`,
471
+ };
472
+ }
473
+ }
474
+ }
475
+ return { ok: true };
476
+ }
477
+ for (const [channelName, channel] of channelEntries) {
478
+ for (const body of collectLocalizedBodyStrings(channel?.body)) {
479
+ if (textTemplateContainsContentSlot(body)) {
480
+ return {
481
+ ok: false,
482
+ error: `Content template channel "${channelName}" must not include the reserved [content] slot.`,
483
+ };
484
+ }
485
+ }
486
+ }
487
+ return { ok: true };
488
+ }
489
+ /**
490
+ * Validates parent reference and walks the parent chain for kind, cycles, and max depth.
491
+ * `loadParent` should return the next parent row (or null when missing).
492
+ */
493
+ async function validateTextTemplateParentChain(input) {
494
+ const parentId = input.parentId?.trim() || null;
495
+ if (!parentId) {
496
+ return { ok: true };
497
+ }
498
+ const selfId = input.id?.trim() || null;
499
+ if (selfId && parentId === selfId) {
500
+ return { ok: false, error: 'A text template cannot be its own parent.' };
501
+ }
502
+ const maxDepth = input.maxDepth ?? AXM_TEXT_TEMPLATE_MAX_PARENT_DEPTH;
503
+ const visited = new Set();
504
+ if (selfId) {
505
+ visited.add(selfId);
506
+ }
507
+ let currentId = parentId;
508
+ let depth = 1;
509
+ while (currentId) {
510
+ if (visited.has(currentId)) {
511
+ return { ok: false, error: 'Text template parent chain contains a cycle.' };
512
+ }
513
+ if (depth > maxDepth) {
514
+ return {
515
+ ok: false,
516
+ error: `Text template parent chain exceeds the maximum depth of ${maxDepth}.`,
517
+ };
518
+ }
519
+ visited.add(currentId);
520
+ const parent = await input.loadParent(currentId);
521
+ if (!parent) {
522
+ return { ok: false, error: `Parent text template not found (id: ${currentId}).` };
523
+ }
524
+ if ((parent.kind ?? AXM_TEXT_TEMPLATE_KIND.Content) !== AXM_TEXT_TEMPLATE_KIND.Master) {
525
+ return { ok: false, error: 'Parent text template must be a master template.' };
526
+ }
527
+ currentId = parent.parentId?.trim() || null;
528
+ depth += 1;
529
+ }
530
+ return { ok: true };
531
+ }
532
+ //#endregion
533
+
534
+ //#region ----- Text template render contracts -----
535
+ /** Server + client command key for rendering a text template channel. */
536
+ const AXM_TEXT_TEMPLATE_RENDER_COMMAND_KEY = 'ContentManagement:TextTemplate:Render';
537
+ //#endregion
538
+ //#region ----- Text template draft preview contracts -----
539
+ /** Server command key for previewing unsaved draft text template content. */
540
+ const AXM_TEXT_TEMPLATE_PREVIEW_COMMAND_KEY = 'ContentManagement:TextTemplate:Preview';
541
+ //#endregion
542
+
543
+ //#region ----- Seeded text template ids -----
544
+ /**
545
+ * Namespace for {@link tenantMasterId} when cloning text templates per tenant
546
+ * (`packs` SetupTenant → `content-management.common-setup`).
547
+ */
548
+ const AXM_TEXT_TEMPLATE_TENANT_MASTER_NAMESPACE = 'content';
549
+ /**
550
+ * Canonical ContentManagement.TextTemplate ids used as SetupTenant clone keys
551
+ * and runtime lookups (resolve via tenantMasterId + tenantId).
552
+ * Keep aligned with packs `text-template.mock.ids.ts`.
553
+ *
554
+ * Prefix `c71000` — same as `mockStableUuid('c71000', seq)`.
555
+ */
556
+ const AXM_TEXT_TEMPLATE_IDS = {
557
+ emailCompanyMaster: 'c7100001-0001-4111-b111-010101010101',
558
+ passwordReset: 'c7100002-0001-4111-b111-020202020202',
559
+ emailVerification: 'c7100003-0001-4111-b111-030303030303',
560
+ passwordChanged: 'c7100004-0001-4111-b111-040404040404',
561
+ ticketReceived: 'c7100005-0001-4111-b111-050505050505',
562
+ ticketResolved: 'c7100006-0001-4111-b111-060606060606',
563
+ employeeHappyBirthday: 'c7100007-0001-4111-b111-070707070707',
564
+ employeeJoiningCompany: 'c7100008-0001-4111-b111-080808080808',
565
+ /** Document expired — daily notifier (email + in-app). */
566
+ documentExpired: 'c7100009-0001-4111-b111-090909090909',
567
+ /** Document near expiry — daily notifier (email + in-app). */
568
+ documentNearExpiry: 'c710000a-0001-4111-b111-0a0a0a0a0a0a',
569
+ /** Company print master — header, border, footer around `[content]`. */
570
+ printCompanyMaster: 'c710000b-0001-4111-b111-0b0b0b0b0b0b',
571
+ /** Default questionnaire print child (title, date, `{{sessionBody}}`). */
572
+ questionnairePrint: 'c710000c-0001-4111-b111-0c0c0c0c0c0c',
573
+ };
574
+ //#endregion
575
+
576
+ //#region ---- Contracts barrel ----
577
+
578
+ /**
579
+ * Generated bundle index. Do not edit.
580
+ */
581
+
582
+ export { AXMPermissionsKeys, AXM_TEXT_TEMPLATE_CONTENT_SLOT, AXM_TEXT_TEMPLATE_IDS, AXM_TEXT_TEMPLATE_KIND, AXM_TEXT_TEMPLATE_MAX_PARENT_DEPTH, AXM_TEXT_TEMPLATE_PREVIEW_COMMAND_KEY, AXM_TEXT_TEMPLATE_PRINT_CHANNEL, AXM_TEXT_TEMPLATE_RENDER_COMMAND_KEY, AXM_TEXT_TEMPLATE_TENANT_MASTER_NAMESPACE, AXPContentDocumentsPermissionKeys, AXPContentManagementMenuKeys, RootConfig, collectTextTemplatePlaceholderNames, composeTextTemplateBodyChain, defaultShortDatePatternForLocale, defaultShortTimePatternForLocale, formatTextTemplateDate, formatTextTemplateMergeValues, formatTextTemplateNumber, parseTextTemplateDateValue, replaceTextTemplateContentSlot, replaceTextTemplatePlaceholders, resolveTextTemplateCalendar, resolveTextTemplateLocalizedString, textTemplateContainsContentSlot, validateTextTemplateContentSlots, validateTextTemplateParentChain };
583
+ //# sourceMappingURL=acorex-platform-module-content-management-client-contracts.mjs.map