@dignite/ng.site 0.0.0-stage → 1.0.0

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 @@
1
+ {"version":3,"file":"dignite-ng.site-content-editor.component-iFWDeYJ9.mjs","sources":["../../../projects/site/src/lib/field-types/flex-field-error-message.ts","../../../projects/site/src/lib/components/contents/content-editor.component.ts","../../../projects/site/src/lib/components/contents/content-editor.component.html"],"sourcesContent":["import { AbstractControl } from '@angular/forms';\nimport { LocalizationService } from '@abp/ng.core';\n\n/**\n * Maps a mounted flex field's own control errors to the same localized text, regardless of which\n * field type produced them or how deep the control is nested (top-level, a Table column, a Matrix\n * sub-field): every built-in field type only ever raises `required`/`min`/`max`/`maxlength`, since\n * that's the full set `NumberControlComponent`/`TextControlComponent`/etc. push in `@dignite/ng.flex-fields`.\n * Gated on `touched` so a fresh, still-empty required field doesn't show red before the user reaches it.\n */\nexport function flexFieldErrorMessage(\n control: AbstractControl | null | undefined,\n localization: LocalizationService,\n): string | null {\n const errors = control?.touched ? control.errors : null;\n if (!errors) {\n return null;\n }\n\n if (errors['required']) {\n return localization.instant('AbpValidation::ThisFieldIsRequired');\n }\n if (errors['min']) {\n return localization.instant('FlexFieldsSite::Validate:MinValue', String(errors['min'].min));\n }\n if (errors['max']) {\n return localization.instant('FlexFieldsSite::Validate:MaxValue', String(errors['max'].max));\n }\n if (errors['maxlength']) {\n return localization.instant('FlexFieldsSite::Validate:MaxLength', String(errors['maxlength'].requiredLength));\n }\n return null;\n}\n","import { CoreModule, LocalizationService } from '@abp/ng.core';\nimport { ThemeSharedModule, ToasterService } from '@abp/ng.theme.shared';\nimport { Component, inject, signal, ChangeDetectionStrategy } from '@angular/core';\nimport { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';\nimport { ActivatedRoute, Router, RouterLink } from '@angular/router';\nimport { FlexFieldControlComponent } from '@dignite/ng.flex-fields';\nimport type { FlexFieldData, FlexFieldValue } from '@dignite/ng.flex-fields';\nimport { catchError, forkJoin, of, switchMap } from 'rxjs';\nimport { ContentAdminService } from '../../proxy/dignite/site/admin/contents/content-admin.service';\nimport { ContentTypeAdminService } from '../../proxy/dignite/site/admin/content-types/content-type-admin.service';\nimport { PageAdminService } from '../../proxy/dignite/site/admin/pages/page-admin.service';\nimport type { ContentTypeDto } from '../../proxy/dignite/site/content-types/models';\nimport type { ContentDto } from '../../proxy/dignite/site/contents/models';\nimport { ContentStatus } from '../../proxy/dignite/site/contents/content-status.enum';\nimport type { FieldDto } from '../../proxy/dignite/site/fields/models';\nimport type { PageDto } from '../../proxy/dignite/site/pages/models';\nimport { SiteReferenceDataService } from '../../services/site-reference-data.service';\nimport { flexFieldErrorMessage } from '../../field-types/flex-field-error-message';\nimport type { ArrangedField } from './arranged-field';\n\nconst MAX_SLUG_LENGTH = 256;\nconst OPTIONAL_SLUG_TOKEN = '{slug?}';\nconst REQUIRED_SLUG_TOKEN = '{slug}';\n\n/** Mirrors `SlugNormalizer.AllowedCharactersPattern` (`src/Dignite.Site.Domain.Shared/SlugNormalizer.cs`). */\nconst SLUG_PATTERN = /^[\\p{L}\\p{Nd}_-]*$/u;\n\n/** Mirrors `PageRoute.HasSlug`/`PageRoute.IsSlugOptional` (总体设计 §3.3) - see {@link ContentEditorComponent.slugState}. */\ntype SlugState = 'forbidden' | 'required' | 'optional';\n\n/**\n * Creating and editing one content, as a page of its own rather than a dialog.\n *\n * The two halves answer different questions and are sized accordingly: the left is the content itself -\n * however many fields its content type defines, which can be a long form - and the right is the handful\n * of system fields that place and schedule it. A dialog forced both into one scrolling column.\n *\n * Switching the content type rebuilds the left half, because the fields *are* the content type's\n * definition. Values already typed under fields the new type does not have are dropped, which is the\n * honest outcome: they have nowhere to be stored.\n *\n * **Deliberately no CKEditor theme bridge here.** This component used to carry a global\n * `::ng-deep :root` block remapping CKEditor 5's `--ck-color-base-*` tokens onto the host theme -\n * CKEditor ships one fixed light palette with no dark-mode switch, so the editor stayed light in a\n * dark-themed host. `@dignite/ng.flex-fields-ckeditor` >= 10.0.0-rc.13 ships that bridge itself, with\n * a longer fallback chain than this copy had (LeptonX token -> the Bootstrap 5.3 token every ABP\n * Angular theme defines -> CKEditor's own stock literal) plus fixes this copy never had. Re-adding a\n * copy here would not add anything and would fight it at equal `:root` specificity, decided by style\n * injection order alone.\n */\n@Component({\n selector: 'site-content-editor',\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './content-editor.component.html',\n imports: [CoreModule, ThemeSharedModule, ReactiveFormsModule, RouterLink, FlexFieldControlComponent],\n})\nexport class ContentEditorComponent {\n private readonly contentService = inject(ContentAdminService);\n private readonly contentTypeService = inject(ContentTypeAdminService);\n private readonly pageService = inject(PageAdminService);\n private readonly referenceData = inject(SiteReferenceDataService);\n private readonly route = inject(ActivatedRoute);\n private readonly router = inject(Router);\n private readonly fb = inject(FormBuilder);\n private readonly toaster = inject(ToasterService);\n private readonly localization = inject(LocalizationService);\n\n readonly ContentStatus = ContentStatus;\n readonly statuses = [ContentStatus.Draft, ContentStatus.Published, ContentStatus.Archived];\n\n readonly contentId: string | null = this.route.snapshot.paramMap.get('id');\n readonly isEdit = !!this.contentId;\n\n // State that changes in an HTTP callback is a signal, not a plain field: the host layout's ancestors\n // (`abp-router-outlet`, the LeptonX layouts) are OnPush, and a plain-field write in a response\n // callback never marks them for check, so the form would stay blank (or go stale) until some\n // unrelated event happened to.\n readonly page = signal<PageDto | undefined>(undefined);\n readonly contentTypes = signal<ContentTypeDto[]>([]);\n readonly languages = signal<string[]>([]);\n defaultLanguage = '';\n content?: ContentDto;\n\n /**\n * Whether the page's own address (an empty slug) is already taken by another content, for the\n * currently selected language - only meaningful, and only checked, when {@link slugState} is\n * `'optional'`. Read by the template to decide whether the \"use the page's own address\" checkbox is\n * offered at all.\n */\n readonly emptySlotTaken = signal(false);\n\n private fieldsById = new Map<string, FieldDto>();\n private arrangementByContentType = new Map<string, ArrangedField[]>();\n\n /**\n * The current content type's fields, as stable `FlexFieldValue`s. Rebuilt only when the content type\n * changes - `ff-flex-field-control` compares this input by object reference, and handing it a freshly\n * allocated array on every change-detection pass would make it tear down and recreate its dynamic\n * child forever (`NG0103`).\n */\n readonly fields = signal<FlexFieldValue[]>([]);\n\n form?: FormGroup;\n readonly isBusy = signal(false);\n readonly isLoading = signal(true);\n\n constructor() {\n const pageId = this.route.snapshot.queryParamMap.get('pageId');\n\n forkJoin({\n schema: this.referenceData.getSchema(),\n fieldsById: this.referenceData.getFieldsById(),\n content: this.contentId ? this.contentService.get(this.contentId) : of(undefined),\n })\n .pipe(\n switchMap(loaded => {\n const languages = loaded.schema.enabledLanguages ?? [];\n this.languages.set(languages);\n this.defaultLanguage = loaded.schema.defaultLanguage ?? languages[0] ?? '';\n this.fieldsById = loaded.fieldsById;\n this.content = loaded.content;\n\n const resolvedPageId = loaded.content?.pageId ?? pageId ?? '';\n\n return forkJoin({\n page: resolvedPageId ? this.pageService.get(resolvedPageId) : of(undefined),\n contentTypes: resolvedPageId\n ? this.contentTypeService.getListByPage(resolvedPageId)\n : of({ items: [] as ContentTypeDto[] }),\n });\n }),\n )\n .subscribe(({ page, contentTypes }) => {\n this.page.set(page);\n this.contentTypes.set(contentTypes.items ?? []);\n this.rebuildArrangements();\n this.form = this.buildForm();\n this.rebuildFields();\n this.isLoading.set(false);\n this.refreshEmptySlotAvailability();\n });\n }\n\n get hasMultipleContentTypes(): boolean {\n return this.contentTypes().length > 1;\n }\n\n /**\n * What this page's route (总体设计 §3.3) says about the slug beneath it - the front-end mirror of\n * `PageRoute.HasSlug`/`PageRoute.IsSlugOptional`. `'forbidden'`: no `{slug}`/`{slug?}` at all, so this\n * page has nothing beneath it but its own address. `'required'`: `{slug}` or `{slug:REGEX}`, so every\n * content needs one. `'optional'`: `{slug?}`, so an empty slug is also allowed, subject to\n * {@link emptySlotTaken}.\n */\n get slugState(): SlugState {\n const route = this.page()?.route ?? '';\n if (route.includes(OPTIONAL_SLUG_TOKEN)) {\n return 'optional';\n }\n return route.includes(REQUIRED_SLUG_TOKEN) || this.hasSlugWithRegex(route) ? 'required' : 'forbidden';\n }\n\n /**\n * Whether `route` carries `{slug:REGEX}` - a slug placeholder with a regex and no format, the one\n * decorated form `PageRoute.HasSlug` recognizes. Whether a given slug matches that regex is left to the\n * server (`PageRoute.IsSlugAllowed`): a .NET pattern does not always mean the same thing in JavaScript.\n */\n private hasSlugWithRegex(route: string): boolean {\n for (let i = route.indexOf('{'); i >= 0; i = route.indexOf('{', i + 1)) {\n const placeholder = this.readPlaceholder(route, i);\n if (placeholder && placeholder.name.toLowerCase() === 'slug' && placeholder.format === null) {\n return true;\n }\n if (placeholder) {\n i = placeholder.end - 1;\n }\n }\n return false;\n }\n\n get usePageOwnAddress(): boolean {\n return !!this.form?.get('usePageOwnAddress')?.value;\n }\n\n /**\n * The address this content will actually be served at, recomputed on every read so the template can\n * bind it directly and have it track the slug field as the user types. An approximation, not the\n * source of truth: {@link resolvePlaceholder} resolves every placeholder generically off the form and\n * the loaded content, falling back to the raw token when it cannot - `Page.BuildContentPath` on the\n * server is what actually decides the URL.\n *\n * The empty-slug branch mirrors `Page.BuildContentPath` itself, not a naming convention: an empty\n * slug is short-circuited to the page's own path there regardless of what the rest of the route\n * contains, so the preview has to do the same rather than run it through the placeholder replacement\n * below.\n */\n get slugPreview(): string {\n const route = this.page()?.route;\n if (!route) {\n return '';\n }\n\n const slug = (this.form?.get('slug')?.value ?? '').trim();\n const path = slug ? this.buildPreviewPath(route) : this.pageOwnPath(route);\n return this.applyCulturePrefix(path);\n }\n\n get isDraft(): boolean {\n return this.form?.getRawValue().status === ContentStatus.Draft;\n }\n\n statusKeyOf(status: ContentStatus): string {\n return `Site::Enum:ContentStatus:${ContentStatus[status]}`;\n }\n\n fieldErrorMessage(fieldValue: FlexFieldValue): string | null {\n return flexFieldErrorMessage(this.form?.get('flexFields')?.get(fieldValue.field.name), this.localization);\n }\n\n private rebuildArrangements(): void {\n this.arrangementByContentType = new Map(\n this.contentTypes().map(contentType => [\n contentType.id!,\n (contentType.fields ?? [])\n .map(entry => {\n const definition = entry.fieldId ? this.fieldsById.get(entry.fieldId) : undefined;\n if (!definition) {\n return undefined;\n }\n\n const fieldData: FlexFieldData = {\n id: definition.id!,\n name: definition.name!,\n displayName: entry.displayName || definition.displayName!,\n description: definition.description ?? undefined,\n fieldTypeName: definition.fieldTypeName!,\n configuration: (definition.configuration ?? {}) as Record<string, unknown>,\n };\n\n return {\n fieldId: definition.id!,\n name: definition.name!,\n displayName: fieldData.displayName,\n fieldTypeName: definition.fieldTypeName!,\n required: !!entry.required,\n searchable: !!entry.searchable,\n showInList: !!entry.showInList,\n order: entry.order ?? 0,\n fieldData,\n } satisfies ArrangedField;\n })\n .filter((field): field is ArrangedField => !!field)\n .sort((a, b) => a.order - b.order),\n ]),\n );\n }\n\n /** Called on load and whenever the content type changes. See {@link fields}. */\n private rebuildFields(): void {\n const contentTypeId = this.form?.get('contentTypeId')?.value as string | undefined;\n const arranged = this.arrangementByContentType.get(contentTypeId ?? '') ?? [];\n const values = this.content?.fieldValues ?? {};\n\n this.fields.set(\n arranged.map(field => ({\n field: field.fieldData,\n required: field.required,\n searchable: field.searchable,\n value: values[field.name],\n })),\n );\n }\n\n onContentTypeChange(): void {\n // The value bag is keyed by field name, so controls for the old type's fields have to go before the\n // new type's are registered - otherwise the payload carries both.\n this.form?.setControl('flexFields', this.fb.group({}));\n this.rebuildFields();\n }\n\n onStatusChange(): void {\n this.applyPublishTimeAvailability();\n }\n\n onCultureNameChange(): void {\n this.refreshEmptySlotAvailability();\n }\n\n onUsePageOwnAddressChange(): void {\n this.applySlugAvailability();\n }\n\n save(): void {\n if (!this.form || this.form.invalid || this.isBusy()) {\n return;\n }\n\n this.isBusy.set(true);\n const value = this.form.getRawValue();\n const fieldValues = (this.form.get('flexFields')?.getRawValue() ?? {}) as Record<string, object>;\n const slug = value.slug ?? '';\n\n const request$ = this.content\n ? this.contentService.update(this.content.id!, {\n contentTypeId: value.contentTypeId,\n slug,\n publishTime: value.publishTime,\n status: value.status,\n fieldValues,\n })\n : this.contentService.create({\n contentTypeId: value.contentTypeId,\n cultureName: value.cultureName,\n slug,\n publishTime: value.publishTime,\n status: value.status,\n fieldValues,\n });\n\n request$.subscribe({\n next: () => {\n this.isBusy.set(false);\n this.toaster.success('AbpUi::SavedSuccessfully');\n this.backToList();\n },\n error: () => this.isBusy.set(false),\n });\n }\n\n backToList(): void {\n this.router.navigate(['/site/contents']);\n }\n\n private buildForm(): FormGroup {\n const content = this.content;\n\n const form = this.fb.group({\n contentTypeId: [\n content?.contentTypeId ?? this.contentTypes()[0]?.id ?? '',\n Validators.required,\n ],\n cultureName: [\n {\n value:\n content?.cultureName ??\n this.route.snapshot.queryParamMap.get('cultureName') ??\n this.languages()[0] ??\n '',\n // Fixed after creation: the language is half the natural key that groups a content's\n // translations, so changing it would silently move the content to another translation set.\n disabled: !!content,\n },\n Validators.required,\n ],\n // Editing a content that is already the page's own (an empty slug) starts the checkbox checked -\n // otherwise applySlugAvailability would immediately overwrite its empty slug with a forced-required\n // value the moment the form builds.\n usePageOwnAddress: [!!content && !content.slug],\n slug: [content?.slug ?? '', [Validators.maxLength(MAX_SLUG_LENGTH), Validators.pattern(SLUG_PATTERN)]],\n publishTime: [this.toLocalDateTime(content?.publishTime), Validators.required],\n status: [content?.status ?? ContentStatus.Published],\n flexFields: this.fb.group({}),\n });\n\n this.form = form;\n this.applySlugAvailability();\n this.applyPublishTimeAvailability();\n\n return form;\n }\n\n /**\n * Enables/disables and (in)validates the `slug` control from {@link slugState} - and, in the\n * `'optional'` case, from whether the \"use the page's own address\" checkbox is ticked. The same\n * three-way split `ContentManager.CheckSlugRequirementAsync` enforces server-side; this only decides\n * what the form looks like before that ever runs.\n */\n private applySlugAvailability(): void {\n const slug = this.form?.get('slug');\n if (!slug) {\n return;\n }\n\n const forceOwnAddress = this.slugState === 'forbidden' ||\n (this.slugState === 'optional' && this.usePageOwnAddress);\n\n if (forceOwnAddress) {\n slug.setValue('');\n slug.clearValidators();\n slug.disable();\n } else {\n slug.enable();\n slug.setValidators([\n Validators.required,\n Validators.maxLength(MAX_SLUG_LENGTH),\n Validators.pattern(SLUG_PATTERN),\n ]);\n }\n\n slug.updateValueAndValidity();\n }\n\n /**\n * Looks up whether this page's own address (an empty slug) is already taken for the selected\n * language, so the \"use the page's own address\" option can be withheld before the editor ever submits\n * and hits `Site:040001`. A no-op outside the `'optional'` state, where the question does not arise.\n *\n * Goes through `getList` rather than the more direct `findBySlug`, which exists for exactly this\n * lookup - `IContentAdminAppService.FindBySlugAsync(pageId, cultureName, \"\")` is the same query\n * `SiteRouteResolver` uses to resolve a bare page match. But ABP's `RestService.getParams` silently\n * drops any query parameter whose value is an empty string, so a `slug` of `\"\"` never reaches the\n * request at all; the server then sees a *missing* parameter, not an intentionally empty one, and\n * rejects it exactly as `Should_Reject_A_Null_Slug` says it must. Listing this page's contents for the\n * language and filtering for an empty slug client-side sidesteps that without weakening the server's\n * null-vs-empty distinction.\n */\n private refreshEmptySlotAvailability(): void {\n const pageId = this.page()?.id;\n if (this.slugState !== 'optional' || !pageId) {\n this.emptySlotTaken.set(false);\n return;\n }\n\n const cultureName = this.form?.get('cultureName')?.value;\n if (!cultureName) {\n return;\n }\n\n this.contentService\n .getList({ pageId, cultureName, maxResultCount: 1000 })\n .pipe(catchError(() => of({ items: [] as ContentDto[], totalCount: 0 })))\n .subscribe(result => {\n const existing = (result.items ?? []).find(item => item.slug === '');\n const taken = !!existing && existing.id !== this.content?.id;\n this.emptySlotTaken.set(taken);\n\n // The slot this checkbox promised is gone (e.g. the language was switched to one where it is\n // already taken) - un-tick it rather than leave a checked box the next save would reject.\n if (taken && this.usePageOwnAddress) {\n this.form?.get('usePageOwnAddress')?.setValue(false);\n this.applySlugAvailability();\n }\n });\n }\n\n /**\n * A draft is never public, so a publish time on one is not a schedule anything acts on - the server\n * rejects the combination outright (`Site:040003`). Scheduling is Published plus a future time.\n */\n private applyPublishTimeAvailability(): void {\n const publishTime = this.form?.get('publishTime');\n const status = this.form?.get('status')?.value as ContentStatus | undefined;\n\n if (!publishTime) {\n return;\n }\n\n if (status === ContentStatus.Draft) {\n publishTime.setValue(this.toLocalDateTime());\n publishTime.disable();\n } else {\n publishTime.enable();\n }\n }\n\n /** `<input type=\"datetime-local\">` wants `yyyy-MM-ddTHH:mm`, not an ISO string with a zone. */\n private toLocalDateTime(value?: string): string {\n const date = value ? new Date(value) : new Date();\n const offset = date.getTimezoneOffset() * 60_000;\n return new Date(date.getTime() - offset).toISOString().slice(0, 16);\n }\n\n /**\n * Mirrors `SiteUrlContext.ApplyCulturePrefix` (总体设计 §5.5): every enabled language carries a\n * `/cultureName` prefix except the default one, which is served unprefixed. `cultureName` here is\n * already one of `languages` (or the loaded content's own, already-normalized value), so - unlike the\n * server, which normalizes through `CultureNameNormalizer` first - a plain equality check against\n * {@link defaultLanguage} is enough to tell the two apart.\n */\n private applyCulturePrefix(path: string): string {\n const cultureName = this.form?.get('cultureName')?.value;\n if (!cultureName || cultureName === this.defaultLanguage) {\n return path;\n }\n\n return path === '/' ? `/${cultureName}` : `/${cultureName}${path}`;\n }\n\n /** Mirrors `PageRoute.GetPath`: everything up to the segment that first carries a placeholder. */\n private pageOwnPath(route: string): string {\n const index = route.indexOf('{');\n if (index < 0) {\n return route;\n }\n\n const boundary = route.lastIndexOf('/', index);\n return boundary <= 0 ? '/' : route.slice(0, boundary);\n }\n\n /**\n * A rough, client-side mirror of `PageRoute.Build` - see {@link slugPreview}'s remarks. Every\n * placeholder, `{slug}` included, goes through the same generic {@link resolvePlaceholder} - no\n * per-name branch here, deliberately: the server does not have one either, it reflects over `Content`\n * (`Page.ResolveFieldValue`), and a name-by-name `if` ladder here would just be that same mistake\n * moved to TypeScript instead of removed.\n * <p>\n * Placeholders are located with {@link readPlaceholder}'s brace-depth-aware scan, not a single regex -\n * a `/\\{...\\}/` pattern cannot tell a placeholder's own closing brace apart from a balanced one inside\n * its `:REGEX` segment (a quantifier like `\\d{4}`, e.g. in `{publishTime:yyyy-MM:^\\d{4}-(0[1-9]|1[0-2])$}`),\n * the same reason `PageRoute.TryParseTemplate` on the server had to stop using ABP's own tokenizer.\n */\n private buildPreviewPath(route: string): string {\n const canonical = route.replace(OPTIONAL_SLUG_TOKEN, REQUIRED_SLUG_TOKEN);\n\n let result = '';\n let i = 0;\n\n while (i < canonical.length) {\n if (canonical[i] !== '{') {\n result += canonical[i];\n i++;\n continue;\n }\n\n const placeholder = this.readPlaceholder(canonical, i);\n if (!placeholder) {\n // Not a well-formed placeholder start - a route that reaches this component was already\n // validated by `PageRoute.IsValid` when it was saved, so this is only ever a defensive fallback:\n // treat '{' as a literal character rather than throwing out of what is meant to be a best-effort\n // preview.\n result += canonical[i];\n i++;\n continue;\n }\n\n result += this.resolvePlaceholder(placeholder.name, placeholder.format) ?? placeholder.token;\n i = placeholder.end;\n }\n\n return result;\n }\n\n /**\n * Reads one `{name}` / `{name:FORMAT}` / `{name:REGEX}` / `{name:FORMAT:REGEX}` placeholder starting at\n * `route[start]` (must be `'{'`), mirroring `PageRoute.TryParseTemplate`'s single-placeholder case\n * (`src/Dignite.Site.Domain.Shared/Pages/PageRoute.cs`): brace depth is tracked once inside the\n * `:`-segment so an embedded, balanced `{`/`}` does not get mistaken for the placeholder's own closing\n * brace, and a backslash escapes the next character so an explicitly-escaped `\\{`/`\\}` never perturbs\n * the count either. Returns `null` for anything that does not parse as a well-formed placeholder -\n * {@link buildPreviewPath} treats that as a literal `{` rather than failing the whole preview.\n * <p>\n * Only `name` and `format` are extracted, never the regex itself - a placeholder's regex constrains\n * what may be read back out of a request path, and has nothing to say about a value being written into\n * one, the same reason `PageRoute.Build` on the server discards it too (see that method's remarks).\n */\n private readPlaceholder(\n route: string,\n start: number,\n ): { token: string; end: number; name: string; format: string | null } | null {\n let i = start + 1; // consume '{'\n const nameStart = i;\n\n // Mirrors `PageRoute.IsNameStartChar`/`IsNameChar`: every `IdentifierName` a field can have, plus\n // uppercase for `Content`'s camel-cased system properties (`publishTime`).\n if (i >= route.length || !/[a-zA-Z0-9]/.test(route[i])) {\n return null;\n }\n i++;\n while (i < route.length && /[a-zA-Z0-9_-]/.test(route[i])) {\n i++;\n }\n const name = route.slice(nameStart, i);\n\n if (i >= route.length) {\n return null;\n }\n\n if (route[i] === '}') {\n return { token: route.slice(start, i + 1), end: i + 1, name, format: null };\n }\n\n if (route[i] !== ':') {\n return null;\n }\n\n i++; // consume ':'\n const innerStart = i;\n let depth = 0;\n let closed = false;\n\n while (i < route.length) {\n const c = route[i];\n if (c === '\\\\' && i + 1 < route.length) {\n i += 2;\n continue;\n }\n if (c === '{') {\n depth++;\n } else if (c === '}') {\n if (depth === 0) {\n closed = true;\n break;\n }\n depth--;\n }\n i++;\n }\n\n if (!closed) {\n return null;\n }\n\n const inner = route.slice(innerStart, i);\n const end = i + 1; // consume the closing '}'\n return { token: route.slice(start, end), end, name, format: this.disambiguateFormat(inner) };\n }\n\n /**\n * Mirrors `PageRoute.TryDisambiguate`, reduced to the half a preview needs: a placeholder's already\n * brace-depth-isolated inner text (everything after the first `:`) splits into a `:FORMAT` prefix plus a\n * regex remainder when the prefix is format-shaped (letters/digits/`.`/`_`/`-` only - a real format\n * string, by construction, can never also look like a regex worth compiling, which is what keeps every\n * `{name:FORMAT}` route classified exactly as it always was); otherwise the inner text is a bare format\n * on its own if it matches that shape whole, or else it is a regex with no format at all.\n */\n private disambiguateFormat(inner: string): string | null {\n const formatCharacters = /^[a-zA-Z0-9_.-]+$/;\n const colon = inner.indexOf(':');\n\n if (colon >= 0) {\n const candidate = inner.slice(0, colon);\n if (candidate && formatCharacters.test(candidate)) {\n return candidate;\n }\n }\n\n return formatCharacters.test(inner) ? inner : null;\n }\n\n /**\n * Resolves one placeholder's preview text purely from *where* `name` is actually found, never from\n * what it is called. Angular forms have no runtime type to reflect over the way `Content` does server\n * side - `ContentDto` is a TypeScript interface, erased at compile time - so the nearest equivalent is\n * a generic, case-insensitive walk of what is actually on the form and the loaded content, checked in\n * the same order the server would find them: a real property first, `FlexFields` second.\n * 1. A top-level form control - every system field the editor can change lives here (`cultureName`,\n * `status`, `publishTime`, `contentTypeId`, `slug`...), whatever it happens to be named.\n * 2. A control inside `flexFields` - a content type's own business fields.\n * 3. A property already on the loaded `this.content` DTO - covers the read-only audit trail\n * (`id`, `creationTime`, ...) `FullAuditedEntityDto` carries but no form control exists for.\n * Naturally empty until this content has actually been saved once - which is honest, since the\n * server has nothing to reflect there yet either.\n *\n * One deliberate, named exception to \"no name-based branching\": {@link usePageOwnAddress} is a UI-only\n * control with no server-side counterpart, and step 1 cannot tell that apart from a real field by\n * shape alone - a route that actually referenced `{usePageOwnAddress}` would resolve to \"true\"/\"false\"\n * here. Excluding it by name would be exactly the kind of branch this method exists to avoid, and the\n * failure mode is harmless: `Page.BuildContentPath` has no such property either, so a route that made\n * this mistake for real still fails loudly server-side the first time a URL is actually built from it.\n */\n private resolvePlaceholder(name: string, format: string | null): string | null {\n const raw = this.findValue(name);\n if (raw === null || raw === undefined || raw === '') {\n return null;\n }\n\n if (format) {\n const formatted = this.tryFormatByShape(raw, format);\n if (formatted !== null) {\n return formatted;\n }\n }\n\n return String(raw);\n }\n\n /**\n * Applies `format` to `raw` purely from what `raw` looks like, never from which placeholder produced\n * it - the same dispatch-by-shape rule {@link resolvePlaceholder} follows, extended to every shape\n * this preview knows how to format. Mirrors `Page.FormatFieldValue`'s `value is IFormattable` check,\n * just done by sniffing a form value's shape with a regex instead of asking the CLR for its runtime\n * type - there is no such type to ask here, `raw` is already unwrapped to a plain JS value by the time\n * it reaches this method.\n * <p>\n * Each shape is tried in turn; the first one whose regex matches `raw` owns the answer from there,\n * whether or not it actually recognizes `format` - a date-shaped value with a nonsense format is a\n * date that failed to format, not a number or a Guid that happened to also look like one. Recognizing\n * the shape but not the specific `format` asked of it (a Guid's `X`, a numeric format outside\n * {@link formatNumber}'s subset) returns null, same as a shape nothing here recognizes at all - both\n * fall back to the unformatted value in {@link resolvePlaceholder}, deliberately: this is a preview,\n * and a plausible but possibly-wrong guess at an unimplemented format would be worse than showing the\n * plain value.\n */\n private tryFormatByShape(raw: unknown, format: string): string | null {\n const date = this.tryParseDate(raw);\n if (date) {\n return this.formatDate(date, format);\n }\n\n const guid = this.tryParseGuid(raw);\n if (guid) {\n return this.formatGuid(guid, format);\n }\n\n const number = this.tryParseNumber(raw);\n if (number !== null) {\n return this.formatNumber(number, format);\n }\n\n return null;\n }\n\n private findValue(name: string): unknown {\n const lowerName = name.toLowerCase();\n\n const topLevelControls = this.form?.controls;\n const topLevelKey = topLevelControls && Object.keys(topLevelControls).find(key => key.toLowerCase() === lowerName);\n if (topLevelKey) {\n return this.form!.get(topLevelKey)!.value;\n }\n\n const flexFields = this.form?.get('flexFields');\n if (flexFields instanceof FormGroup) {\n const flexKey = Object.keys(flexFields.controls).find(key => key.toLowerCase() === lowerName);\n if (flexKey) {\n return flexFields.get(flexKey)!.value;\n }\n }\n\n if (this.content) {\n const contentRecord = this.content as unknown as Record<string, unknown>;\n const contentKey = Object.keys(contentRecord).find(key => key.toLowerCase() === lowerName);\n if (contentKey !== undefined) {\n return contentRecord[contentKey];\n }\n }\n\n return undefined;\n }\n\n /** Whether `value` is shaped like a `datetime-local` input's own output (`yyyy-MM-ddTHH:mm`). */\n private tryParseDate(value: unknown): Date | null {\n if (typeof value !== 'string' || !/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}/.test(value)) {\n return null;\n }\n\n const date = new Date(value);\n return isNaN(date.getTime()) ? null : date;\n }\n\n /** Only the three specifiers a `:FORMAT` suffix may actually contain (总体设计 §3.3) - no '/' among them. */\n private formatDate(date: Date, format: string): string {\n return format\n .replace(/yyyy/g, String(date.getFullYear()))\n .replace(/yy/g, String(date.getFullYear()).slice(-2))\n .replace(/MM/g, String(date.getMonth() + 1).padStart(2, '0'))\n .replace(/dd/g, String(date.getDate()).padStart(2, '0'));\n }\n\n /** Whether `value` is shaped like the canonical, hyphenated form of a Guid. */\n private tryParseGuid(value: unknown): string | null {\n return typeof value === 'string' &&\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value)\n ? value\n : null;\n }\n\n /**\n * `Guid.ToString(format)`'s four straightforward specifiers - N (no separators), D (hyphenated, the\n * shape already checked by {@link tryParseGuid}), B (braced) and P (parenthesized). `X` deliberately\n * not implemented: it renders as a C#-syntax initializer list, not a URL-shaped string, and nothing\n * about a route makes that a plausible thing to have ever asked for.\n */\n private formatGuid(guid: string, format: string): string | null {\n const digits = guid.replace(/-/g, '').toLowerCase();\n const dashed = `${digits.slice(0, 8)}-${digits.slice(8, 12)}-${digits.slice(12, 16)}-${digits.slice(16, 20)}-${digits.slice(20)}`;\n\n switch (format.toUpperCase()) {\n case 'N':\n return digits;\n case 'D':\n return dashed;\n case 'B':\n return `{${dashed}}`;\n case 'P':\n return `(${dashed})`;\n default:\n return null;\n }\n }\n\n /** Whether `value` is shaped like a plain, optionally negative, optionally decimal number. */\n private tryParseNumber(value: unknown): number | null {\n if (typeof value === 'number' && isFinite(value)) {\n return value;\n }\n\n return typeof value === 'string' && /^-?\\d+(\\.\\d+)?$/.test(value) ? Number(value) : null;\n }\n\n /**\n * A representative slice of .NET's numeric format strings - the ones a URL path segment plausibly\n * asks for. Custom zero-padding (`\"0000\"`, unchanged from before) plus four standard specifiers, each\n * with an optional 1-2 digit precision: `D` (zero-padded, integers only - the standard-format spelling\n * of the same thing `\"0000\"` does), `X`/`x` (hexadecimal, integers only, letter case controls digit\n * case, negative not attempted - see {@link formatHex}'s remarks), `F` (fixed-point) and `N`\n * (fixed-point with thousands grouping). Deliberately not `C`/`P` (currency/percent): both put a\n * character that means something else in a URL - a locale-dependent currency symbol, or `%`, the URL\n * escape character itself - into the path, which nothing about routing would ever actually want. Not\n * `E`/`G`/`R` either: those are about round-trip print fidelity for an arbitrary number, not a shape\n * any real route has a reason to ask for.\n */\n private formatNumber(value: number, format: string): string | null {\n if (/^0+$/.test(format)) {\n return Number.isInteger(value) ? this.padInteger(value, format.length) : null;\n }\n\n const standard = /^([DdXxFfNn])(\\d{0,2})$/.exec(format);\n if (!standard) {\n return null;\n }\n\n const [, specifier, precisionText] = standard;\n const precision = precisionText === '' ? null : Number(precisionText);\n\n switch (specifier.toUpperCase()) {\n case 'D':\n return Number.isInteger(value) ? this.padInteger(value, precision ?? 0) : null;\n case 'X':\n return Number.isInteger(value) && value >= 0 ? this.formatHex(value, precision ?? 0, specifier === 'X') : null;\n case 'F':\n return value.toFixed(precision ?? 2);\n case 'N':\n return this.groupThousands(value.toFixed(precision ?? 2));\n default:\n return null;\n }\n }\n\n private padInteger(value: number, width: number): string {\n const negative = value < 0;\n const padded = Math.abs(value).toString().padStart(width, '0');\n return negative ? `-${padded}` : padded;\n }\n\n /**\n * Negative values are rejected before this is ever called - .NET's `X` renders a negative signed\n * integer as its two's-complement bit pattern, which depends on the source type's own width (`int` vs\n * `long` vs `byte` produce different digits for the same value), information a form control's plain\n * number does not carry. Guessing a width would risk a preview that looks plausible and is wrong;\n * not attempting it is the same call already made for a Guid's `X` and for `C`/`P`/`E`/`G`/`R`.\n */\n private formatHex(value: number, width: number, uppercase: boolean): string {\n const hex = value.toString(16).padStart(width, '0');\n return uppercase ? hex.toUpperCase() : hex;\n }\n\n private groupThousands(fixedValue: string): string {\n const negative = fixedValue.startsWith('-');\n const [integerPart, fractionPart] = (negative ? fixedValue.slice(1) : fixedValue).split('.');\n const grouped = integerPart.replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',');\n return `${negative ? '-' : ''}${grouped}${fractionPart ? '.' + fractionPart : ''}`;\n }\n}\n","@if (!isLoading() && form) {\n <form [formGroup]=\"form\" (submit)=\"save()\">\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\n <div>\n <h5 class=\"mb-0\">\n {{ (isEdit ? 'Site::EditContent' : 'Site::NewContent') | abpLocalization }}\n </h5>\n @if (page(); as currentPage) {\n <small class=\"text-muted\">{{ currentPage.displayName }}</small>\n }\n </div>\n <div>\n <a class=\"btn btn-secondary me-2\" [routerLink]=\"['/site/contents']\">\n {{ 'AbpUi::Cancel' | abpLocalization }}\n </a>\n <!-- A plain submit button, not `abp-button`: that component declares its own `click` output,\n so `(click)` binds to an EventEmitter instead of the DOM event, and it takes `buttonType`\n rather than `type`. Both make it easy to end up with a button that quietly never submits.\n Inside a form on a page, the native element says what it does. -->\n <button type=\"submit\" class=\"btn btn-primary\" [disabled]=\"!form.valid || isBusy()\">\n @if (isBusy()) {\n <span class=\"spinner-border spinner-border-sm me-1\" role=\"status\"></span>\n } @else {\n <i class=\"fa fa-check me-1\"></i>\n }\n {{ 'AbpUi::Save' | abpLocalization }}\n </button>\n </div>\n </div>\n\n <div class=\"row g-3\">\n <!-- Left: the content itself - whatever fields its content type defines. -->\n <div class=\"col-lg-8\">\n <div class=\"card h-100\">\n <div class=\"card-body\">\n @if (hasMultipleContentTypes) {\n <div class=\"mb-3 pb-3 border-bottom\">\n <label class=\"form-label\">\n {{ 'Site::DisplayName:Content.ContentType' | abpLocalization }}\n </label>\n <select\n class=\"form-select\"\n formControlName=\"contentTypeId\"\n (change)=\"onContentTypeChange()\"\n >\n @for (contentType of contentTypes(); track contentType.id) {\n <option [value]=\"contentType.id\">{{ contentType.displayName }}</option>\n }\n </select>\n <small class=\"form-text text-muted\">\n {{ 'Site::ContentTypeSwitchHint' | abpLocalization }}\n </small>\n </div>\n }\n\n @if (fields().length > 0) {\n @for (fieldValue of fields(); track fieldValue.field.name) {\n <ff-flex-field-control\n [fields]=\"fieldValue\"\n [entity]=\"form\"\n parentFieldName=\"flexFields\"\n [selected]=\"fieldValue.value\"\n ></ff-flex-field-control>\n @if (fieldErrorMessage(fieldValue); as message) {\n <div class=\"invalid-feedback d-block mb-3\">{{ message }}</div>\n }\n }\n } @else {\n <p class=\"text-muted mb-0\">{{ 'Site::NoFieldsArranged' | abpLocalization }}</p>\n }\n </div>\n </div>\n </div>\n\n <!-- Right: the system fields that place and schedule the content. -->\n <div class=\"col-lg-4\">\n <div class=\"card\">\n <div class=\"card-body\">\n @if (!hasMultipleContentTypes && contentTypes().length === 1) {\n <div class=\"mb-3\">\n <label class=\"form-label\">\n {{ 'Site::DisplayName:Content.ContentType' | abpLocalization }}\n </label>\n <input type=\"text\" class=\"form-control\" [value]=\"contentTypes()[0].displayName\" disabled />\n </div>\n }\n\n <div class=\"mb-3\">\n <label class=\"form-label\">\n {{ 'Site::DisplayName:Content.CultureName' | abpLocalization }}\n </label>\n <select class=\"form-select\" formControlName=\"cultureName\" (change)=\"onCultureNameChange()\">\n @for (language of languages(); track language) {\n <option [value]=\"language\">{{ language }}</option>\n }\n </select>\n </div>\n\n <!-- The page's route (总体设计 §3.3) decides this, not the editor: no {slug}/{slug?} at all\n means nothing is shown here - there is nothing to choose. -->\n @if (slugState !== 'forbidden') {\n <div class=\"mb-3\">\n <label class=\"form-label\">{{ 'Site::DisplayName:Content.Slug' | abpLocalization }}</label>\n\n @if (slugState === 'optional' && !emptySlotTaken()) {\n <div class=\"form-check mb-2\">\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n id=\"site-use-page-own-address\"\n formControlName=\"usePageOwnAddress\"\n (change)=\"onUsePageOwnAddressChange()\"\n />\n <label class=\"form-check-label\" for=\"site-use-page-own-address\">\n {{ 'Site::UsePageOwnAddress' | abpLocalization }}\n </label>\n </div>\n }\n\n @if (!usePageOwnAddress) {\n <input type=\"text\" class=\"form-control\" formControlName=\"slug\" />\n <small class=\"form-text text-muted d-block mt-1\">\n {{ 'Site::Description:Content.Slug' | abpLocalization }}\n </small>\n }\n\n @if (slugState === 'optional' && emptySlotTaken()) {\n <small class=\"form-text text-muted d-block mt-1\">\n {{ 'Site::SlugSlotTaken' | abpLocalization }}\n </small>\n }\n\n <small class=\"form-text text-muted d-block mt-1\">\n {{ 'Site::SlugPreview' | abpLocalization: slugPreview }}\n </small>\n </div>\n }\n\n <div class=\"mb-3\">\n <label class=\"form-label\">{{ 'Site::DisplayName:Content.Status' | abpLocalization }}</label>\n <select class=\"form-select\" formControlName=\"status\" (change)=\"onStatusChange()\">\n @for (status of statuses; track status) {\n <option [ngValue]=\"status\">{{ statusKeyOf(status) | abpLocalization }}</option>\n }\n </select>\n </div>\n\n <div class=\"mb-0\">\n <label class=\"form-label\">\n {{ 'Site::DisplayName:Content.PublishTime' | abpLocalization }}\n </label>\n <input type=\"datetime-local\" class=\"form-control\" formControlName=\"publishTime\" />\n @if (isDraft) {\n <small class=\"form-text text-muted\">\n {{ 'Site::DraftPublishTimeDisabledHint' | abpLocalization }}\n </small>\n }\n </div>\n </div>\n </div>\n </div>\n </div>\n </form>\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,324 @@
1
+ import * as i4 from '@abp/ng.core';
2
+ import { LocalizationService, CoreModule, PermissionService, PermissionDirective } from '@abp/ng.core';
3
+ import * as i2$1 from '@abp/ng.theme.shared';
4
+ import { ConfirmationService, ToasterService, Confirmation, ThemeSharedModule } from '@abp/ng.theme.shared';
5
+ import * as i0 from '@angular/core';
6
+ import { inject, Input, ChangeDetectionStrategy, Component, signal, ViewChild } from '@angular/core';
7
+ import * as i1 from '@angular/forms';
8
+ import { FormsModule, FormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
9
+ import { ActivatedRoute } from '@angular/router';
10
+ import { forkJoin } from 'rxjs';
11
+ import { c as ContentTypeAdminService, P as PageAdminService, S as SiteReferenceDataService, e as eSitePolicyNames } from './dignite-ng.site-dignite-ng.site-CsKxqjY9.mjs';
12
+ import * as i2 from '@angular/cdk/drag-drop';
13
+ import { moveItemInArray, DragDropModule } from '@angular/cdk/drag-drop';
14
+ import { FieldTypeResolver, FlexFieldsStyleLoader, NZ_SELECT_STYLE } from '@dignite/ng.flex-fields';
15
+ import * as i3 from 'ng-zorro-antd/select';
16
+ import { NzSelectModule } from 'ng-zorro-antd/select';
17
+ import * as i3$1 from '@ngx-validate/core';
18
+
19
+ /**
20
+ * Arranges a content type's fields: which ones it pulls in from the library, in what order, and how
21
+ * each is used.
22
+ *
23
+ * **The arrangement is edited in place and saved whole.** `UpdateContentTypeDto.Fields` replaces the
24
+ * entire list server-side - any usage flag left out of the payload resets to its default. So the
25
+ * parent holds the complete `ContentTypeFieldDto[]`, this component mutates that same array, and the
26
+ * parent sends all of it. Never send a delta.
27
+ *
28
+ * `ContentTypeFieldDto` carries only `fieldId`; names, types and descriptions come from
29
+ * {@link fieldsById}, joined here.
30
+ */
31
+ class FieldArrangementComponent {
32
+ constructor() {
33
+ this.fieldTypeResolver = inject(FieldTypeResolver);
34
+ this.localization = inject(LocalizationService);
35
+ this.styleLoader = inject(FlexFieldsStyleLoader);
36
+ this.fieldTypes = this.fieldTypeResolver.getAll();
37
+ /** The live arrangement. Mutated in place - the parent reads it back at save time. */
38
+ this.arrangement = [];
39
+ /** The whole field library, keyed by id. */
40
+ this.fieldsById = new Map();
41
+ /** Whether a field type has a query index at all, keyed by field type name. */
42
+ this.indexableByFieldType = new Map();
43
+ /** Field ids chosen in the "add field" picker, before they're committed via {@link addFields}. */
44
+ this.fieldsToAdd = [];
45
+ }
46
+ /**
47
+ * Fetches `ng-zorro-antd-select.css` by bundle name, the way flex-fields' own `Select` controls and
48
+ * `abp-tree` load theirs: the host declares that bundle with `inject: false`, so nothing else puts it
49
+ * on the page. Same loader as flex-fields, so it is requested once however many selects render.
50
+ */
51
+ ngOnInit() {
52
+ this.styleLoader.load(NZ_SELECT_STYLE);
53
+ }
54
+ fieldOf(entry) {
55
+ return entry.fieldId ? this.fieldsById.get(entry.fieldId) : undefined;
56
+ }
57
+ labelOf(entry) {
58
+ return entry.displayName || this.fieldOf(entry)?.displayName || this.fieldOf(entry)?.name || '';
59
+ }
60
+ /** Localization key for a field type's display name, for use with `| abpLocalization`. */
61
+ displayNameKeyOf(fieldTypeName) {
62
+ return (this.fieldTypes.find(type => type.name === fieldTypeName)?.displayNameKey ??
63
+ fieldTypeName ??
64
+ '');
65
+ }
66
+ /** Library fields not already in the arrangement. */
67
+ get addableFields() {
68
+ const used = new Set(this.arrangement.map(entry => entry.fieldId));
69
+ return [...this.fieldsById.values()].filter(field => !used.has(field.id));
70
+ }
71
+ /**
72
+ * {@link addableFields} as `nz-select` options. Labels are resolved here, not in the template -
73
+ * `nzOptions` is consumed internally by ng-zorro rather than rendered through Angular's pipe on each
74
+ * row, so the localized field type name has to already be a plain string by the time it's bound.
75
+ *
76
+ * **Cached against a cheap key, not recomputed every read.** `nzOptions` expects a stable array
77
+ * reference; `arrangement` is mutated in place ({@link arrangement}'s own doc), so its identity never
78
+ * changes when a field is added or removed, and a plain `.map()` here would hand `nz-select` a fresh
79
+ * array and fresh option objects on every change-detection cycle - including the ones firing while the
80
+ * dropdown is open (hover, search keystrokes). `ff-select-control` in flex-fields guards the same
81
+ * getter the same way, for the same reason.
82
+ */
83
+ get addableFieldOptions() {
84
+ const key = `${this.fieldsById.size}:${this.arrangement.length}`;
85
+ if (this.addableFieldOptionsCache?.key !== key) {
86
+ this.addableFieldOptionsCache = {
87
+ key,
88
+ options: this.addableFields.map(field => ({
89
+ label: `${field.displayName} (${this.localization.instant(this.displayNameKeyOf(field.fieldTypeName))})`,
90
+ value: field.id,
91
+ })),
92
+ };
93
+ }
94
+ return this.addableFieldOptionsCache.options;
95
+ }
96
+ /**
97
+ * A field whose type has no query-index slot can never be searched, whatever this flag says -
98
+ * `FlexFieldIndexManagerBase.GetIndexableFieldsAsync` skips it. Lock the checkbox off and say why,
99
+ * rather than letting someone set something that silently does nothing.
100
+ */
101
+ isSearchableSupported(entry) {
102
+ const fieldTypeName = this.fieldOf(entry)?.fieldTypeName;
103
+ return !fieldTypeName || this.indexableByFieldType.get(fieldTypeName) !== false;
104
+ }
105
+ addFields() {
106
+ if (this.fieldsToAdd.length === 0) {
107
+ return;
108
+ }
109
+ this.fieldsToAdd.forEach(fieldId => {
110
+ this.arrangement.push({
111
+ fieldId,
112
+ required: false,
113
+ searchable: false,
114
+ showInList: false,
115
+ displayName: null,
116
+ order: this.arrangement.length,
117
+ });
118
+ });
119
+ this.fieldsToAdd = [];
120
+ this.reindex();
121
+ }
122
+ removeField(index) {
123
+ this.arrangement.splice(index, 1);
124
+ this.reindex();
125
+ }
126
+ drop(event) {
127
+ moveItemInArray(this.arrangement, event.previousIndex, event.currentIndex);
128
+ this.reindex();
129
+ }
130
+ onSearchableChange(entry) {
131
+ if (!this.isSearchableSupported(entry)) {
132
+ entry.searchable = false;
133
+ }
134
+ }
135
+ /** `order` is the server's sort key, so it has to follow the visual order after any change. */
136
+ reindex() {
137
+ this.arrangement.forEach((entry, index) => (entry.order = index));
138
+ }
139
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.2", ngImport: i0, type: FieldArrangementComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
140
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.2.2", type: FieldArrangementComponent, isStandalone: true, selector: "site-field-arrangement", inputs: { arrangement: "arrangement", fieldsById: "fieldsById", indexableByFieldType: "indexableByFieldType" }, ngImport: i0, template: "<div class=\"d-flex align-items-end gap-2 mb-3\">\n <div class=\"flex-grow-1\">\n <label class=\"form-label\">{{ 'Site::AddField' | abpLocalization }}</label>\n <!--\n nzDropdownMatchSelectWidth defaults to true, but inside this abp-modal ng-zorro measures the\n trigger's width before the modal's layout has settled and gets ~0 - the dropdown then renders\n with no real width and swallows clicks. Forcing it off falls back to content-based auto-width,\n which sidesteps the bad measurement entirely.\n -->\n <nz-select\n class=\"form-select field-picker-select\"\n nzMode=\"multiple\"\n nzShowSearch\n nzPlaceHolder=\"---\"\n [nzDropdownMatchSelectWidth]=\"false\"\n nzDropdownClassName=\"field-picker-dropdown\"\n [nzOptions]=\"addableFieldOptions\"\n [(ngModel)]=\"fieldsToAdd\"\n [ngModelOptions]=\"{ standalone: true }\"\n ></nz-select>\n </div>\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n [disabled]=\"fieldsToAdd.length === 0\"\n (click)=\"addFields()\"\n >\n <i class=\"fa fa-plus me-1\"></i>{{ 'Site::AddField' | abpLocalization }}\n </button>\n</div>\n\n@if (arrangement.length === 0) {\n <p class=\"text-muted\">{{ 'Site::NoFieldsArranged' | abpLocalization }}</p>\n} @else {\n <div class=\"table-responsive\">\n <table class=\"table table-sm align-middle mb-0\">\n <thead>\n <tr>\n <th style=\"width: 2rem\"></th>\n <th>{{ 'Site::DisplayName:Field.DisplayName' | abpLocalization }}</th>\n <th>{{ 'Site::DisplayName:Field.FieldTypeName' | abpLocalization }}</th>\n <th class=\"text-center\">{{ 'Site::DisplayName:ContentTypeField.Required' | abpLocalization }}</th>\n <th class=\"text-center\">{{ 'Site::DisplayName:ContentTypeField.Searchable' | abpLocalization }}</th>\n <th class=\"text-center\">{{ 'Site::DisplayName:ContentTypeField.ShowInList' | abpLocalization }}</th>\n <th>{{ 'Site::DisplayName:ContentTypeField.DisplayNameOverride' | abpLocalization }}</th>\n <th style=\"width: 3rem\"></th>\n </tr>\n </thead>\n <tbody cdkDropList (cdkDropListDropped)=\"drop($event)\">\n @for (entry of arrangement; track entry.fieldId; let i = $index) {\n <tr cdkDrag>\n <td class=\"text-muted\" cdkDragHandle style=\"cursor: move\">\n <i class=\"fa fa-grip-vertical\"></i>\n </td>\n <td>\n <div>{{ labelOf(entry) }}</div>\n <small class=\"text-muted\">{{ fieldOf(entry)?.name }}</small>\n </td>\n <td>{{ displayNameKeyOf(fieldOf(entry)?.fieldTypeName) | abpLocalization }}</td>\n <td class=\"text-center\">\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [(ngModel)]=\"entry.required\"\n [ngModelOptions]=\"{ standalone: true }\"\n />\n </td>\n <td class=\"text-center\">\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [(ngModel)]=\"entry.searchable\"\n [ngModelOptions]=\"{ standalone: true }\"\n [disabled]=\"!isSearchableSupported(entry)\"\n (ngModelChange)=\"onSearchableChange(entry)\"\n />\n @if (!isSearchableSupported(entry)) {\n <div class=\"form-text small\">\n {{ 'Site::FieldTypeNotSearchable' | abpLocalization }}\n </div>\n }\n </td>\n <td class=\"text-center\">\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [(ngModel)]=\"entry.showInList\"\n [ngModelOptions]=\"{ standalone: true }\"\n />\n </td>\n <td>\n <input\n type=\"text\"\n class=\"form-control form-control-sm\"\n [(ngModel)]=\"entry.displayName\"\n [ngModelOptions]=\"{ standalone: true }\"\n [placeholder]=\"fieldOf(entry)?.displayName ?? ''\"\n />\n </td>\n <td>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-link\"\n (click)=\"removeField(i)\"\n [title]=\"'AbpUi::Delete' | abpLocalization\"\n >\n <i class=\"fa fa-trash text-danger\"></i>\n </button>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n}\n", styles: [":host ::ng-deep .field-picker-select{width:100%}:host ::ng-deep .field-picker-select.ant-select{color:inherit!important}:host ::ng-deep .field-picker-select .ant-select-selector{background:transparent!important;border:none!important;padding:.475rem 1.25rem!important;box-shadow:none!important;height:auto!important}:host ::ng-deep .field-picker-select .ant-select-arrow{display:none!important}:host ::ng-deep .field-picker-select .ant-select-selection-placeholder{color:inherit!important}:host ::ng-deep .field-picker-select.form-select{padding:0!important}:host ::ng-deep .field-picker-select .ant-select-selection-item{background:var(--bs-secondary-bg, #f5f5f5)!important;border-color:var(--bs-border-color, #f0f0f0)!important}:host ::ng-deep .field-picker-select .ant-select-selection-item-remove{color:var(--bs-secondary-color, rgba(0, 0, 0, .45))!important}::ng-deep .field-picker-dropdown .ant-select-dropdown{background:var(--lpx-content-bg, var(--bs-secondary-bg, #fff))!important}::ng-deep .field-picker-dropdown .ant-select-item-option-active:not(.ant-select-item-option-disabled){background-color:var(--bs-primary)!important;color:var(--bs-white)!important}::ng-deep .field-picker-dropdown .ant-select-item{color:var(--bs-body-color)!important;background-color:transparent!important}\n"], dependencies: [{ kind: "ngmodule", type: CoreModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox]:not([ngNoCva])[formControlName],input[type=checkbox]:not([ngNoCva])[formControl],input[type=checkbox]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i2.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i2.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i2.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "ngmodule", type: NzSelectModule }, { kind: "component", type: i3.NzSelectComponent, selector: "nz-select", inputs: ["nzId", "nzSize", "nzStatus", "nzVariant", "nzOptionHeightPx", "nzOptionOverflowSize", "nzDropdownClassName", "nzDropdownMatchSelectWidth", "nzDropdownStyle", "nzNotFoundContent", "nzPlaceHolder", "nzPlacement", "nzMaxTagCount", "nzDropdownRender", "nzCustomTemplate", "nzPrefix", "nzSuffixIcon", "nzClearIcon", "nzRemoveIcon", "nzMenuItemSelectedIcon", "nzTokenSeparators", "nzMaxTagPlaceholder", "nzMaxMultipleCount", "nzMode", "nzFilterOption", "compareWith", "nzAllowClear", "nzShowSearch", "nzLoading", "nzAutoFocus", "nzAutoClearSearchValue", "nzServerSearch", "nzDisabled", "nzOpen", "nzSelectOnTab", "nzBackdrop", "nzOptions", "nzShowArrow"], outputs: ["nzOnSearch", "nzScrollToBottom", "nzOpenChange", "nzBlur", "nzFocus", "nzOnClear"], exportAs: ["nzSelect"] }, { kind: "pipe", type: i4.LocalizationPipe, name: "abpLocalization" }], changeDetection: i0.ChangeDetectionStrategy.Eager }); }
141
+ }
142
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.2", ngImport: i0, type: FieldArrangementComponent, decorators: [{
143
+ type: Component,
144
+ args: [{ selector: 'site-field-arrangement', changeDetection: ChangeDetectionStrategy.Eager, imports: [CoreModule, FormsModule, DragDropModule, NzSelectModule], template: "<div class=\"d-flex align-items-end gap-2 mb-3\">\n <div class=\"flex-grow-1\">\n <label class=\"form-label\">{{ 'Site::AddField' | abpLocalization }}</label>\n <!--\n nzDropdownMatchSelectWidth defaults to true, but inside this abp-modal ng-zorro measures the\n trigger's width before the modal's layout has settled and gets ~0 - the dropdown then renders\n with no real width and swallows clicks. Forcing it off falls back to content-based auto-width,\n which sidesteps the bad measurement entirely.\n -->\n <nz-select\n class=\"form-select field-picker-select\"\n nzMode=\"multiple\"\n nzShowSearch\n nzPlaceHolder=\"---\"\n [nzDropdownMatchSelectWidth]=\"false\"\n nzDropdownClassName=\"field-picker-dropdown\"\n [nzOptions]=\"addableFieldOptions\"\n [(ngModel)]=\"fieldsToAdd\"\n [ngModelOptions]=\"{ standalone: true }\"\n ></nz-select>\n </div>\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n [disabled]=\"fieldsToAdd.length === 0\"\n (click)=\"addFields()\"\n >\n <i class=\"fa fa-plus me-1\"></i>{{ 'Site::AddField' | abpLocalization }}\n </button>\n</div>\n\n@if (arrangement.length === 0) {\n <p class=\"text-muted\">{{ 'Site::NoFieldsArranged' | abpLocalization }}</p>\n} @else {\n <div class=\"table-responsive\">\n <table class=\"table table-sm align-middle mb-0\">\n <thead>\n <tr>\n <th style=\"width: 2rem\"></th>\n <th>{{ 'Site::DisplayName:Field.DisplayName' | abpLocalization }}</th>\n <th>{{ 'Site::DisplayName:Field.FieldTypeName' | abpLocalization }}</th>\n <th class=\"text-center\">{{ 'Site::DisplayName:ContentTypeField.Required' | abpLocalization }}</th>\n <th class=\"text-center\">{{ 'Site::DisplayName:ContentTypeField.Searchable' | abpLocalization }}</th>\n <th class=\"text-center\">{{ 'Site::DisplayName:ContentTypeField.ShowInList' | abpLocalization }}</th>\n <th>{{ 'Site::DisplayName:ContentTypeField.DisplayNameOverride' | abpLocalization }}</th>\n <th style=\"width: 3rem\"></th>\n </tr>\n </thead>\n <tbody cdkDropList (cdkDropListDropped)=\"drop($event)\">\n @for (entry of arrangement; track entry.fieldId; let i = $index) {\n <tr cdkDrag>\n <td class=\"text-muted\" cdkDragHandle style=\"cursor: move\">\n <i class=\"fa fa-grip-vertical\"></i>\n </td>\n <td>\n <div>{{ labelOf(entry) }}</div>\n <small class=\"text-muted\">{{ fieldOf(entry)?.name }}</small>\n </td>\n <td>{{ displayNameKeyOf(fieldOf(entry)?.fieldTypeName) | abpLocalization }}</td>\n <td class=\"text-center\">\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [(ngModel)]=\"entry.required\"\n [ngModelOptions]=\"{ standalone: true }\"\n />\n </td>\n <td class=\"text-center\">\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [(ngModel)]=\"entry.searchable\"\n [ngModelOptions]=\"{ standalone: true }\"\n [disabled]=\"!isSearchableSupported(entry)\"\n (ngModelChange)=\"onSearchableChange(entry)\"\n />\n @if (!isSearchableSupported(entry)) {\n <div class=\"form-text small\">\n {{ 'Site::FieldTypeNotSearchable' | abpLocalization }}\n </div>\n }\n </td>\n <td class=\"text-center\">\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [(ngModel)]=\"entry.showInList\"\n [ngModelOptions]=\"{ standalone: true }\"\n />\n </td>\n <td>\n <input\n type=\"text\"\n class=\"form-control form-control-sm\"\n [(ngModel)]=\"entry.displayName\"\n [ngModelOptions]=\"{ standalone: true }\"\n [placeholder]=\"fieldOf(entry)?.displayName ?? ''\"\n />\n </td>\n <td>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-link\"\n (click)=\"removeField(i)\"\n [title]=\"'AbpUi::Delete' | abpLocalization\"\n >\n <i class=\"fa fa-trash text-danger\"></i>\n </button>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n}\n", styles: [":host ::ng-deep .field-picker-select{width:100%}:host ::ng-deep .field-picker-select.ant-select{color:inherit!important}:host ::ng-deep .field-picker-select .ant-select-selector{background:transparent!important;border:none!important;padding:.475rem 1.25rem!important;box-shadow:none!important;height:auto!important}:host ::ng-deep .field-picker-select .ant-select-arrow{display:none!important}:host ::ng-deep .field-picker-select .ant-select-selection-placeholder{color:inherit!important}:host ::ng-deep .field-picker-select.form-select{padding:0!important}:host ::ng-deep .field-picker-select .ant-select-selection-item{background:var(--bs-secondary-bg, #f5f5f5)!important;border-color:var(--bs-border-color, #f0f0f0)!important}:host ::ng-deep .field-picker-select .ant-select-selection-item-remove{color:var(--bs-secondary-color, rgba(0, 0, 0, .45))!important}::ng-deep .field-picker-dropdown .ant-select-dropdown{background:var(--lpx-content-bg, var(--bs-secondary-bg, #fff))!important}::ng-deep .field-picker-dropdown .ant-select-item-option-active:not(.ant-select-item-option-disabled){background-color:var(--bs-primary)!important;color:var(--bs-white)!important}::ng-deep .field-picker-dropdown .ant-select-item{color:var(--bs-body-color)!important;background-color:transparent!important}\n"] }]
145
+ }], propDecorators: { arrangement: [{
146
+ type: Input
147
+ }], fieldsById: [{
148
+ type: Input
149
+ }], indexableByFieldType: [{
150
+ type: Input
151
+ }] } });
152
+
153
+ const MAX_NAME_LENGTH = 64;
154
+ const MAX_DISPLAY_NAME_LENGTH = 128;
155
+ const MAX_DESCRIPTION_LENGTH = 512;
156
+ /** Mirrors `IdentifierName.Pattern` (`src/Dignite.Site.Domain.Shared/IdentifierName.cs`). */
157
+ const NAME_PATTERN = /^[a-z0-9][a-z0-9_-]*$/;
158
+ /** The content types of one page. Reached from the Pages list; there is no menu entry. */
159
+ class ContentTypesComponent {
160
+ constructor() {
161
+ this.contentTypeService = inject(ContentTypeAdminService);
162
+ this.pageService = inject(PageAdminService);
163
+ this.referenceData = inject(SiteReferenceDataService);
164
+ this.route = inject(ActivatedRoute);
165
+ this.fb = inject(FormBuilder);
166
+ this.confirmation = inject(ConfirmationService);
167
+ this.toaster = inject(ToasterService);
168
+ this.permission = inject(PermissionService);
169
+ this.policies = eSitePolicyNames;
170
+ this.pageId = this.route.snapshot.paramMap.get('pageId') ?? '';
171
+ // State that changes in an HTTP callback is a signal, not a plain field: the host layout's ancestors
172
+ // (`abp-router-outlet`, the LeptonX layouts) are OnPush, and a plain-field write in a response
173
+ // callback never marks them for check, so the page would stay empty until some unrelated event did.
174
+ this.page = signal(undefined, /* @ts-ignore */
175
+ ...(ngDevMode ? [{ debugName: "page" }] : /* istanbul ignore next */ []));
176
+ this.contentTypes = signal([], /* @ts-ignore */
177
+ ...(ngDevMode ? [{ debugName: "contentTypes" }] : /* istanbul ignore next */ []));
178
+ this.fieldsById = signal(new Map(), /* @ts-ignore */
179
+ ...(ngDevMode ? [{ debugName: "fieldsById" }] : /* istanbul ignore next */ []));
180
+ this.indexableByFieldType = signal(new Map(), /* @ts-ignore */
181
+ ...(ngDevMode ? [{ debugName: "indexableByFieldType" }] : /* istanbul ignore next */ []));
182
+ this.isModalOpen = signal(false, /* @ts-ignore */
183
+ ...(ngDevMode ? [{ debugName: "isModalOpen" }] : /* istanbul ignore next */ []));
184
+ this.isModalBusy = signal(false, /* @ts-ignore */
185
+ ...(ngDevMode ? [{ debugName: "isModalBusy" }] : /* istanbul ignore next */ []));
186
+ /**
187
+ * The complete arrangement being edited. Held here, not inside the form, because it is saved whole:
188
+ * `UpdateContentTypeDto.Fields` replaces the server's list outright, so anything missing from this
189
+ * array comes back reset to its default.
190
+ */
191
+ this.arrangement = [];
192
+ this.pageService.get(this.pageId).subscribe(page => this.page.set(page));
193
+ forkJoin({
194
+ fields: this.referenceData.getFieldsById(),
195
+ indexable: this.referenceData.getIndexableByFieldType(),
196
+ }).subscribe(({ fields, indexable }) => {
197
+ this.fieldsById.set(fields);
198
+ this.indexableByFieldType.set(indexable);
199
+ });
200
+ this.load(true);
201
+ }
202
+ /**
203
+ * `autoOpenIfSingle` only applies to this initial load: a page with exactly one content type has
204
+ * nothing to disambiguate, so arriving here always means "go edit it". Reloads after a save or delete
205
+ * must not repeat this - otherwise closing the edit modal after a successful save would immediately
206
+ * reopen it.
207
+ */
208
+ load(autoOpenIfSingle = false) {
209
+ this.contentTypeService.getListByPage(this.pageId).subscribe(result => {
210
+ const contentTypes = result.items ?? [];
211
+ this.contentTypes.set(contentTypes);
212
+ if (autoOpenIfSingle &&
213
+ contentTypes.length === 1 &&
214
+ this.permission.getGrantedPolicy(this.policies.ContentTypesUpdate)) {
215
+ this.openEdit(contentTypes[0]);
216
+ }
217
+ });
218
+ }
219
+ fieldCountOf(contentType) {
220
+ return contentType.fields?.length ?? 0;
221
+ }
222
+ openCreate() {
223
+ this.editingContentType = undefined;
224
+ this.arrangement = [];
225
+ this.form = this.buildForm();
226
+ this.isModalOpen.set(true);
227
+ }
228
+ openEdit(contentType) {
229
+ this.editingContentType = contentType;
230
+ // A copy: abandoning the modal must not leave the list's row mutated by the arrangement editor.
231
+ this.arrangement = (contentType.fields ?? [])
232
+ .map(entry => ({ ...entry }))
233
+ .sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
234
+ this.form = this.buildForm(contentType);
235
+ this.isModalOpen.set(true);
236
+ }
237
+ closeModal() {
238
+ this.isModalOpen.set(false);
239
+ this.form = undefined;
240
+ this.editingContentType = undefined;
241
+ this.arrangement = [];
242
+ }
243
+ save() {
244
+ if (!this.form || this.form.invalid || this.isModalBusy()) {
245
+ return;
246
+ }
247
+ this.isModalBusy.set(true);
248
+ const value = this.form.getRawValue();
249
+ const request$ = this.editingContentType
250
+ ? this.contentTypeService.update(this.editingContentType.id, {
251
+ name: value.name,
252
+ displayName: value.displayName,
253
+ description: value.description,
254
+ fields: this.arrangement,
255
+ })
256
+ : this.contentTypeService.create({
257
+ pageId: this.pageId,
258
+ name: value.name,
259
+ displayName: value.displayName,
260
+ description: value.description,
261
+ fields: this.arrangement,
262
+ });
263
+ request$.subscribe({
264
+ next: () => {
265
+ this.isModalBusy.set(false);
266
+ this.closeModal();
267
+ this.load();
268
+ this.referenceData.invalidateContentTypes();
269
+ this.toaster.success('AbpUi::SavedSuccessfully');
270
+ },
271
+ error: () => this.isModalBusy.set(false),
272
+ });
273
+ }
274
+ delete(contentType) {
275
+ this.confirmation
276
+ .warn('AbpUi::ItemWillBeDeletedMessage', 'AbpUi::AreYouSure', {
277
+ messageLocalizationParams: [contentType.displayName ?? ''],
278
+ })
279
+ .subscribe((status) => {
280
+ if (status === Confirmation.Status.confirm) {
281
+ // Answers ContentTypeStillHasContents when contents remain; the server's message says so.
282
+ this.contentTypeService.delete(contentType.id).subscribe(() => {
283
+ this.load();
284
+ this.referenceData.invalidateContentTypes();
285
+ this.toaster.success('AbpUi::SuccessfullyDeleted');
286
+ });
287
+ }
288
+ });
289
+ }
290
+ buildForm(contentType) {
291
+ return this.fb.group({
292
+ displayName: [
293
+ contentType?.displayName ?? '',
294
+ [Validators.required, Validators.maxLength(MAX_DISPLAY_NAME_LENGTH)],
295
+ ],
296
+ name: [
297
+ contentType?.name ?? '',
298
+ [Validators.required, Validators.maxLength(MAX_NAME_LENGTH), Validators.pattern(NAME_PATTERN)],
299
+ ],
300
+ description: [
301
+ contentType?.description ?? '',
302
+ [Validators.maxLength(MAX_DESCRIPTION_LENGTH)],
303
+ ],
304
+ });
305
+ }
306
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.2", ngImport: i0, type: ContentTypesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
307
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.2.2", type: ContentTypesComponent, isStandalone: true, selector: "site-content-types", viewQueries: [{ propertyName: "submitButton", first: true, predicate: ["submitButton"], descendants: true }], ngImport: i0, template: "<div class=\"card\">\n <div class=\"card-body\">\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\n <h5 class=\"mb-0\">\n {{ 'Site::ContentTypesOfPage' | abpLocalization: (page()?.displayName ?? '') }}\n </h5>\n <button\n *abpPermission=\"policies.ContentTypesCreate\"\n type=\"button\"\n class=\"btn btn-primary\"\n (click)=\"openCreate()\"\n >\n <i class=\"fa fa-plus me-1\"></i>{{ 'Site::NewContentType' | abpLocalization }}\n </button>\n </div>\n\n <div class=\"table-responsive\">\n <table class=\"table align-middle mb-0\">\n <thead>\n <tr>\n <th>{{ 'Site::DisplayName:ContentType.DisplayName' | abpLocalization }}</th>\n <th>{{ 'Site::DisplayName:ContentType.Name' | abpLocalization }}</th>\n <th>{{ 'Site::FieldArrangement' | abpLocalization }}</th>\n <th>{{ 'AbpUi::Actions' | abpLocalization }}</th>\n </tr>\n </thead>\n <tbody>\n @for (contentType of contentTypes(); track contentType.id) {\n <tr>\n <td>\n <div>{{ contentType.displayName }}</div>\n @if (contentType.description) {\n <small class=\"text-muted\">{{ contentType.description }}</small>\n }\n </td>\n <td>{{ contentType.name }}</td>\n <td>{{ fieldCountOf(contentType) }}</td>\n <td>\n <button\n *abpPermission=\"policies.ContentTypesUpdate\"\n type=\"button\"\n class=\"btn btn-sm btn-light me-1\"\n (click)=\"openEdit(contentType)\"\n [title]=\"'AbpUi::Edit' | abpLocalization\"\n >\n <i class=\"fa fa-pencil\"></i>\n </button>\n <button\n *abpPermission=\"policies.ContentTypesDelete\"\n type=\"button\"\n class=\"btn btn-sm btn-light\"\n (click)=\"delete(contentType)\"\n [title]=\"'AbpUi::Delete' | abpLocalization\"\n >\n <i class=\"fa fa-trash text-danger\"></i>\n </button>\n </td>\n </tr>\n } @empty {\n <tr>\n <td colspan=\"4\" class=\"text-muted\">\n {{ 'AbpUi::NoDataAvailableInDatatable' | abpLocalization }}\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n</div>\n\n<abp-modal\n [(visible)]=\"isModalOpen\"\n [busy]=\"isModalBusy()\"\n [options]=\"{ size: 'xl' }\"\n (disappear)=\"closeModal()\"\n>\n <ng-template #abpHeader>\n <h3>\n {{ (editingContentType ? 'Site::EditContentType' : 'Site::NewContentType') | abpLocalization }}\n </h3>\n </ng-template>\n <ng-template #abpBody>\n @if (form) {\n <form [formGroup]=\"form\" (submit)=\"save()\">\n <button type=\"submit\" #submitButton style=\"display: none\"></button>\n\n <div class=\"row\">\n <div class=\"col-md-6 mb-3\">\n <label class=\"form-label\">\n {{ 'Site::DisplayName:ContentType.DisplayName' | abpLocalization }}\n </label>\n <input type=\"text\" class=\"form-control\" formControlName=\"displayName\" />\n </div>\n <div class=\"col-md-6 mb-3\">\n <label class=\"form-label\">\n {{ 'Site::DisplayName:ContentType.Name' | abpLocalization }}\n </label>\n <input type=\"text\" class=\"form-control\" formControlName=\"name\" />\n <small class=\"form-text text-muted\">\n {{ 'Site::Description:ContentType.Name' | abpLocalization }}\n </small>\n </div>\n </div>\n\n <!-- Description sits above everything structural on purpose: it is product copy aimed at AI -\n an MCP client picks the right content type by reading it (\u603B\u4F53\u8BBE\u8BA1 \u00A76.2.6). -->\n <div class=\"mb-3\">\n <label class=\"form-label fw-semibold\">\n {{ 'Site::DisplayName:ContentType.Description' | abpLocalization }}\n </label>\n <textarea class=\"form-control\" formControlName=\"description\" rows=\"3\"></textarea>\n <small class=\"form-text text-muted\">\n {{ 'Site::Description:ContentType.Description' | abpLocalization }}\n </small>\n </div>\n\n <hr />\n <h6>{{ 'Site::FieldArrangement' | abpLocalization }}</h6>\n </form>\n\n <!-- Outside the form: the arrangement is plain state, saved whole alongside it. -->\n <site-field-arrangement\n [arrangement]=\"arrangement\"\n [fieldsById]=\"fieldsById()\"\n [indexableByFieldType]=\"indexableByFieldType()\"\n ></site-field-arrangement>\n }\n </ng-template>\n <ng-template #abpFooter>\n <button type=\"button\" class=\"btn btn-secondary\" abpClose>\n {{ 'AbpUi::Cancel' | abpLocalization }}\n </button>\n <abp-button\n iconClass=\"fa fa-check\"\n [disabled]=\"!form?.valid\"\n [loading]=\"isModalBusy()\"\n (click)=\"submitButton?.nativeElement?.click()\"\n >\n {{ 'AbpUi::Save' | abpLocalization }}\n </abp-button>\n </ng-template>\n</abp-modal>\n", dependencies: [{ kind: "ngmodule", type: CoreModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i3$1.ValidationGroupDirective, selector: "[formGroup],[formGroupName]", exportAs: ["validationGroup"] }, { kind: "directive", type: i3$1.ValidationDirective, selector: "[formControl],[formControlName]", exportAs: ["validationDirective"] }, { kind: "directive", type: i4.PermissionDirective, selector: "[abpPermission]", inputs: ["abpPermission", "abpPermissionRunChangeDetection"] }, { kind: "ngmodule", type: ThemeSharedModule }, { kind: "component", type: i2$1.ButtonComponent, selector: "abp-button", inputs: ["buttonId", "buttonClass", "buttonType", "formName", "iconClass", "loading", "disabled", "attributes"], outputs: ["click", "focus", "blur", "abpClick", "abpFocus", "abpBlur"] }, { kind: "component", type: i2$1.ModalComponent, selector: "abp-modal", inputs: ["visible", "busy", "options", "suppressUnsavedChangesWarning"], outputs: ["visibleChange", "init", "appear", "disappear"] }, { kind: "directive", type: i2$1.ModalCloseDirective, selector: "[abpClose]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: FieldArrangementComponent, selector: "site-field-arrangement", inputs: ["arrangement", "fieldsById", "indexableByFieldType"] }, { kind: "pipe", type: i4.LocalizationPipe, name: "abpLocalization" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
308
+ }
309
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.2", ngImport: i0, type: ContentTypesComponent, decorators: [{
310
+ type: Component,
311
+ args: [{ selector: 'site-content-types', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
312
+ CoreModule,
313
+ ThemeSharedModule,
314
+ ReactiveFormsModule,
315
+ PermissionDirective,
316
+ FieldArrangementComponent,
317
+ ], template: "<div class=\"card\">\n <div class=\"card-body\">\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\n <h5 class=\"mb-0\">\n {{ 'Site::ContentTypesOfPage' | abpLocalization: (page()?.displayName ?? '') }}\n </h5>\n <button\n *abpPermission=\"policies.ContentTypesCreate\"\n type=\"button\"\n class=\"btn btn-primary\"\n (click)=\"openCreate()\"\n >\n <i class=\"fa fa-plus me-1\"></i>{{ 'Site::NewContentType' | abpLocalization }}\n </button>\n </div>\n\n <div class=\"table-responsive\">\n <table class=\"table align-middle mb-0\">\n <thead>\n <tr>\n <th>{{ 'Site::DisplayName:ContentType.DisplayName' | abpLocalization }}</th>\n <th>{{ 'Site::DisplayName:ContentType.Name' | abpLocalization }}</th>\n <th>{{ 'Site::FieldArrangement' | abpLocalization }}</th>\n <th>{{ 'AbpUi::Actions' | abpLocalization }}</th>\n </tr>\n </thead>\n <tbody>\n @for (contentType of contentTypes(); track contentType.id) {\n <tr>\n <td>\n <div>{{ contentType.displayName }}</div>\n @if (contentType.description) {\n <small class=\"text-muted\">{{ contentType.description }}</small>\n }\n </td>\n <td>{{ contentType.name }}</td>\n <td>{{ fieldCountOf(contentType) }}</td>\n <td>\n <button\n *abpPermission=\"policies.ContentTypesUpdate\"\n type=\"button\"\n class=\"btn btn-sm btn-light me-1\"\n (click)=\"openEdit(contentType)\"\n [title]=\"'AbpUi::Edit' | abpLocalization\"\n >\n <i class=\"fa fa-pencil\"></i>\n </button>\n <button\n *abpPermission=\"policies.ContentTypesDelete\"\n type=\"button\"\n class=\"btn btn-sm btn-light\"\n (click)=\"delete(contentType)\"\n [title]=\"'AbpUi::Delete' | abpLocalization\"\n >\n <i class=\"fa fa-trash text-danger\"></i>\n </button>\n </td>\n </tr>\n } @empty {\n <tr>\n <td colspan=\"4\" class=\"text-muted\">\n {{ 'AbpUi::NoDataAvailableInDatatable' | abpLocalization }}\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n</div>\n\n<abp-modal\n [(visible)]=\"isModalOpen\"\n [busy]=\"isModalBusy()\"\n [options]=\"{ size: 'xl' }\"\n (disappear)=\"closeModal()\"\n>\n <ng-template #abpHeader>\n <h3>\n {{ (editingContentType ? 'Site::EditContentType' : 'Site::NewContentType') | abpLocalization }}\n </h3>\n </ng-template>\n <ng-template #abpBody>\n @if (form) {\n <form [formGroup]=\"form\" (submit)=\"save()\">\n <button type=\"submit\" #submitButton style=\"display: none\"></button>\n\n <div class=\"row\">\n <div class=\"col-md-6 mb-3\">\n <label class=\"form-label\">\n {{ 'Site::DisplayName:ContentType.DisplayName' | abpLocalization }}\n </label>\n <input type=\"text\" class=\"form-control\" formControlName=\"displayName\" />\n </div>\n <div class=\"col-md-6 mb-3\">\n <label class=\"form-label\">\n {{ 'Site::DisplayName:ContentType.Name' | abpLocalization }}\n </label>\n <input type=\"text\" class=\"form-control\" formControlName=\"name\" />\n <small class=\"form-text text-muted\">\n {{ 'Site::Description:ContentType.Name' | abpLocalization }}\n </small>\n </div>\n </div>\n\n <!-- Description sits above everything structural on purpose: it is product copy aimed at AI -\n an MCP client picks the right content type by reading it (\u603B\u4F53\u8BBE\u8BA1 \u00A76.2.6). -->\n <div class=\"mb-3\">\n <label class=\"form-label fw-semibold\">\n {{ 'Site::DisplayName:ContentType.Description' | abpLocalization }}\n </label>\n <textarea class=\"form-control\" formControlName=\"description\" rows=\"3\"></textarea>\n <small class=\"form-text text-muted\">\n {{ 'Site::Description:ContentType.Description' | abpLocalization }}\n </small>\n </div>\n\n <hr />\n <h6>{{ 'Site::FieldArrangement' | abpLocalization }}</h6>\n </form>\n\n <!-- Outside the form: the arrangement is plain state, saved whole alongside it. -->\n <site-field-arrangement\n [arrangement]=\"arrangement\"\n [fieldsById]=\"fieldsById()\"\n [indexableByFieldType]=\"indexableByFieldType()\"\n ></site-field-arrangement>\n }\n </ng-template>\n <ng-template #abpFooter>\n <button type=\"button\" class=\"btn btn-secondary\" abpClose>\n {{ 'AbpUi::Cancel' | abpLocalization }}\n </button>\n <abp-button\n iconClass=\"fa fa-check\"\n [disabled]=\"!form?.valid\"\n [loading]=\"isModalBusy()\"\n (click)=\"submitButton?.nativeElement?.click()\"\n >\n {{ 'AbpUi::Save' | abpLocalization }}\n </abp-button>\n </ng-template>\n</abp-modal>\n" }]
318
+ }], ctorParameters: () => [], propDecorators: { submitButton: [{
319
+ type: ViewChild,
320
+ args: ['submitButton']
321
+ }] } });
322
+
323
+ export { ContentTypesComponent };
324
+ //# sourceMappingURL=dignite-ng.site-content-types.component-D2jMSSq6.mjs.map