@firestitch/content 18.0.10 → 18.0.12

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.
@@ -1,30 +1,29 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, ElementRef, Component, ChangeDetectionStrategy, ViewChild, Input, InjectionToken, ChangeDetectorRef, NgModule, EventEmitter, Output, ViewChildren } from '@angular/core';
2
+ import { inject, ElementRef, Component, ChangeDetectionStrategy, ViewChild, Input, InjectionToken, ChangeDetectorRef, NgModule, input, model, signal, computed, DestroyRef, EventEmitter, Output, viewChild } from '@angular/core';
3
3
  import { DomSanitizer, Title } from '@angular/platform-browser';
4
4
  import { Router, NavigationEnd } from '@angular/router';
5
5
  import { HtmlRenderer } from '@firestitch/html';
6
- import { Subject, of, throwError, fromEvent } from 'rxjs';
7
- import { filter, takeUntil, tap, switchMap, finalize, map } from 'rxjs/operators';
6
+ import { Subject, EMPTY, concat, fromEvent } from 'rxjs';
7
+ import { filter, takeUntil, last, tap, finalize, map } from 'rxjs/operators';
8
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
8
9
  import * as i2$1 from '@angular/forms';
9
10
  import { FormsModule } from '@angular/forms';
10
11
  import { CdkScrollable } from '@angular/cdk/scrolling';
11
12
  import * as i4 from '@angular/material/dialog';
12
13
  import { MAT_DIALOG_DATA, MatDialogRef, MatDialogTitle, MatDialogContent, MatDialogActions, MatDialog } from '@angular/material/dialog';
13
- import * as i5$1 from '@firestitch/dialog';
14
+ import * as i5 from '@firestitch/dialog';
14
15
  import { FsDialogModule } from '@firestitch/dialog';
15
16
  import * as i3 from '@firestitch/form';
16
17
  import { FsFormModule, FsFormDirective } from '@firestitch/form';
17
- import { FsMessage } from '@firestitch/message';
18
18
  import { FsPrompt } from '@firestitch/prompt';
19
19
  import * as i1$1 from '@firestitch/skeleton';
20
20
  import { FsSkeletonModule } from '@firestitch/skeleton';
21
+ import { FsMessage } from '@firestitch/message';
21
22
  import * as i2 from '@firestitch/text-editor';
22
23
  import { FsTextEditorModule, FsTextEditorComponent } from '@firestitch/text-editor';
23
24
  import * as i1 from 'angular-split';
24
25
  import { AngularSplitModule } from 'angular-split';
25
26
  import { NgClass, NgTemplateOutlet } from '@angular/common';
26
- import * as i5 from '@firestitch/common';
27
- import { FsCommonModule, index } from '@firestitch/common';
28
27
  import { MatButtonToggleGroup, MatButtonToggle } from '@angular/material/button-toggle';
29
28
  import { MatButton, MatIconButton } from '@angular/material/button';
30
29
  import { MatIcon } from '@angular/material/icon';
@@ -38,6 +37,8 @@ import * as i2$2 from '@firestitch/date';
38
37
  import { FsDateModule } from '@firestitch/date';
39
38
  import { MatSelect } from '@angular/material/select';
40
39
  import { MatOption } from '@angular/material/core';
40
+ import * as i4$1 from '@firestitch/common';
41
+ import { index, FsCommonModule } from '@firestitch/common';
41
42
 
42
43
  class ContentRendererComponent {
43
44
  _sanitizer = inject(DomSanitizer);
@@ -226,116 +227,132 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImpor
226
227
  }] } });
227
228
 
228
229
  class EditorComponent {
229
- _cdRef = inject(ChangeDetectorRef);
230
- _message = inject(FsMessage);
231
- showHtml = false;
232
- showScss = false;
233
- showJs = false;
234
- showGlobalScss = false;
235
- html;
236
- scss;
237
- js;
238
- contentConfig;
239
- changed = new EventEmitter();
240
- focused = new EventEmitter();
241
- blured = new EventEmitter();
242
- changes = {};
230
+ showHtml = input(false);
231
+ showScss = input(false);
232
+ showJs = input(false);
233
+ showGlobalScss = input(false);
234
+ contentConfig = input();
235
+ // Two-way bound to the text editors, so each holds exactly what is on screen. Nothing sits
236
+ // between a keystroke and a save: the fsModelChange this replaced debounced every pane by
237
+ // 300 ms (its `debounce: 0` falls back to 300), so a quick Ctrl+S posted text without its
238
+ // last keystrokes (IEB-T292).
239
+ html = model();
240
+ scss = model();
241
+ js = model();
242
+ globalScss = signal(null);
243
+ contentStyle = signal(null);
244
+ focusedArea = signal(null);
245
+ saving = signal(false);
246
+ // A pane is changed when its text differs from what the server last confirmed. A pane
247
+ // cleared to '' counts; one typed back to its original text does not (IEB-T292).
248
+ changedTypes = computed(() => {
249
+ const saved = this._saved();
250
+ return Object.values(EditorType)
251
+ .filter((type) => type in saved && this.value(type) !== saved[type]);
252
+ });
253
+ hasChanges = computed(() => this.changedTypes().length !== 0);
243
254
  EditorType = EditorType;
244
- focusedArea;
245
- contentStyle;
246
- resizing = false;
247
- title;
248
255
  scssConfig;
249
256
  globalScssConfig;
250
257
  htmlConfig;
251
258
  jsConfig;
252
- _destroy$ = new Subject();
259
+ _saved = signal({});
260
+ _message = inject(FsMessage);
261
+ _destroyRef = inject(DestroyRef);
253
262
  ngOnInit() {
254
- this.initTextEditors();
255
- this.initGlobalContentStyle();
256
- }
257
- ngOnDestroy() {
258
- this._destroy$.next(null);
259
- this._destroy$.complete();
263
+ this._initTextEditors();
264
+ this._saved.set({
265
+ [EditorType.Html]: this.html() ?? '',
266
+ [EditorType.Scss]: this.scss() ?? '',
267
+ [EditorType.Js]: this.js() ?? '',
268
+ });
269
+ this._initGlobalContentStyle();
270
+ }
271
+ value(type) {
272
+ const panes = {
273
+ [EditorType.Html]: this.html,
274
+ [EditorType.Scss]: this.scss,
275
+ [EditorType.Js]: this.js,
276
+ [EditorType.GlobalScss]: this.globalScss,
277
+ };
278
+ return panes[type]() ?? '';
279
+ }
280
+ /**
281
+ * Saves every changed pane, not just the one last focused, and records what was sent as
282
+ * the new baseline, so anything typed while the request is in flight stays changed. With
283
+ * nothing changed it sends nothing and shows no toast. Ctrl+S reaches this through fsForm's
284
+ * [submit], so it shares the Save button's gate (IEB-T292).
285
+ *
286
+ * `fields` maps a pane to its property on the record (html → content); `saveRecord` posts
287
+ * those properties.
288
+ */
289
+ save$(fields, saveRecord) {
290
+ const sent = Object.fromEntries(this.changedTypes().map((type) => [type, this.value(type)]));
291
+ const saves = [
292
+ this._saveRecord$(fields, sent, saveRecord),
293
+ this._saveGlobalScss$(sent),
294
+ ].filter((save) => !!save);
295
+ if (!saves.length) {
296
+ return EMPTY;
297
+ }
298
+ this.saving.set(true);
299
+ return concat(...saves)
300
+ .pipe(last(), tap(() => this._message.success('Saved Changes')), finalize(() => this.saving.set(false)));
301
+ }
302
+ _saveRecord$(fields, sent, saveRecord) {
303
+ const types = Object.keys(sent)
304
+ .filter((type) => fields[type]);
305
+ if (!types.length) {
306
+ return null;
307
+ }
308
+ return saveRecord(Object.fromEntries(types.map((type) => [fields[type], sent[type]])))
309
+ .pipe(tap(() => this._markSaved(types, sent)));
260
310
  }
261
- change(type, value) {
262
- this.changed.emit({ type, value });
263
- this.changes[type] = value;
311
+ _saveGlobalScss$(sent) {
312
+ if (!(EditorType.GlobalScss in sent)) {
313
+ return null;
314
+ }
315
+ const contentStyle = { ...this.contentStyle(), scss: sent[EditorType.GlobalScss] };
316
+ return this.contentConfig().saveContentStyle(contentStyle)
317
+ .pipe(tap(() => {
318
+ this.contentStyle.set(contentStyle);
319
+ this._markSaved([EditorType.GlobalScss], sent);
320
+ }));
264
321
  }
265
- get hasChanges() {
266
- return Object.keys(this.changes)
267
- .filter((name) => !!this.changes[name])
268
- .length !== 0;
322
+ _markSaved(types, sent) {
323
+ this._saved.update((saved) => ({
324
+ ...saved,
325
+ ...Object.fromEntries(types.map((type) => [type, sent[type]])),
326
+ }));
269
327
  }
270
- clearChange(type) {
271
- this.changes[type] = undefined;
272
- this._cdRef.markForCheck();
328
+ _initTextEditors() {
329
+ this.scssConfig = this._createTextEditorConfig(EditorType.Scss, 'scss');
330
+ this.jsConfig = this._createTextEditorConfig(EditorType.Js, 'js');
331
+ this.htmlConfig = this._createTextEditorConfig(EditorType.Html, 'html');
332
+ this.globalScssConfig = this._createTextEditorConfig(EditorType.GlobalScss, 'scss');
273
333
  }
274
- initTextEditors() {
275
- this.scssConfig = {
276
- tabSize: 2,
277
- language: 'scss',
278
- height: '100%',
279
- focus: () => {
280
- this._onFocus(EditorType.Scss);
281
- },
282
- blur: () => {
283
- this.blured.emit(EditorType.Scss);
284
- },
285
- };
286
- this.jsConfig = {
287
- tabSize: 2,
288
- language: 'js',
289
- height: '100%',
290
- focus: () => {
291
- this._onFocus(EditorType.Js);
292
- },
293
- blur: () => {
294
- this.blured.emit(EditorType.Js);
295
- },
296
- };
297
- this.htmlConfig = {
298
- tabSize: 2,
299
- language: 'html',
300
- height: '100%',
301
- focus: () => {
302
- this._onFocus(EditorType.Html);
303
- },
304
- blur: () => {
305
- this.blured.emit(EditorType.Html);
306
- },
307
- };
308
- this.globalScssConfig = {
334
+ _createTextEditorConfig(type, language) {
335
+ return {
309
336
  tabSize: 2,
310
- language: 'scss',
337
+ language,
311
338
  height: '100%',
312
339
  focus: () => {
313
- this._onFocus(EditorType.GlobalScss);
314
- },
315
- blur: () => {
316
- this.blured.emit(EditorType.GlobalScss);
340
+ this.focusedArea.set(type);
317
341
  },
318
342
  };
319
343
  }
320
- initGlobalContentStyle() {
321
- this.contentConfig.loadContentStyle()
322
- .subscribe((contentStyle) => {
323
- this.contentStyle = contentStyle || {};
324
- this._cdRef.markForCheck();
325
- });
326
- }
327
- saveGlobalScss() {
328
- return this.contentConfig.saveContentStyle(this.contentStyle)
329
- .pipe(tap(() => {
330
- this._message.success('Saved Changes');
331
- }));
332
- }
333
- _onFocus(type) {
334
- this.focusedArea = type;
335
- this.focused.emit(type);
344
+ _initGlobalContentStyle() {
345
+ this.contentConfig().loadContentStyle()
346
+ .pipe(tap((contentStyle) => {
347
+ const scss = contentStyle?.scss ?? '';
348
+ this.contentStyle.set(contentStyle || {});
349
+ this.globalScss.set(scss);
350
+ this._saved.update((saved) => ({ ...saved, [EditorType.GlobalScss]: scss }));
351
+ }), takeUntilDestroyed(this._destroyRef))
352
+ .subscribe();
336
353
  }
337
354
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: EditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
338
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.7", type: EditorComponent, isStandalone: true, selector: "app-editor", inputs: { showHtml: "showHtml", showScss: "showScss", showJs: "showJs", showGlobalScss: "showGlobalScss", html: "html", scss: "scss", js: "js", contentConfig: "contentConfig" }, outputs: { changed: "changed", focused: "focused", blured: "blured" }, ngImport: i0, template: "<as-split [unit]=\"'percent'\" [gutterSize]=\"25\"> \n <as-split-area [size]=\"70\" [visible]=\"showHtml\" [order]=\"1\">\n <div class=\"editor-container\">\n <app-editor-label\n [changed]=\"changes.html !== undefined\"\n [focused]=\"focusedArea === 'html'\">\n HTML\n </app-editor-label>\n <fs-text-editor \n [(ngModel)]=\"html\" \n name=\"html\"\n [fsModelChangeOptions]=\"{ debounce: 0 }\"\n (fsModelChange)=\"change(EditorType.Html, $event)\"\n [config]=\"htmlConfig\">\n </fs-text-editor> \n </div>\n </as-split-area>\n <as-split-area [size]=\"30\" [visible]=\"showScss\" [order]=\"2\">\n <div class=\"editor-container\">\n <app-editor-label\n [changed]=\"changes.scss !== undefined\"\n [focused]=\"focusedArea === 'scss'\">\n SCSS\n </app-editor-label>\n <fs-text-editor \n [(ngModel)]=\"scss\" \n name=\"scss\"\n [fsModelChangeOptions]=\"{ debounce: 0 }\"\n (fsModelChange)=\"change(EditorType.Scss, $event)\"\n [config]=\"scssConfig\">\n </fs-text-editor> \n </div>\n </as-split-area>\n <as-split-area [size]=\"30\" [visible]=\"showJs\" [order]=\"3\">\n <div class=\"editor-container\">\n <app-editor-label\n [changed]=\"changes.js !== undefined\"\n [focused]=\"focusedArea === 'js'\">\n JS\n </app-editor-label>\n <fs-text-editor \n [(ngModel)]=\"js\" \n name=\"js\"\n [fsModelChangeOptions]=\"{ debounce: 0 }\"\n (fsModelChange)=\"change(EditorType.Js, $event)\"\n [config]=\"jsConfig\">\n </fs-text-editor> \n </div>\n </as-split-area>\n <as-split-area [size]=\"30\" [visible]=\"showGlobalScss\" [order]=\"4\">\n <div class=\"editor-container\">\n <app-editor-label\n [changed]=\"changes.globalScss !== undefined\"\n [focused]=\"focusedArea === 'globalScss'\">\n Global SCSS\n </app-editor-label>\n <ng-container *fsSkeleton=\"contentStyle\">\n <fs-text-editor \n [(ngModel)]=\"contentStyle.scss\" \n name=\"globalScss\"\n [fsModelChangeOptions]=\"{ debounce: 300 }\"\n (fsModelChange)=\"change(EditorType.GlobalScss, $event)\"\n [config]=\"globalScssConfig\">\n </fs-text-editor> \n </ng-container> \n </div>\n </as-split-area>\n</as-split>\n", styles: [":host{display:block;flex:1;height:100%;min-height:0}as-split{height:100%}:host ::ng-deep .as-split-gutter{background-color:unset!important}.editor-container{height:100%;border-radius:5px;display:flex;flex-direction:column}.editor-container fs-text-editor{flex:1}\n"], dependencies: [{ kind: "ngmodule", type: AngularSplitModule }, { kind: "component", type: i1.SplitComponent, selector: "as-split", inputs: ["direction", "unit", "gutterSize", "gutterStep", "restrictMove", "useTransition", "disabled", "dir", "gutterDblClickDuration", "gutterClickDeltaPx", "gutterAriaLabel"], outputs: ["transitionEnd", "dragStart", "dragEnd", "gutterClick", "gutterDblClick"], exportAs: ["asSplit"] }, { kind: "directive", type: i1.SplitAreaDirective, selector: "as-split-area, [as-split-area]", inputs: ["order", "size", "minSize", "maxSize", "lockSize", "visible"], exportAs: ["asSplitArea"] }, { kind: "component", type: EditorLabelComponent, selector: "app-editor-label", inputs: ["changed", "focused"] }, { kind: "ngmodule", type: FsTextEditorModule }, { kind: "component", type: i2.FsTextEditorComponent, selector: "fs-text-editor", inputs: ["config", "scrollable"], outputs: ["ready", "blur"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: FsFormModule }, { kind: "directive", type: i3.FsFormNoFsValidatorsDirective, selector: "[ngModel]:not([required]):not([fsFormRequired]):not([fsFormCompare]):not([fsFormDateRange]):not([fsFormEmail]):not([fsFormEmails]):not([fsFormFunction]):not([fsFormGreater]):not([fsFormGreaterEqual]):not([fsFormInteger]):not([fsFormLesser]):not([fsFormMax]):not([fsFormMaxLength]):not([fsFormMin]):not([fsFormMinLength]):not([fsFormNumeric]):not([fsFormPattern]):not([fsFormPhone]):not([fsFormUrl]):not([validate])" }, { kind: "ngmodule", type: FsCommonModule }, { kind: "directive", type: i5.FsModelChangeDirective, selector: "[fsModelChange]", inputs: ["fsModelChangeOptions"], outputs: ["fsModelChange"] }, { kind: "ngmodule", type: FsSkeletonModule }, { kind: "directive", type: i1$1.FsSkeletonContentDirective, selector: "[fsSkeleton]", inputs: ["fsSkeleton", "fsSkeletonPattern"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
355
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "18.2.7", type: EditorComponent, isStandalone: true, selector: "app-editor", inputs: { showHtml: { classPropertyName: "showHtml", publicName: "showHtml", isSignal: true, isRequired: false, transformFunction: null }, showScss: { classPropertyName: "showScss", publicName: "showScss", isSignal: true, isRequired: false, transformFunction: null }, showJs: { classPropertyName: "showJs", publicName: "showJs", isSignal: true, isRequired: false, transformFunction: null }, showGlobalScss: { classPropertyName: "showGlobalScss", publicName: "showGlobalScss", isSignal: true, isRequired: false, transformFunction: null }, contentConfig: { classPropertyName: "contentConfig", publicName: "contentConfig", isSignal: true, isRequired: false, transformFunction: null }, html: { classPropertyName: "html", publicName: "html", isSignal: true, isRequired: false, transformFunction: null }, scss: { classPropertyName: "scss", publicName: "scss", isSignal: true, isRequired: false, transformFunction: null }, js: { classPropertyName: "js", publicName: "js", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { html: "htmlChange", scss: "scssChange", js: "jsChange" }, ngImport: i0, template: "<as-split [unit]=\"'percent'\" [gutterSize]=\"25\">\n <as-split-area [size]=\"70\" [visible]=\"showHtml()\" [order]=\"1\">\n <div class=\"editor-container\">\n <app-editor-label\n [changed]=\"changedTypes().includes(EditorType.Html)\"\n [focused]=\"focusedArea() === EditorType.Html\">\n HTML\n </app-editor-label>\n <fs-text-editor\n [(ngModel)]=\"html\"\n name=\"html\"\n [config]=\"htmlConfig\">\n </fs-text-editor>\n </div>\n </as-split-area>\n <as-split-area [size]=\"30\" [visible]=\"showScss()\" [order]=\"2\">\n <div class=\"editor-container\">\n <app-editor-label\n [changed]=\"changedTypes().includes(EditorType.Scss)\"\n [focused]=\"focusedArea() === EditorType.Scss\">\n SCSS\n </app-editor-label>\n <fs-text-editor\n [(ngModel)]=\"scss\"\n name=\"scss\"\n [config]=\"scssConfig\">\n </fs-text-editor>\n </div>\n </as-split-area>\n <as-split-area [size]=\"30\" [visible]=\"showJs()\" [order]=\"3\">\n <div class=\"editor-container\">\n <app-editor-label\n [changed]=\"changedTypes().includes(EditorType.Js)\"\n [focused]=\"focusedArea() === EditorType.Js\">\n JS\n </app-editor-label>\n <fs-text-editor\n [(ngModel)]=\"js\"\n name=\"js\"\n [config]=\"jsConfig\">\n </fs-text-editor>\n </div>\n </as-split-area>\n <as-split-area [size]=\"30\" [visible]=\"showGlobalScss()\" [order]=\"4\">\n <div class=\"editor-container\">\n <app-editor-label\n [changed]=\"changedTypes().includes(EditorType.GlobalScss)\"\n [focused]=\"focusedArea() === EditorType.GlobalScss\">\n Global SCSS\n </app-editor-label>\n <ng-container *fsSkeleton=\"contentStyle()\">\n <fs-text-editor\n [(ngModel)]=\"globalScss\"\n name=\"globalScss\"\n [config]=\"globalScssConfig\">\n </fs-text-editor>\n </ng-container>\n </div>\n </as-split-area>\n</as-split>\n", styles: [":host{display:block;flex:1;height:100%;min-height:0}as-split{height:100%}:host ::ng-deep .as-split-gutter{background-color:unset!important}.editor-container{height:100%;border-radius:5px;display:flex;flex-direction:column}.editor-container fs-text-editor{flex:1}\n"], dependencies: [{ kind: "ngmodule", type: AngularSplitModule }, { kind: "component", type: i1.SplitComponent, selector: "as-split", inputs: ["direction", "unit", "gutterSize", "gutterStep", "restrictMove", "useTransition", "disabled", "dir", "gutterDblClickDuration", "gutterClickDeltaPx", "gutterAriaLabel"], outputs: ["transitionEnd", "dragStart", "dragEnd", "gutterClick", "gutterDblClick"], exportAs: ["asSplit"] }, { kind: "directive", type: i1.SplitAreaDirective, selector: "as-split-area, [as-split-area]", inputs: ["order", "size", "minSize", "maxSize", "lockSize", "visible"], exportAs: ["asSplitArea"] }, { kind: "component", type: EditorLabelComponent, selector: "app-editor-label", inputs: ["changed", "focused"] }, { kind: "ngmodule", type: FsTextEditorModule }, { kind: "component", type: i2.FsTextEditorComponent, selector: "fs-text-editor", inputs: ["config", "scrollable"], outputs: ["ready", "blur"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: FsFormModule }, { kind: "directive", type: i3.FsFormNoFsValidatorsDirective, selector: "[ngModel]:not([required]):not([fsFormRequired]):not([fsFormCompare]):not([fsFormDateRange]):not([fsFormEmail]):not([fsFormEmails]):not([fsFormFunction]):not([fsFormGreater]):not([fsFormGreaterEqual]):not([fsFormInteger]):not([fsFormLesser]):not([fsFormMax]):not([fsFormMaxLength]):not([fsFormMin]):not([fsFormMinLength]):not([fsFormNumeric]):not([fsFormPattern]):not([fsFormPhone]):not([fsFormUrl]):not([validate])" }, { kind: "ngmodule", type: FsSkeletonModule }, { kind: "directive", type: i1$1.FsSkeletonContentDirective, selector: "[fsSkeleton]", inputs: ["fsSkeleton", "fsSkeletonPattern"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
339
356
  }
340
357
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: EditorComponent, decorators: [{
341
358
  type: Component,
@@ -345,32 +362,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImpor
345
362
  FsTextEditorModule,
346
363
  FormsModule,
347
364
  FsFormModule,
348
- FsCommonModule,
349
365
  FsSkeletonModule,
350
- ], template: "<as-split [unit]=\"'percent'\" [gutterSize]=\"25\"> \n <as-split-area [size]=\"70\" [visible]=\"showHtml\" [order]=\"1\">\n <div class=\"editor-container\">\n <app-editor-label\n [changed]=\"changes.html !== undefined\"\n [focused]=\"focusedArea === 'html'\">\n HTML\n </app-editor-label>\n <fs-text-editor \n [(ngModel)]=\"html\" \n name=\"html\"\n [fsModelChangeOptions]=\"{ debounce: 0 }\"\n (fsModelChange)=\"change(EditorType.Html, $event)\"\n [config]=\"htmlConfig\">\n </fs-text-editor> \n </div>\n </as-split-area>\n <as-split-area [size]=\"30\" [visible]=\"showScss\" [order]=\"2\">\n <div class=\"editor-container\">\n <app-editor-label\n [changed]=\"changes.scss !== undefined\"\n [focused]=\"focusedArea === 'scss'\">\n SCSS\n </app-editor-label>\n <fs-text-editor \n [(ngModel)]=\"scss\" \n name=\"scss\"\n [fsModelChangeOptions]=\"{ debounce: 0 }\"\n (fsModelChange)=\"change(EditorType.Scss, $event)\"\n [config]=\"scssConfig\">\n </fs-text-editor> \n </div>\n </as-split-area>\n <as-split-area [size]=\"30\" [visible]=\"showJs\" [order]=\"3\">\n <div class=\"editor-container\">\n <app-editor-label\n [changed]=\"changes.js !== undefined\"\n [focused]=\"focusedArea === 'js'\">\n JS\n </app-editor-label>\n <fs-text-editor \n [(ngModel)]=\"js\" \n name=\"js\"\n [fsModelChangeOptions]=\"{ debounce: 0 }\"\n (fsModelChange)=\"change(EditorType.Js, $event)\"\n [config]=\"jsConfig\">\n </fs-text-editor> \n </div>\n </as-split-area>\n <as-split-area [size]=\"30\" [visible]=\"showGlobalScss\" [order]=\"4\">\n <div class=\"editor-container\">\n <app-editor-label\n [changed]=\"changes.globalScss !== undefined\"\n [focused]=\"focusedArea === 'globalScss'\">\n Global SCSS\n </app-editor-label>\n <ng-container *fsSkeleton=\"contentStyle\">\n <fs-text-editor \n [(ngModel)]=\"contentStyle.scss\" \n name=\"globalScss\"\n [fsModelChangeOptions]=\"{ debounce: 300 }\"\n (fsModelChange)=\"change(EditorType.GlobalScss, $event)\"\n [config]=\"globalScssConfig\">\n </fs-text-editor> \n </ng-container> \n </div>\n </as-split-area>\n</as-split>\n", styles: [":host{display:block;flex:1;height:100%;min-height:0}as-split{height:100%}:host ::ng-deep .as-split-gutter{background-color:unset!important}.editor-container{height:100%;border-radius:5px;display:flex;flex-direction:column}.editor-container fs-text-editor{flex:1}\n"] }]
351
- }], propDecorators: { showHtml: [{
352
- type: Input
353
- }], showScss: [{
354
- type: Input
355
- }], showJs: [{
356
- type: Input
357
- }], showGlobalScss: [{
358
- type: Input
359
- }], html: [{
360
- type: Input
361
- }], scss: [{
362
- type: Input
363
- }], js: [{
364
- type: Input
365
- }], contentConfig: [{
366
- type: Input
367
- }], changed: [{
368
- type: Output
369
- }], focused: [{
370
- type: Output
371
- }], blured: [{
372
- type: Output
373
- }] } });
366
+ ], template: "<as-split [unit]=\"'percent'\" [gutterSize]=\"25\">\n <as-split-area [size]=\"70\" [visible]=\"showHtml()\" [order]=\"1\">\n <div class=\"editor-container\">\n <app-editor-label\n [changed]=\"changedTypes().includes(EditorType.Html)\"\n [focused]=\"focusedArea() === EditorType.Html\">\n HTML\n </app-editor-label>\n <fs-text-editor\n [(ngModel)]=\"html\"\n name=\"html\"\n [config]=\"htmlConfig\">\n </fs-text-editor>\n </div>\n </as-split-area>\n <as-split-area [size]=\"30\" [visible]=\"showScss()\" [order]=\"2\">\n <div class=\"editor-container\">\n <app-editor-label\n [changed]=\"changedTypes().includes(EditorType.Scss)\"\n [focused]=\"focusedArea() === EditorType.Scss\">\n SCSS\n </app-editor-label>\n <fs-text-editor\n [(ngModel)]=\"scss\"\n name=\"scss\"\n [config]=\"scssConfig\">\n </fs-text-editor>\n </div>\n </as-split-area>\n <as-split-area [size]=\"30\" [visible]=\"showJs()\" [order]=\"3\">\n <div class=\"editor-container\">\n <app-editor-label\n [changed]=\"changedTypes().includes(EditorType.Js)\"\n [focused]=\"focusedArea() === EditorType.Js\">\n JS\n </app-editor-label>\n <fs-text-editor\n [(ngModel)]=\"js\"\n name=\"js\"\n [config]=\"jsConfig\">\n </fs-text-editor>\n </div>\n </as-split-area>\n <as-split-area [size]=\"30\" [visible]=\"showGlobalScss()\" [order]=\"4\">\n <div class=\"editor-container\">\n <app-editor-label\n [changed]=\"changedTypes().includes(EditorType.GlobalScss)\"\n [focused]=\"focusedArea() === EditorType.GlobalScss\">\n Global SCSS\n </app-editor-label>\n <ng-container *fsSkeleton=\"contentStyle()\">\n <fs-text-editor\n [(ngModel)]=\"globalScss\"\n name=\"globalScss\"\n [config]=\"globalScssConfig\">\n </fs-text-editor>\n </ng-container>\n </div>\n </as-split-area>\n</as-split>\n", styles: [":host{display:block;flex:1;height:100%;min-height:0}as-split{height:100%}:host ::ng-deep .as-split-gutter{background-color:unset!important}.editor-container{height:100%;border-radius:5px;display:flex;flex-direction:column}.editor-container fs-text-editor{flex:1}\n"] }]
367
+ }] });
374
368
 
375
369
  class EditorTogglesComponent {
376
370
  editors;
@@ -430,41 +424,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImpor
430
424
  }] } });
431
425
 
432
426
  class ContentLayoutComponent {
427
+ contentLayout = signal(null);
433
428
  _config = inject(FS_CONTENT_CONFIG);
434
429
  _data = inject(MAT_DIALOG_DATA);
435
430
  _dialogRef = inject(MatDialogRef);
436
431
  _message = inject(FsMessage);
437
- _cdRef = inject(ChangeDetectorRef);
438
- textEditors;
439
- contentLayout = null;
440
- editors = { content: true, styles: true };
441
- _destroy$ = new Subject();
442
- ngOnInit() {
443
- this._fetchData();
444
- }
445
- ngOnDestroy() {
446
- this._destroy$.next(null);
447
- this._destroy$.complete();
432
+ constructor() {
433
+ this._init();
448
434
  }
449
435
  save = () => {
450
- return this._config.saveContentLayout(this.contentLayout)
436
+ // Only the fields this dialog edits. Sending the whole layout re-sent the Layout Editor's
437
+ // copy of content and styles, which the server wrote over edits saved since (IEB-T292).
438
+ const { id, name, tag } = this.contentLayout();
439
+ return this._config.saveContentLayout({ id, name, tag })
451
440
  .pipe(tap((contentLayout) => {
452
441
  this._message.success('Saved Changes');
453
442
  this._dialogRef.close(contentLayout);
454
443
  }));
455
444
  };
456
- _fetchData() {
457
- of(this._data.contentLayout)
458
- .pipe(switchMap((contentLayout) => {
459
- return of(contentLayout);
460
- }), takeUntil(this._destroy$))
461
- .subscribe((contentLayout) => {
462
- this.contentLayout = { ...contentLayout };
463
- this._cdRef.markForCheck();
464
- });
445
+ _init() {
446
+ this.contentLayout.set({ ...this._data.contentLayout });
465
447
  }
466
448
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: ContentLayoutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
467
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.7", type: ContentLayoutComponent, isStandalone: true, selector: "ng-component", viewQueries: [{ propertyName: "textEditors", predicate: FsTextEditorComponent, descendants: true }], ngImport: i0, template: "<form fsForm [submit]=\"save\" *fsSkeletonForm=\"contentLayout\">\n <fs-dialog>\n <h1 mat-dialog-title>{{contentLayout.id ? 'Layout' : 'Layout Page'}}</h1>\n <div mat-dialog-content>\n <div class=\"fs-column\">\n <mat-form-field>\n <mat-label>Name</mat-label>\n <input\n matInput\n [(ngModel)]=\"contentLayout.name\"\n name=\"name\"\n required>\n </mat-form-field>\n <mat-form-field>\n <mat-label>Tag</mat-label>\n <input\n matInput\n [(ngModel)]=\"contentLayout.tag\"\n name=\"tag\">\n </mat-form-field>\n </div>\n </div>\n\n <div mat-dialog-actions>\n <fs-form-dialog-actions>\n </fs-form-dialog-actions>\n </div>\n </fs-dialog>\n</form>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: FsSkeletonModule }, { kind: "directive", type: i1$1.FsSkeletonFormDirective, selector: "[fsSkeletonForm]", inputs: ["fsSkeletonForm", "fsSkeletonFormLines"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i2$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: FsFormModule }, { kind: "directive", type: i3.FsFormDirective, selector: "[fsForm]", inputs: ["wrapperSelector", "messageSelector", "hintSelector", "labelSelector", "autocomplete", "shortcuts", "confirm", "confirmDialog", "confirmDrawer", "confirmBrowser", "confirmTabs", "dirtySubmitButton", "submit", "successDelay", "errorDelay", "tabGroup", "deactivationGuard"], outputs: ["fsForm", "invalid", "valid", "submitted", "reseted", "cleared"], exportAs: ["fsForm"] }, { kind: "directive", type: i3.FsFormRequiredDirective, selector: "[fsFormRequired],[ngModel][required]", inputs: ["fsFormRequired", "required", "fsFormRequiredMessage"] }, { kind: "component", type: i3.FsFormDialogActionsComponent, selector: "fs-form-dialog-actions", inputs: ["save", "create", "close", "done", "closeData", "name"] }, { kind: "directive", type: i3.FsFormNoFsValidatorsDirective, selector: "[ngModel]:not([required]):not([fsFormRequired]):not([fsFormCompare]):not([fsFormDateRange]):not([fsFormEmail]):not([fsFormEmails]):not([fsFormFunction]):not([fsFormGreater]):not([fsFormGreaterEqual]):not([fsFormInteger]):not([fsFormLesser]):not([fsFormMax]):not([fsFormMaxLength]):not([fsFormMin]):not([fsFormMinLength]):not([fsFormNumeric]):not([fsFormPattern]):not([fsFormPhone]):not([fsFormUrl]):not([validate])" }, { kind: "ngmodule", type: FsDialogModule }, { kind: "directive", type: i4.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i4.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i4.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "component", type: i5$1.FsDialogComponent, selector: "fs-dialog", inputs: ["mobileMode", "mobileButtonPlacement", "mobileWidth", "mode", "buttonLayout", "dock", "dockable", "fullscreenPercent"] }, { kind: "component", type: MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: MatLabel, selector: "mat-label" }, { kind: "directive", type: MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
449
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.7", type: ContentLayoutComponent, isStandalone: true, selector: "ng-component", ngImport: i0, template: "<form fsForm [submit]=\"save\" *fsSkeletonForm=\"contentLayout()\">\n <fs-dialog>\n <h1 mat-dialog-title>{{contentLayout().id ? 'Layout' : 'Layout Page'}}</h1>\n <div mat-dialog-content>\n <div class=\"fs-column\">\n <mat-form-field>\n <mat-label>Name</mat-label>\n <input\n matInput\n [(ngModel)]=\"contentLayout().name\"\n name=\"name\"\n required>\n </mat-form-field>\n <mat-form-field>\n <mat-label>Tag</mat-label>\n <input\n matInput\n [(ngModel)]=\"contentLayout().tag\"\n name=\"tag\">\n </mat-form-field>\n </div>\n </div>\n\n <div mat-dialog-actions>\n <fs-form-dialog-actions>\n </fs-form-dialog-actions>\n </div>\n </fs-dialog>\n</form>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: FsSkeletonModule }, { kind: "directive", type: i1$1.FsSkeletonFormDirective, selector: "[fsSkeletonForm]", inputs: ["fsSkeletonForm", "fsSkeletonFormLines"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i2$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: FsFormModule }, { kind: "directive", type: i3.FsFormDirective, selector: "[fsForm]", inputs: ["wrapperSelector", "messageSelector", "hintSelector", "labelSelector", "autocomplete", "shortcuts", "confirm", "confirmDialog", "confirmDrawer", "confirmBrowser", "confirmTabs", "dirtySubmitButton", "submit", "successDelay", "errorDelay", "tabGroup", "deactivationGuard"], outputs: ["fsForm", "invalid", "valid", "submitted", "reseted", "cleared"], exportAs: ["fsForm"] }, { kind: "directive", type: i3.FsFormRequiredDirective, selector: "[fsFormRequired],[ngModel][required]", inputs: ["fsFormRequired", "required", "fsFormRequiredMessage"] }, { kind: "component", type: i3.FsFormDialogActionsComponent, selector: "fs-form-dialog-actions", inputs: ["save", "create", "close", "done", "closeData", "name"] }, { kind: "directive", type: i3.FsFormNoFsValidatorsDirective, selector: "[ngModel]:not([required]):not([fsFormRequired]):not([fsFormCompare]):not([fsFormDateRange]):not([fsFormEmail]):not([fsFormEmails]):not([fsFormFunction]):not([fsFormGreater]):not([fsFormGreaterEqual]):not([fsFormInteger]):not([fsFormLesser]):not([fsFormMax]):not([fsFormMaxLength]):not([fsFormMin]):not([fsFormMinLength]):not([fsFormNumeric]):not([fsFormPattern]):not([fsFormPhone]):not([fsFormUrl]):not([validate])" }, { kind: "ngmodule", type: FsDialogModule }, { kind: "directive", type: i4.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i4.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i4.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "component", type: i5.FsDialogComponent, selector: "fs-dialog", inputs: ["mobileMode", "mobileButtonPlacement", "mobileWidth", "mode", "buttonLayout", "dock", "dockable", "fullscreenPercent"] }, { kind: "component", type: MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: MatLabel, selector: "mat-label" }, { kind: "directive", type: MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
468
450
  }
469
451
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: ContentLayoutComponent, decorators: [{
470
452
  type: Component,
@@ -480,102 +462,47 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImpor
480
462
  MatLabel,
481
463
  MatInput,
482
464
  MatDialogActions,
483
- ], template: "<form fsForm [submit]=\"save\" *fsSkeletonForm=\"contentLayout\">\n <fs-dialog>\n <h1 mat-dialog-title>{{contentLayout.id ? 'Layout' : 'Layout Page'}}</h1>\n <div mat-dialog-content>\n <div class=\"fs-column\">\n <mat-form-field>\n <mat-label>Name</mat-label>\n <input\n matInput\n [(ngModel)]=\"contentLayout.name\"\n name=\"name\"\n required>\n </mat-form-field>\n <mat-form-field>\n <mat-label>Tag</mat-label>\n <input\n matInput\n [(ngModel)]=\"contentLayout.tag\"\n name=\"tag\">\n </mat-form-field>\n </div>\n </div>\n\n <div mat-dialog-actions>\n <fs-form-dialog-actions>\n </fs-form-dialog-actions>\n </div>\n </fs-dialog>\n</form>\n" }]
484
- }], propDecorators: { textEditors: [{
485
- type: ViewChildren,
486
- args: [FsTextEditorComponent]
487
- }] } });
465
+ ], template: "<form fsForm [submit]=\"save\" *fsSkeletonForm=\"contentLayout()\">\n <fs-dialog>\n <h1 mat-dialog-title>{{contentLayout().id ? 'Layout' : 'Layout Page'}}</h1>\n <div mat-dialog-content>\n <div class=\"fs-column\">\n <mat-form-field>\n <mat-label>Name</mat-label>\n <input\n matInput\n [(ngModel)]=\"contentLayout().name\"\n name=\"name\"\n required>\n </mat-form-field>\n <mat-form-field>\n <mat-label>Tag</mat-label>\n <input\n matInput\n [(ngModel)]=\"contentLayout().tag\"\n name=\"tag\">\n </mat-form-field>\n </div>\n </div>\n\n <div mat-dialog-actions>\n <fs-form-dialog-actions>\n </fs-form-dialog-actions>\n </div>\n </fs-dialog>\n</form>\n" }]
466
+ }], ctorParameters: () => [] });
488
467
 
489
468
  class ContentLayoutEditorComponent {
490
- editor;
491
- contentLayout;
469
+ editor = viewChild(EditorComponent);
470
+ contentLayout = signal(null);
492
471
  get isMac() {
493
472
  return navigator.platform.toUpperCase().indexOf('MAC') >= 0;
494
473
  }
495
- submitting;
496
474
  config;
497
475
  EditorType = EditorType;
498
- focused = null;
499
- title;
500
- editors = {
476
+ editors = signal({
501
477
  [EditorType.Html]: true,
502
478
  [EditorType.Scss]: true,
503
479
  [EditorType.GlobalScss]: false,
504
- };
505
- _destroy$ = new Subject();
480
+ });
481
+ submitted = () => this.editor().save$({
482
+ [EditorType.Html]: 'content',
483
+ [EditorType.Scss]: 'styles',
484
+ }, (values) => this.config.saveContentLayout({ id: this.contentLayout().id, ...values }));
506
485
  _data = inject(MAT_DIALOG_DATA);
507
486
  _dialogRef = inject(MatDialogRef);
508
- _message = inject(FsMessage);
509
487
  _dialog = inject(MatDialog);
510
- _cdRef = inject(ChangeDetectorRef);
511
488
  _prompt = inject(FsPrompt);
489
+ _destroyRef = inject(DestroyRef);
512
490
  ngOnInit() {
513
491
  this._dialogRef.addPanelClass('fs-content-editor-overlay-pane');
514
492
  this._dialogRef.disableClose = true;
515
493
  this.config = this._data.contentConfig;
516
- this._initContentLayout(this._data.contentLayout);
494
+ this._initContentLayout();
517
495
  this._initEscape();
518
496
  }
519
497
  editorToggleChange(event) {
520
- this.editors[event.value] = !this.editors[event.value];
521
- }
522
- ngOnDestroy() {
523
- this._destroy$.next(null);
524
- this._destroy$.complete();
525
- }
526
- _initContentLayout(contentLayout) {
527
- this.config.loadContentLayout(contentLayout.id)
528
- .subscribe((data) => {
529
- this.contentLayout = data;
530
- this._cdRef.markForCheck();
531
- });
532
- }
533
- editorFocused(type) {
534
- this.focused = type;
498
+ this.editors.update((editors) => ({ ...editors, [event.value]: !editors[event.value] }));
535
499
  }
536
500
  save() {
537
501
  this.submitted()
538
502
  .subscribe();
539
503
  }
540
- submitted = () => {
541
- this.submitting = true;
542
- return of(null)
543
- .pipe(filter(() => this.focused), switchMap(() => {
544
- switch (this.focused) {
545
- case EditorType.Html:
546
- case EditorType.Scss:
547
- return this.saveContentPage();
548
- case EditorType.GlobalScss:
549
- return this.editor.saveGlobalScss();
550
- }
551
- return throwError('Invalid focus');
552
- }), tap(() => {
553
- this.editor.clearChange(this.focused);
554
- this._cdRef.markForCheck();
555
- }), finalize(() => {
556
- this.submitting = false;
557
- this._cdRef.markForCheck();
558
- }));
559
- };
560
- saveContentPage() {
561
- const names = {
562
- [EditorType.Scss]: 'styles',
563
- [EditorType.Html]: 'content',
564
- };
565
- const data = {
566
- id: this.contentLayout.id,
567
- [names[this.focused]]: this.editor.changes[this.focused],
568
- };
569
- return this.config.saveContentLayout({
570
- id: this.contentLayout.id,
571
- ...data,
572
- })
573
- .pipe(tap(() => {
574
- this._message.success('Saved Changes');
575
- }));
576
- }
577
504
  close() {
578
- if (!this.editor.hasChanges) {
505
+ if (!this.editor()?.hasChanges()) {
579
506
  return this._dialogRef.close();
580
507
  }
581
508
  this._prompt.confirm({
@@ -595,40 +522,45 @@ class ContentLayoutEditorComponent {
595
522
  },
596
523
  ],
597
524
  })
598
- .pipe(takeUntil(this._destroy$))
599
- .subscribe((value) => {
600
- if (value === 'discard') {
601
- this._dialogRef.close();
602
- }
603
- });
525
+ .pipe(filter((value) => value === 'discard'), tap(() => this._dialogRef.close()), takeUntilDestroyed(this._destroyRef))
526
+ .subscribe();
604
527
  }
605
528
  openSettings() {
606
529
  this._dialog.open(ContentLayoutComponent, {
607
530
  data: {
608
- contentLayout: this.contentLayout,
531
+ contentLayout: this.contentLayout(),
609
532
  },
610
533
  })
611
534
  .afterClosed()
612
- .pipe(takeUntil(this._destroy$))
613
- .subscribe((contentLayout) => {
614
- this.contentLayout = {
615
- ...this.contentLayout,
616
- ...contentLayout,
617
- };
618
- this._cdRef.markForCheck();
619
- });
535
+ .pipe(filter((contentLayout) => !!contentLayout),
536
+ // Only what the settings dialog edits. Merging content/styles from its response would
537
+ // push them through [html]/[scss] into the editors and wipe unsaved typing (IEB-T292).
538
+ tap((contentLayout) => {
539
+ this.contentLayout.update((current) => ({
540
+ ...current,
541
+ name: contentLayout.name,
542
+ tag: contentLayout.tag,
543
+ }));
544
+ }), takeUntilDestroyed(this._destroyRef))
545
+ .subscribe();
546
+ }
547
+ _initContentLayout() {
548
+ this.config.loadContentLayout(this._data.contentLayout.id)
549
+ .pipe(tap((contentLayout) => this.contentLayout.set(contentLayout)), takeUntilDestroyed(this._destroyRef))
550
+ .subscribe();
620
551
  }
621
552
  _initEscape() {
622
553
  fromEvent(document, 'keydown')
623
- .pipe(filter((event) => event.code === 'Escape'), takeUntil(this._destroy$)).subscribe(() => {
554
+ .pipe(filter((event) => event.code === 'Escape'), tap(() => {
624
555
  const dialogRef = this._dialog.openDialogs.reverse()[0];
625
556
  if (dialogRef?.componentInstance === this) {
626
557
  this.close();
627
558
  }
628
- });
559
+ }), takeUntilDestroyed(this._destroyRef))
560
+ .subscribe();
629
561
  }
630
562
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: ContentLayoutEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
631
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.7", type: ContentLayoutEditorComponent, isStandalone: true, selector: "ng-component", viewQueries: [{ propertyName: "editor", first: true, predicate: EditorComponent, descendants: true }], ngImport: i0, template: "<form\n fsForm\n [submit]=\"submitted\"\n [dirtySubmitButton]=\"false\"\n [confirm]=\"false\">\n <fs-dialog *fsSkeletonForm=\"contentLayout\">\n <h1 mat-dialog-title>\n <div class=\"title-container\">\n <div class=\"title\">\n Layout Editor\n <div class=\"small\">\n {{ contentLayout.name }}\n </div>\n </div>\n <div class=\"toggles\">\n <app-editor-toggles\n [editors]=\"editors\"\n (toggleChange)=\"editorToggleChange($event)\">\n </app-editor-toggles>\n </div>\n <app-editor-actions\n [saveDisabled]=\"!editor?.hasChanges || submitting\"\n [isMac]=\"isMac\"\n (settings)=\"openSettings()\"\n (save)=\"save()\"\n (done)=\"close()\">\n </app-editor-actions>\n </div>\n </h1>\n <div mat-dialog-content>\n <app-editor\n [contentConfig]=\"config\"\n [showHtml]=\"editors.html\"\n [showScss]=\"editors.scss\"\n [showGlobalScss]=\"editors.globalScss\"\n [html]=\"contentLayout.content\"\n [scss]=\"contentLayout.styles\"\n (focused)=\"editorFocused($event)\">\n </app-editor>\n </div>\n <!-- <div mat-dialog-actions>\n <button \n mat-button\n color=\"primary\"\n (click)=\"close()\"\n type=\"button\"> \n Done \n </button>\n <div class=\"toggles\">\n <mat-button-toggle-group multiple>\n <mat-button-toggle value=\"html\" [checked]=\"editors.html\" (change)=\"editorToggleChange($event)\">HTML</mat-button-toggle>\n <mat-button-toggle value=\"scss\" [checked]=\"editors.scss\" (change)=\"editorToggleChange($event)\">SCSS</mat-button-toggle>\n <mat-button-toggle value=\"globalScss\" [checked]=\"editors.globalScss\" (change)=\"editorToggleChange($event)\">Global SCSS</mat-button-toggle>\n </mat-button-toggle-group> \n </div>\n </div> -->\n </fs-dialog>\n</form>", styles: ["::ng-deep .fs-content-editor-overlay-pane .mat-dialog-container,::ng-deep .fs-content-editor-overlay-pane .mat-mdc-dialog-container{border-radius:0;height:100%}::ng-deep .fs-content-editor-overlay-pane .mdc-dialog__container,::ng-deep .fs-content-editor-overlay-pane .mat-mdc-dialog-surface{height:100%}form{height:100%}fs-dialog{display:flex;height:100%;flex-direction:column}fs-dialog ::ng-deep .mat-dialog-content,fs-dialog ::ng-deep .mat-mdc-dialog-content{max-height:none;flex:1;min-height:0;overflow:hidden;display:flex;flex-direction:column}fs-dialog ::ng-deep .mat-button-toggle-label-content{line-height:36px}h1 .toggles{display:flex;justify-content:flex-end;justify-self:baseline;font-size:13px}h1 .actions{margin-left:30px}h1 .actions .mdc-button{margin-left:5px}.title-container{display:flex;align-items:center}.title-container .title{flex:1}.title-container .title .small{line-height:normal}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: FsFormModule }, { kind: "directive", type: i3.FsFormDirective, selector: "[fsForm]", inputs: ["wrapperSelector", "messageSelector", "hintSelector", "labelSelector", "autocomplete", "shortcuts", "confirm", "confirmDialog", "confirmDrawer", "confirmBrowser", "confirmTabs", "dirtySubmitButton", "submit", "successDelay", "errorDelay", "tabGroup", "deactivationGuard"], outputs: ["fsForm", "invalid", "valid", "submitted", "reseted", "cleared"], exportAs: ["fsForm"] }, { kind: "ngmodule", type: FsSkeletonModule }, { kind: "directive", type: i1$1.FsSkeletonFormDirective, selector: "[fsSkeletonForm]", inputs: ["fsSkeletonForm", "fsSkeletonFormLines"] }, { kind: "ngmodule", type: FsDialogModule }, { kind: "directive", type: i4.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i4.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "component", type: i5$1.FsDialogComponent, selector: "fs-dialog", inputs: ["mobileMode", "mobileButtonPlacement", "mobileWidth", "mode", "buttonLayout", "dock", "dockable", "fullscreenPercent"] }, { kind: "component", type: EditorTogglesComponent, selector: "app-editor-toggles", inputs: ["editors", "showJs"], outputs: ["toggleChange"] }, { kind: "component", type: EditorActionsComponent, selector: "app-editor-actions", inputs: ["saveDisabled", "isMac", "previewPath"], outputs: ["settings", "save", "done", "preview"] }, { kind: "component", type: EditorComponent, selector: "app-editor", inputs: ["showHtml", "showScss", "showJs", "showGlobalScss", "html", "scss", "js", "contentConfig"], outputs: ["changed", "focused", "blured"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
563
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "18.2.7", type: ContentLayoutEditorComponent, isStandalone: true, selector: "ng-component", viewQueries: [{ propertyName: "editor", first: true, predicate: EditorComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<form\n fsForm\n [submit]=\"submitted\"\n [dirtySubmitButton]=\"false\"\n [confirm]=\"false\">\n <fs-dialog *fsSkeletonForm=\"contentLayout()\">\n <h1 mat-dialog-title>\n <div class=\"title-container\">\n <div class=\"title\">\n Layout Editor\n <div class=\"small\">\n {{ contentLayout().name }}\n </div>\n </div>\n <div class=\"toggles\">\n <app-editor-toggles\n [editors]=\"editors()\"\n (toggleChange)=\"editorToggleChange($event)\">\n </app-editor-toggles>\n </div>\n <app-editor-actions\n [saveDisabled]=\"!editor()?.hasChanges() || editor()?.saving()\"\n [isMac]=\"isMac\"\n (settings)=\"openSettings()\"\n (save)=\"save()\"\n (done)=\"close()\">\n </app-editor-actions>\n </div>\n </h1>\n <div mat-dialog-content>\n <app-editor\n [contentConfig]=\"config\"\n [showHtml]=\"editors().html\"\n [showScss]=\"editors().scss\"\n [showGlobalScss]=\"editors().globalScss\"\n [html]=\"contentLayout().content\"\n [scss]=\"contentLayout().styles\">\n </app-editor>\n </div>\n </fs-dialog>\n</form>\n", styles: ["::ng-deep .fs-content-editor-overlay-pane .mat-dialog-container,::ng-deep .fs-content-editor-overlay-pane .mat-mdc-dialog-container{border-radius:0;height:100%}::ng-deep .fs-content-editor-overlay-pane .mdc-dialog__container,::ng-deep .fs-content-editor-overlay-pane .mat-mdc-dialog-surface{height:100%}form{height:100%}fs-dialog{display:flex;height:100%;flex-direction:column}fs-dialog ::ng-deep .mat-dialog-content,fs-dialog ::ng-deep .mat-mdc-dialog-content{max-height:none;flex:1;min-height:0;overflow:hidden;display:flex;flex-direction:column}fs-dialog ::ng-deep .mat-button-toggle-label-content{line-height:36px}h1 .toggles{display:flex;justify-content:flex-end;justify-self:baseline;font-size:13px}h1 .actions{margin-left:30px}h1 .actions .mdc-button{margin-left:5px}.title-container{display:flex;align-items:center}.title-container .title{flex:1}.title-container .title .small{line-height:normal}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: FsFormModule }, { kind: "directive", type: i3.FsFormDirective, selector: "[fsForm]", inputs: ["wrapperSelector", "messageSelector", "hintSelector", "labelSelector", "autocomplete", "shortcuts", "confirm", "confirmDialog", "confirmDrawer", "confirmBrowser", "confirmTabs", "dirtySubmitButton", "submit", "successDelay", "errorDelay", "tabGroup", "deactivationGuard"], outputs: ["fsForm", "invalid", "valid", "submitted", "reseted", "cleared"], exportAs: ["fsForm"] }, { kind: "ngmodule", type: FsSkeletonModule }, { kind: "directive", type: i1$1.FsSkeletonFormDirective, selector: "[fsSkeletonForm]", inputs: ["fsSkeletonForm", "fsSkeletonFormLines"] }, { kind: "ngmodule", type: FsDialogModule }, { kind: "directive", type: i4.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i4.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "component", type: i5.FsDialogComponent, selector: "fs-dialog", inputs: ["mobileMode", "mobileButtonPlacement", "mobileWidth", "mode", "buttonLayout", "dock", "dockable", "fullscreenPercent"] }, { kind: "component", type: EditorTogglesComponent, selector: "app-editor-toggles", inputs: ["editors", "showJs"], outputs: ["toggleChange"] }, { kind: "component", type: EditorActionsComponent, selector: "app-editor-actions", inputs: ["saveDisabled", "isMac", "previewPath"], outputs: ["settings", "save", "done", "preview"] }, { kind: "component", type: EditorComponent, selector: "app-editor", inputs: ["showHtml", "showScss", "showJs", "showGlobalScss", "contentConfig", "html", "scss", "js"], outputs: ["htmlChange", "scssChange", "jsChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
632
564
  }
633
565
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: ContentLayoutEditorComponent, decorators: [{
634
566
  type: Component,
@@ -643,11 +575,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImpor
643
575
  CdkScrollable,
644
576
  MatDialogContent,
645
577
  EditorComponent,
646
- ], template: "<form\n fsForm\n [submit]=\"submitted\"\n [dirtySubmitButton]=\"false\"\n [confirm]=\"false\">\n <fs-dialog *fsSkeletonForm=\"contentLayout\">\n <h1 mat-dialog-title>\n <div class=\"title-container\">\n <div class=\"title\">\n Layout Editor\n <div class=\"small\">\n {{ contentLayout.name }}\n </div>\n </div>\n <div class=\"toggles\">\n <app-editor-toggles\n [editors]=\"editors\"\n (toggleChange)=\"editorToggleChange($event)\">\n </app-editor-toggles>\n </div>\n <app-editor-actions\n [saveDisabled]=\"!editor?.hasChanges || submitting\"\n [isMac]=\"isMac\"\n (settings)=\"openSettings()\"\n (save)=\"save()\"\n (done)=\"close()\">\n </app-editor-actions>\n </div>\n </h1>\n <div mat-dialog-content>\n <app-editor\n [contentConfig]=\"config\"\n [showHtml]=\"editors.html\"\n [showScss]=\"editors.scss\"\n [showGlobalScss]=\"editors.globalScss\"\n [html]=\"contentLayout.content\"\n [scss]=\"contentLayout.styles\"\n (focused)=\"editorFocused($event)\">\n </app-editor>\n </div>\n <!-- <div mat-dialog-actions>\n <button \n mat-button\n color=\"primary\"\n (click)=\"close()\"\n type=\"button\"> \n Done \n </button>\n <div class=\"toggles\">\n <mat-button-toggle-group multiple>\n <mat-button-toggle value=\"html\" [checked]=\"editors.html\" (change)=\"editorToggleChange($event)\">HTML</mat-button-toggle>\n <mat-button-toggle value=\"scss\" [checked]=\"editors.scss\" (change)=\"editorToggleChange($event)\">SCSS</mat-button-toggle>\n <mat-button-toggle value=\"globalScss\" [checked]=\"editors.globalScss\" (change)=\"editorToggleChange($event)\">Global SCSS</mat-button-toggle>\n </mat-button-toggle-group> \n </div>\n </div> -->\n </fs-dialog>\n</form>", styles: ["::ng-deep .fs-content-editor-overlay-pane .mat-dialog-container,::ng-deep .fs-content-editor-overlay-pane .mat-mdc-dialog-container{border-radius:0;height:100%}::ng-deep .fs-content-editor-overlay-pane .mdc-dialog__container,::ng-deep .fs-content-editor-overlay-pane .mat-mdc-dialog-surface{height:100%}form{height:100%}fs-dialog{display:flex;height:100%;flex-direction:column}fs-dialog ::ng-deep .mat-dialog-content,fs-dialog ::ng-deep .mat-mdc-dialog-content{max-height:none;flex:1;min-height:0;overflow:hidden;display:flex;flex-direction:column}fs-dialog ::ng-deep .mat-button-toggle-label-content{line-height:36px}h1 .toggles{display:flex;justify-content:flex-end;justify-self:baseline;font-size:13px}h1 .actions{margin-left:30px}h1 .actions .mdc-button{margin-left:5px}.title-container{display:flex;align-items:center}.title-container .title{flex:1}.title-container .title .small{line-height:normal}\n"] }]
647
- }], propDecorators: { editor: [{
648
- type: ViewChild,
649
- args: [EditorComponent]
650
- }] } });
578
+ ], template: "<form\n fsForm\n [submit]=\"submitted\"\n [dirtySubmitButton]=\"false\"\n [confirm]=\"false\">\n <fs-dialog *fsSkeletonForm=\"contentLayout()\">\n <h1 mat-dialog-title>\n <div class=\"title-container\">\n <div class=\"title\">\n Layout Editor\n <div class=\"small\">\n {{ contentLayout().name }}\n </div>\n </div>\n <div class=\"toggles\">\n <app-editor-toggles\n [editors]=\"editors()\"\n (toggleChange)=\"editorToggleChange($event)\">\n </app-editor-toggles>\n </div>\n <app-editor-actions\n [saveDisabled]=\"!editor()?.hasChanges() || editor()?.saving()\"\n [isMac]=\"isMac\"\n (settings)=\"openSettings()\"\n (save)=\"save()\"\n (done)=\"close()\">\n </app-editor-actions>\n </div>\n </h1>\n <div mat-dialog-content>\n <app-editor\n [contentConfig]=\"config\"\n [showHtml]=\"editors().html\"\n [showScss]=\"editors().scss\"\n [showGlobalScss]=\"editors().globalScss\"\n [html]=\"contentLayout().content\"\n [scss]=\"contentLayout().styles\">\n </app-editor>\n </div>\n </fs-dialog>\n</form>\n", styles: ["::ng-deep .fs-content-editor-overlay-pane .mat-dialog-container,::ng-deep .fs-content-editor-overlay-pane .mat-mdc-dialog-container{border-radius:0;height:100%}::ng-deep .fs-content-editor-overlay-pane .mdc-dialog__container,::ng-deep .fs-content-editor-overlay-pane .mat-mdc-dialog-surface{height:100%}form{height:100%}fs-dialog{display:flex;height:100%;flex-direction:column}fs-dialog ::ng-deep .mat-dialog-content,fs-dialog ::ng-deep .mat-mdc-dialog-content{max-height:none;flex:1;min-height:0;overflow:hidden;display:flex;flex-direction:column}fs-dialog ::ng-deep .mat-button-toggle-label-content{line-height:36px}h1 .toggles{display:flex;justify-content:flex-end;justify-self:baseline;font-size:13px}h1 .actions{margin-left:30px}h1 .actions .mdc-button{margin-left:5px}.title-container{display:flex;align-items:center}.title-container .title{flex:1}.title-container .title .small{line-height:normal}\n"] }]
579
+ }] });
651
580
 
652
581
  class FsContentLayoutsComponent {
653
582
  _config = inject(FS_CONTENT_CONFIG);
@@ -780,58 +709,63 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImpor
780
709
  }]
781
710
  }] });
782
711
 
712
+ // Every type the server has, so the pages list labels every row. The Page dialog offers
713
+ // only the standard and not-found types (CC-T4102).
783
714
  const PageTypes = [
784
715
  { name: 'Page', value: PageType.StandardPage },
785
716
  { name: 'Page (Not Found)', value: PageType.NotFoundPage },
717
+ { name: 'Page', value: PageType.HomePage },
718
+ { name: 'Blog Post', value: PageType.BlogPost },
786
719
  ];
787
720
 
788
721
  class ContentPageComponent {
722
+ contentPage = signal(null);
723
+ contentLayouts = signal(null);
724
+ PageTypes = PageTypes
725
+ .filter((pageType) => pageType.value === PageType.StandardPage || pageType.value === PageType.NotFoundPage);
726
+ // A home page or blog post has no option to show, which left Type blank and required, so
727
+ // the only way to save was to pick another type. It keeps its type: Type is hidden and save
728
+ // sends it back unchanged (CC-T4102).
729
+ isTypeSelectable = computed(() => this.PageTypes
730
+ .some((pageType) => pageType.value === this.contentPage().type));
731
+ // The home page is the site root, path "". Path is hidden rather than edited, because the
732
+ // site only finds the home page while its path stays "" (CC-T4102).
733
+ isHomePage = computed(() => this.contentPage().type === PageType.HomePage);
789
734
  _config = inject(FS_CONTENT_CONFIG);
790
735
  _data = inject(MAT_DIALOG_DATA);
791
736
  _dialogRef = inject(MatDialogRef);
792
737
  _message = inject(FsMessage);
793
- _cdRef = inject(ChangeDetectorRef);
794
- textEditors;
795
- contentPage = null;
796
- PageTypes = PageTypes;
797
- contentLayouts;
798
- editors = { content: true, styles: true };
799
- _destroy$ = new Subject();
738
+ _destroyRef = inject(DestroyRef);
739
+ constructor() {
740
+ this._init();
741
+ }
800
742
  ngOnInit() {
801
743
  this._dialogRef.updateSize('600px');
802
- this._fetchData();
803
- }
804
- ngOnDestroy() {
805
- this._destroy$.next(null);
806
- this._destroy$.complete();
807
744
  }
808
745
  save = () => {
809
- return this._config.saveContentPage(this.contentPage)
746
+ // Only the fields this dialog edits. Sending the whole page re-sent the Page Editor's copy
747
+ // of content, styles and js, which the server wrote over edits saved since (IEB-T292).
748
+ const { id, type, contentLayoutId, name, path, title } = this.contentPage();
749
+ return this._config.saveContentPage({ id, type, contentLayoutId, name, path, title })
810
750
  .pipe(tap((contentPage) => {
811
751
  this._message.success('Saved Changes');
812
752
  this._dialogRef.close(contentPage);
813
753
  }));
814
754
  };
815
- _fetchData() {
755
+ _init() {
756
+ // The path as stored. Defaulting it to '/' turned the home page's "" into "/", which the
757
+ // site no longer matched to its root (CC-T4102).
758
+ this.contentPage.set({ ...this._data.contentPage });
816
759
  this._config.loadContentLayouts()
817
- .subscribe((contentLayouts) => {
818
- this.contentLayouts = contentLayouts;
819
- this._cdRef.markForCheck();
820
- });
821
- of(this._data.contentPage)
822
- .pipe(switchMap((contentPage) => {
823
- return of(contentPage);
824
- }), takeUntil(this._destroy$))
825
- .subscribe((contentPage) => {
826
- this.contentPage = {
827
- ...contentPage,
828
- path: contentPage.path || '/',
829
- };
830
- this._cdRef.markForCheck();
831
- });
760
+ .pipe(
761
+ // The config answers with the { contentLayouts, paging } envelope
762
+ // (FsContentConfig.loadContentLayouts), so the select takes its array, not the
763
+ // envelope, which rendered no options at all (IEB-T294).
764
+ tap((response) => this.contentLayouts.set(response.contentLayouts)), takeUntilDestroyed(this._destroyRef))
765
+ .subscribe();
832
766
  }
833
767
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: ContentPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
834
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.7", type: ContentPageComponent, isStandalone: true, selector: "ng-component", viewQueries: [{ propertyName: "textEditors", predicate: FsTextEditorComponent, descendants: true }], ngImport: i0, template: "<form fsForm [submit]=\"save\" *fsSkeletonForm=\"contentPage\">\n <fs-dialog>\n <h1 mat-dialog-title>{{contentPage.id ? 'Page' : 'Create Page'}}</h1>\n <div mat-dialog-content>\n <div class=\"fs-column\">\n <mat-form-field>\n <mat-label>Type</mat-label>\n <mat-select\n [(ngModel)]=\"contentPage.type\"\n name=\"type\"\n required>\n @for (item of PageTypes; track item) {\n <mat-option\n [value]=\"item.value\">\n {{ item.name }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n @if (contentLayouts) {\n <mat-form-field>\n <mat-label>Layout</mat-label>\n <mat-select\n [(ngModel)]=\"contentPage.contentLayoutId\"\n required\n name=\"contentLayoutId\">\n @for (item of contentLayouts; track item) {\n <mat-option\n [value]=\"item.id\">\n {{ item.name }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n <mat-form-field>\n <mat-label>Name</mat-label>\n <input\n matInput\n [(ngModel)]=\"contentPage.name\"\n name=\"name\"\n required>\n </mat-form-field>\n <mat-form-field>\n <mat-label>Path</mat-label>\n <input\n matInput\n [(ngModel)]=\"contentPage.path\"\n name=\"path\"\n required>\n </mat-form-field>\n <mat-form-field>\n <mat-label>Title</mat-label>\n <input\n matInput\n [(ngModel)]=\"contentPage.title\"\n name=\"title\">\n </mat-form-field>\n </div>\n </div>\n\n <div mat-dialog-actions>\n <fs-form-dialog-actions>\n </fs-form-dialog-actions>\n </div>\n </fs-dialog>\n</form>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: FsSkeletonModule }, { kind: "directive", type: i1$1.FsSkeletonFormDirective, selector: "[fsSkeletonForm]", inputs: ["fsSkeletonForm", "fsSkeletonFormLines"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i2$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: FsFormModule }, { kind: "directive", type: i3.FsFormDirective, selector: "[fsForm]", inputs: ["wrapperSelector", "messageSelector", "hintSelector", "labelSelector", "autocomplete", "shortcuts", "confirm", "confirmDialog", "confirmDrawer", "confirmBrowser", "confirmTabs", "dirtySubmitButton", "submit", "successDelay", "errorDelay", "tabGroup", "deactivationGuard"], outputs: ["fsForm", "invalid", "valid", "submitted", "reseted", "cleared"], exportAs: ["fsForm"] }, { kind: "directive", type: i3.FsFormRequiredDirective, selector: "[fsFormRequired],[ngModel][required]", inputs: ["fsFormRequired", "required", "fsFormRequiredMessage"] }, { kind: "component", type: i3.FsFormDialogActionsComponent, selector: "fs-form-dialog-actions", inputs: ["save", "create", "close", "done", "closeData", "name"] }, { kind: "directive", type: i3.FsFormNoFsValidatorsDirective, selector: "[ngModel]:not([required]):not([fsFormRequired]):not([fsFormCompare]):not([fsFormDateRange]):not([fsFormEmail]):not([fsFormEmails]):not([fsFormFunction]):not([fsFormGreater]):not([fsFormGreaterEqual]):not([fsFormInteger]):not([fsFormLesser]):not([fsFormMax]):not([fsFormMaxLength]):not([fsFormMin]):not([fsFormMinLength]):not([fsFormNumeric]):not([fsFormPattern]):not([fsFormPhone]):not([fsFormUrl]):not([validate])" }, { kind: "ngmodule", type: FsDialogModule }, { kind: "directive", type: i4.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i4.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i4.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "component", type: i5$1.FsDialogComponent, selector: "fs-dialog", inputs: ["mobileMode", "mobileButtonPlacement", "mobileWidth", "mode", "buttonLayout", "dock", "dockable", "fullscreenPercent"] }, { kind: "component", type: MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: MatLabel, selector: "mat-label" }, { kind: "component", type: MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "directive", type: MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
768
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.7", type: ContentPageComponent, isStandalone: true, selector: "ng-component", ngImport: i0, template: "<form fsForm [submit]=\"save\" *fsSkeletonForm=\"contentPage()\">\n <fs-dialog>\n <h1 mat-dialog-title>{{contentPage().id ? 'Page' : 'Create Page'}}</h1>\n <div mat-dialog-content>\n <div class=\"fs-column\">\n @if (isTypeSelectable()) {\n <mat-form-field>\n <mat-label>Type</mat-label>\n <mat-select\n [(ngModel)]=\"contentPage().type\"\n name=\"type\"\n required>\n @for (item of PageTypes; track item) {\n <mat-option\n [value]=\"item.value\">\n {{ item.name }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n @if (contentLayouts()) {\n <mat-form-field>\n <mat-label>Layout</mat-label>\n <mat-select\n [(ngModel)]=\"contentPage().contentLayoutId\"\n required\n name=\"contentLayoutId\">\n @for (item of contentLayouts(); track item.id) {\n <mat-option\n [value]=\"item.id\">\n {{ item.name }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n <mat-form-field>\n <mat-label>Name</mat-label>\n <input\n matInput\n [(ngModel)]=\"contentPage().name\"\n name=\"name\"\n required>\n </mat-form-field>\n @if (!isHomePage()) {\n <mat-form-field>\n <mat-label>Path</mat-label>\n <input\n matInput\n [(ngModel)]=\"contentPage().path\"\n name=\"path\"\n required>\n </mat-form-field>\n }\n <mat-form-field>\n <mat-label>Title</mat-label>\n <input\n matInput\n [(ngModel)]=\"contentPage().title\"\n name=\"title\">\n </mat-form-field>\n </div>\n </div>\n\n <div mat-dialog-actions>\n <fs-form-dialog-actions>\n </fs-form-dialog-actions>\n </div>\n </fs-dialog>\n</form>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: FsSkeletonModule }, { kind: "directive", type: i1$1.FsSkeletonFormDirective, selector: "[fsSkeletonForm]", inputs: ["fsSkeletonForm", "fsSkeletonFormLines"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i2$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: FsFormModule }, { kind: "directive", type: i3.FsFormDirective, selector: "[fsForm]", inputs: ["wrapperSelector", "messageSelector", "hintSelector", "labelSelector", "autocomplete", "shortcuts", "confirm", "confirmDialog", "confirmDrawer", "confirmBrowser", "confirmTabs", "dirtySubmitButton", "submit", "successDelay", "errorDelay", "tabGroup", "deactivationGuard"], outputs: ["fsForm", "invalid", "valid", "submitted", "reseted", "cleared"], exportAs: ["fsForm"] }, { kind: "directive", type: i3.FsFormRequiredDirective, selector: "[fsFormRequired],[ngModel][required]", inputs: ["fsFormRequired", "required", "fsFormRequiredMessage"] }, { kind: "component", type: i3.FsFormDialogActionsComponent, selector: "fs-form-dialog-actions", inputs: ["save", "create", "close", "done", "closeData", "name"] }, { kind: "directive", type: i3.FsFormNoFsValidatorsDirective, selector: "[ngModel]:not([required]):not([fsFormRequired]):not([fsFormCompare]):not([fsFormDateRange]):not([fsFormEmail]):not([fsFormEmails]):not([fsFormFunction]):not([fsFormGreater]):not([fsFormGreaterEqual]):not([fsFormInteger]):not([fsFormLesser]):not([fsFormMax]):not([fsFormMaxLength]):not([fsFormMin]):not([fsFormMinLength]):not([fsFormNumeric]):not([fsFormPattern]):not([fsFormPhone]):not([fsFormUrl]):not([validate])" }, { kind: "ngmodule", type: FsDialogModule }, { kind: "directive", type: i4.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i4.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i4.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "component", type: i5.FsDialogComponent, selector: "fs-dialog", inputs: ["mobileMode", "mobileButtonPlacement", "mobileWidth", "mode", "buttonLayout", "dock", "dockable", "fullscreenPercent"] }, { kind: "component", type: MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: MatLabel, selector: "mat-label" }, { kind: "component", type: MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "directive", type: MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
835
769
  }
836
770
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: ContentPageComponent, decorators: [{
837
771
  type: Component,
@@ -849,106 +783,48 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImpor
849
783
  MatOption,
850
784
  MatInput,
851
785
  MatDialogActions,
852
- ], template: "<form fsForm [submit]=\"save\" *fsSkeletonForm=\"contentPage\">\n <fs-dialog>\n <h1 mat-dialog-title>{{contentPage.id ? 'Page' : 'Create Page'}}</h1>\n <div mat-dialog-content>\n <div class=\"fs-column\">\n <mat-form-field>\n <mat-label>Type</mat-label>\n <mat-select\n [(ngModel)]=\"contentPage.type\"\n name=\"type\"\n required>\n @for (item of PageTypes; track item) {\n <mat-option\n [value]=\"item.value\">\n {{ item.name }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n @if (contentLayouts) {\n <mat-form-field>\n <mat-label>Layout</mat-label>\n <mat-select\n [(ngModel)]=\"contentPage.contentLayoutId\"\n required\n name=\"contentLayoutId\">\n @for (item of contentLayouts; track item) {\n <mat-option\n [value]=\"item.id\">\n {{ item.name }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n <mat-form-field>\n <mat-label>Name</mat-label>\n <input\n matInput\n [(ngModel)]=\"contentPage.name\"\n name=\"name\"\n required>\n </mat-form-field>\n <mat-form-field>\n <mat-label>Path</mat-label>\n <input\n matInput\n [(ngModel)]=\"contentPage.path\"\n name=\"path\"\n required>\n </mat-form-field>\n <mat-form-field>\n <mat-label>Title</mat-label>\n <input\n matInput\n [(ngModel)]=\"contentPage.title\"\n name=\"title\">\n </mat-form-field>\n </div>\n </div>\n\n <div mat-dialog-actions>\n <fs-form-dialog-actions>\n </fs-form-dialog-actions>\n </div>\n </fs-dialog>\n</form>\n" }]
853
- }], propDecorators: { textEditors: [{
854
- type: ViewChildren,
855
- args: [FsTextEditorComponent]
856
- }] } });
786
+ ], template: "<form fsForm [submit]=\"save\" *fsSkeletonForm=\"contentPage()\">\n <fs-dialog>\n <h1 mat-dialog-title>{{contentPage().id ? 'Page' : 'Create Page'}}</h1>\n <div mat-dialog-content>\n <div class=\"fs-column\">\n @if (isTypeSelectable()) {\n <mat-form-field>\n <mat-label>Type</mat-label>\n <mat-select\n [(ngModel)]=\"contentPage().type\"\n name=\"type\"\n required>\n @for (item of PageTypes; track item) {\n <mat-option\n [value]=\"item.value\">\n {{ item.name }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n @if (contentLayouts()) {\n <mat-form-field>\n <mat-label>Layout</mat-label>\n <mat-select\n [(ngModel)]=\"contentPage().contentLayoutId\"\n required\n name=\"contentLayoutId\">\n @for (item of contentLayouts(); track item.id) {\n <mat-option\n [value]=\"item.id\">\n {{ item.name }}\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n }\n <mat-form-field>\n <mat-label>Name</mat-label>\n <input\n matInput\n [(ngModel)]=\"contentPage().name\"\n name=\"name\"\n required>\n </mat-form-field>\n @if (!isHomePage()) {\n <mat-form-field>\n <mat-label>Path</mat-label>\n <input\n matInput\n [(ngModel)]=\"contentPage().path\"\n name=\"path\"\n required>\n </mat-form-field>\n }\n <mat-form-field>\n <mat-label>Title</mat-label>\n <input\n matInput\n [(ngModel)]=\"contentPage().title\"\n name=\"title\">\n </mat-form-field>\n </div>\n </div>\n\n <div mat-dialog-actions>\n <fs-form-dialog-actions>\n </fs-form-dialog-actions>\n </div>\n </fs-dialog>\n</form>\n" }]
787
+ }], ctorParameters: () => [] });
857
788
 
858
789
  class ContentPageEditorComponent {
859
- _data = inject(MAT_DIALOG_DATA);
860
- _dialogRef = inject(MatDialogRef);
861
- _message = inject(FsMessage);
862
- _dialog = inject(MatDialog);
863
- _cdRef = inject(ChangeDetectorRef);
864
- _prompt = inject(FsPrompt);
865
- editor;
866
- contentPage;
867
- focused = null;
790
+ editor = viewChild(EditorComponent);
791
+ contentPage = signal(null);
868
792
  config;
869
- resizing = false;
870
- title;
871
- editors = {
793
+ editors = signal({
872
794
  [EditorType.Html]: true,
873
795
  [EditorType.Scss]: true,
874
796
  [EditorType.Js]: false,
875
797
  [EditorType.GlobalScss]: false,
876
- };
798
+ });
877
799
  get isMac() {
878
800
  return navigator.platform.toUpperCase().indexOf('MAC') >= 0;
879
801
  }
880
- submitting;
881
- _destroy$ = new Subject();
802
+ submitted = () => this.editor().save$({
803
+ [EditorType.Html]: 'content',
804
+ [EditorType.Scss]: 'styles',
805
+ [EditorType.Js]: 'js',
806
+ }, (values) => this.config.saveContentPage({ id: this.contentPage().id, ...values }));
807
+ _data = inject(MAT_DIALOG_DATA);
808
+ _dialogRef = inject(MatDialogRef);
809
+ _dialog = inject(MatDialog);
810
+ _prompt = inject(FsPrompt);
811
+ _destroyRef = inject(DestroyRef);
882
812
  ngOnInit() {
883
813
  this._dialogRef.addPanelClass('fs-content-editor-overlay-pane');
884
814
  this._dialogRef.disableClose = true;
885
815
  this.config = this._data.contentConfig;
886
- this._initContentPage(this._data.contentPage);
816
+ this._initContentPage();
887
817
  this._initEscape();
888
818
  }
889
819
  editorToggleChange(event) {
890
- this.editors[event.value] = !this.editors[event.value];
891
- }
892
- ngOnDestroy() {
893
- this._destroy$.next(null);
894
- this._destroy$.complete();
895
- }
896
- _initContentPage(contentPage) {
897
- this.config.loadContentPage(contentPage.id)
898
- .subscribe((data) => {
899
- this.contentPage = data;
900
- this._cdRef.markForCheck();
901
- });
902
- }
903
- editorFocused(type) {
904
- this.focused = type;
820
+ this.editors.update((editors) => ({ ...editors, [event.value]: !editors[event.value] }));
905
821
  }
906
822
  save() {
907
823
  this.submitted()
908
824
  .subscribe();
909
825
  }
910
- submitted = () => {
911
- this.submitting = true;
912
- return of(null)
913
- .pipe(filter(() => this.focused), switchMap(() => {
914
- switch (this.focused) {
915
- case EditorType.Js:
916
- case EditorType.Html:
917
- case EditorType.Scss:
918
- return this.saveContentPage();
919
- case EditorType.GlobalScss:
920
- return this.editor.saveGlobalScss();
921
- }
922
- return throwError('Invalid focus');
923
- }), tap(() => {
924
- this.editor.clearChange(this.focused);
925
- this._cdRef.markForCheck();
926
- }), finalize(() => {
927
- this.submitting = false;
928
- this._cdRef.markForCheck();
929
- }))
930
- .pipe(takeUntil(this._destroy$));
931
- };
932
- saveContentPage() {
933
- const names = {
934
- [EditorType.Js]: 'js',
935
- [EditorType.Scss]: 'styles',
936
- [EditorType.Html]: 'content',
937
- };
938
- const data = {
939
- id: this.contentPage.id,
940
- [names[this.focused]]: this.editor.changes[this.focused],
941
- };
942
- return this.config.saveContentPage({
943
- id: this.contentPage.id,
944
- ...data,
945
- })
946
- .pipe(tap(() => {
947
- this._message.success('Saved Changes');
948
- }));
949
- }
950
826
  close() {
951
- if (!this.editor.hasChanges) {
827
+ if (!this.editor()?.hasChanges()) {
952
828
  return this._dialogRef.close();
953
829
  }
954
830
  this._prompt.confirm({
@@ -968,43 +844,54 @@ class ContentPageEditorComponent {
968
844
  },
969
845
  ],
970
846
  })
971
- .pipe(takeUntil(this._destroy$))
972
- .subscribe((value) => {
973
- if (value === 'discard') {
974
- this._dialogRef.close();
975
- }
976
- });
847
+ .pipe(filter((value) => value === 'discard'), tap(() => this._dialogRef.close()), takeUntilDestroyed(this._destroyRef))
848
+ .subscribe();
977
849
  }
978
850
  openPreview() {
979
- window.open(this.contentPage.path, '_blank');
851
+ // Paths are stored without the leading slash, and the home page's is "": opening "" gave
852
+ // a blank tab (CC-T4102). The pages list links the same way.
853
+ window.open('/' + this.contentPage().path, '_blank');
980
854
  }
981
855
  openSettings() {
982
856
  this._dialog.open(ContentPageComponent, {
983
857
  data: {
984
- contentPage: this.contentPage,
858
+ contentPage: this.contentPage(),
985
859
  },
986
860
  })
987
861
  .afterClosed()
988
- .pipe(takeUntil(this._destroy$))
989
- .subscribe((contentPage) => {
990
- this.contentPage = {
991
- ...this.contentPage,
992
- ...contentPage,
993
- };
994
- this._cdRef.markForCheck();
995
- });
862
+ .pipe(filter((contentPage) => !!contentPage),
863
+ // Only what the settings dialog edits. Merging content/styles/js from its response
864
+ // would push them through [html]/[scss]/[js] into the editors and wipe unsaved typing
865
+ // (IEB-T292). The path is merged so Preview follows a renamed page.
866
+ tap((contentPage) => {
867
+ this.contentPage.update((current) => ({
868
+ ...current,
869
+ type: contentPage.type,
870
+ contentLayoutId: contentPage.contentLayoutId,
871
+ name: contentPage.name,
872
+ path: contentPage.path,
873
+ title: contentPage.title,
874
+ }));
875
+ }), takeUntilDestroyed(this._destroyRef))
876
+ .subscribe();
877
+ }
878
+ _initContentPage() {
879
+ this.config.loadContentPage(this._data.contentPage.id)
880
+ .pipe(tap((contentPage) => this.contentPage.set(contentPage)), takeUntilDestroyed(this._destroyRef))
881
+ .subscribe();
996
882
  }
997
883
  _initEscape() {
998
884
  fromEvent(document, 'keydown')
999
- .pipe(filter((event) => event.code === 'Escape'), takeUntil(this._destroy$)).subscribe(() => {
885
+ .pipe(filter((event) => event.code === 'Escape'), tap(() => {
1000
886
  const dialogRef = this._dialog.openDialogs.reverse()[0];
1001
887
  if (dialogRef?.componentInstance === this) {
1002
888
  this.close();
1003
889
  }
1004
- });
890
+ }), takeUntilDestroyed(this._destroyRef))
891
+ .subscribe();
1005
892
  }
1006
893
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: ContentPageEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1007
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.7", type: ContentPageEditorComponent, isStandalone: true, selector: "ng-component", viewQueries: [{ propertyName: "editor", first: true, predicate: EditorComponent, descendants: true }], ngImport: i0, template: "<form fsForm [submit]=\"submitted\" [dirtySubmitButton]=\"false\" [confirm]=\"false\">\n <fs-dialog *fsSkeletonForm=\"contentPage\">\n <h1 mat-dialog-title>\n <div class=\"title-container\">\n <div class=\"title\">\n Page Editor\n <div class=\"small\">{{contentPage.name}}</div>\n </div>\n\n <div class=\"toggles\">\n <app-editor-toggles\n [editors]=\"editors\"\n [showJs]=\"true\"\n (toggleChange)=\"editorToggleChange($event)\">\n </app-editor-toggles>\n </div>\n\n <app-editor-actions\n [saveDisabled]=\"!editor?.hasChanges || submitting\"\n [isMac]=\"isMac\"\n [previewPath]=\"contentPage.path\"\n (settings)=\"openSettings()\"\n (save)=\"save()\"\n (preview)=\"openPreview()\"\n (done)=\"close()\">\n </app-editor-actions>\n </div>\n </h1>\n <div mat-dialog-content>\n <app-editor\n [contentConfig]=\"config\"\n [showHtml]=\"editors.html\"\n [showJs]=\"editors.js\"\n [showScss]=\"editors.scss\"\n [showGlobalScss]=\"editors.globalScss\"\n [html]=\"contentPage.content\"\n [scss]=\"contentPage.styles\"\n [js]=\"contentPage.js\"\n (focused)=\"editorFocused($event)\">\n </app-editor>\n </div>\n </fs-dialog>\n</form>\n", styles: ["::ng-deep .fs-content-editor-overlay-pane .mat-dialog-container,::ng-deep .fs-content-editor-overlay-pane .mat-mdc-dialog-container{border-radius:0;height:100%}::ng-deep .fs-content-editor-overlay-pane .mdc-dialog__container,::ng-deep .fs-content-editor-overlay-pane .mat-mdc-dialog-surface{height:100%}form{height:100%}fs-dialog{display:flex;height:100%;flex-direction:column}fs-dialog ::ng-deep .mat-dialog-content,fs-dialog ::ng-deep .mat-mdc-dialog-content{max-height:none;flex:1;min-height:0;overflow:hidden;display:flex;flex-direction:column}fs-dialog ::ng-deep .mat-button-toggle-label-content{line-height:36px}h1 .toggles{display:flex;justify-content:flex-end;justify-self:baseline;font-size:13px}h1 .actions{margin-left:30px}h1 .actions .mdc-button{margin-left:5px}.title-container{display:flex;align-items:center}.title-container .title{flex:1}.title-container .title .small{line-height:normal}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: FsFormModule }, { kind: "directive", type: i3.FsFormDirective, selector: "[fsForm]", inputs: ["wrapperSelector", "messageSelector", "hintSelector", "labelSelector", "autocomplete", "shortcuts", "confirm", "confirmDialog", "confirmDrawer", "confirmBrowser", "confirmTabs", "dirtySubmitButton", "submit", "successDelay", "errorDelay", "tabGroup", "deactivationGuard"], outputs: ["fsForm", "invalid", "valid", "submitted", "reseted", "cleared"], exportAs: ["fsForm"] }, { kind: "ngmodule", type: FsSkeletonModule }, { kind: "directive", type: i1$1.FsSkeletonFormDirective, selector: "[fsSkeletonForm]", inputs: ["fsSkeletonForm", "fsSkeletonFormLines"] }, { kind: "ngmodule", type: FsDialogModule }, { kind: "directive", type: i4.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i4.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "component", type: i5$1.FsDialogComponent, selector: "fs-dialog", inputs: ["mobileMode", "mobileButtonPlacement", "mobileWidth", "mode", "buttonLayout", "dock", "dockable", "fullscreenPercent"] }, { kind: "component", type: EditorTogglesComponent, selector: "app-editor-toggles", inputs: ["editors", "showJs"], outputs: ["toggleChange"] }, { kind: "component", type: EditorActionsComponent, selector: "app-editor-actions", inputs: ["saveDisabled", "isMac", "previewPath"], outputs: ["settings", "save", "done", "preview"] }, { kind: "component", type: EditorComponent, selector: "app-editor", inputs: ["showHtml", "showScss", "showJs", "showGlobalScss", "html", "scss", "js", "contentConfig"], outputs: ["changed", "focused", "blured"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
894
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "18.2.7", type: ContentPageEditorComponent, isStandalone: true, selector: "ng-component", viewQueries: [{ propertyName: "editor", first: true, predicate: EditorComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<form fsForm [submit]=\"submitted\" [dirtySubmitButton]=\"false\" [confirm]=\"false\">\n <fs-dialog *fsSkeletonForm=\"contentPage()\">\n <h1 mat-dialog-title>\n <div class=\"title-container\">\n <div class=\"title\">\n Page Editor\n <div class=\"small\">{{contentPage().name}}</div>\n </div>\n\n <div class=\"toggles\">\n <app-editor-toggles\n [editors]=\"editors()\"\n [showJs]=\"true\"\n (toggleChange)=\"editorToggleChange($event)\">\n </app-editor-toggles>\n </div>\n\n <app-editor-actions\n [saveDisabled]=\"!editor()?.hasChanges() || editor()?.saving()\"\n [isMac]=\"isMac\"\n [previewPath]=\"contentPage().path\"\n (settings)=\"openSettings()\"\n (save)=\"save()\"\n (preview)=\"openPreview()\"\n (done)=\"close()\">\n </app-editor-actions>\n </div>\n </h1>\n <div mat-dialog-content>\n <app-editor\n [contentConfig]=\"config\"\n [showHtml]=\"editors().html\"\n [showJs]=\"editors().js\"\n [showScss]=\"editors().scss\"\n [showGlobalScss]=\"editors().globalScss\"\n [html]=\"contentPage().content\"\n [scss]=\"contentPage().styles\"\n [js]=\"contentPage().js\">\n </app-editor>\n </div>\n </fs-dialog>\n</form>\n", styles: ["::ng-deep .fs-content-editor-overlay-pane .mat-dialog-container,::ng-deep .fs-content-editor-overlay-pane .mat-mdc-dialog-container{border-radius:0;height:100%}::ng-deep .fs-content-editor-overlay-pane .mdc-dialog__container,::ng-deep .fs-content-editor-overlay-pane .mat-mdc-dialog-surface{height:100%}form{height:100%}fs-dialog{display:flex;height:100%;flex-direction:column}fs-dialog ::ng-deep .mat-dialog-content,fs-dialog ::ng-deep .mat-mdc-dialog-content{max-height:none;flex:1;min-height:0;overflow:hidden;display:flex;flex-direction:column}fs-dialog ::ng-deep .mat-button-toggle-label-content{line-height:36px}h1 .toggles{display:flex;justify-content:flex-end;justify-self:baseline;font-size:13px}h1 .actions{margin-left:30px}h1 .actions .mdc-button{margin-left:5px}.title-container{display:flex;align-items:center}.title-container .title{flex:1}.title-container .title .small{line-height:normal}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: FsFormModule }, { kind: "directive", type: i3.FsFormDirective, selector: "[fsForm]", inputs: ["wrapperSelector", "messageSelector", "hintSelector", "labelSelector", "autocomplete", "shortcuts", "confirm", "confirmDialog", "confirmDrawer", "confirmBrowser", "confirmTabs", "dirtySubmitButton", "submit", "successDelay", "errorDelay", "tabGroup", "deactivationGuard"], outputs: ["fsForm", "invalid", "valid", "submitted", "reseted", "cleared"], exportAs: ["fsForm"] }, { kind: "ngmodule", type: FsSkeletonModule }, { kind: "directive", type: i1$1.FsSkeletonFormDirective, selector: "[fsSkeletonForm]", inputs: ["fsSkeletonForm", "fsSkeletonFormLines"] }, { kind: "ngmodule", type: FsDialogModule }, { kind: "directive", type: i4.MatDialogTitle, selector: "[mat-dialog-title], [matDialogTitle]", inputs: ["id"], exportAs: ["matDialogTitle"] }, { kind: "directive", type: i4.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "component", type: i5.FsDialogComponent, selector: "fs-dialog", inputs: ["mobileMode", "mobileButtonPlacement", "mobileWidth", "mode", "buttonLayout", "dock", "dockable", "fullscreenPercent"] }, { kind: "component", type: EditorTogglesComponent, selector: "app-editor-toggles", inputs: ["editors", "showJs"], outputs: ["toggleChange"] }, { kind: "component", type: EditorActionsComponent, selector: "app-editor-actions", inputs: ["saveDisabled", "isMac", "previewPath"], outputs: ["settings", "save", "done", "preview"] }, { kind: "component", type: EditorComponent, selector: "app-editor", inputs: ["showHtml", "showScss", "showJs", "showGlobalScss", "contentConfig", "html", "scss", "js"], outputs: ["htmlChange", "scssChange", "jsChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1008
895
  }
1009
896
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: ContentPageEditorComponent, decorators: [{
1010
897
  type: Component,
@@ -1019,11 +906,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImpor
1019
906
  CdkScrollable,
1020
907
  MatDialogContent,
1021
908
  EditorComponent,
1022
- ], template: "<form fsForm [submit]=\"submitted\" [dirtySubmitButton]=\"false\" [confirm]=\"false\">\n <fs-dialog *fsSkeletonForm=\"contentPage\">\n <h1 mat-dialog-title>\n <div class=\"title-container\">\n <div class=\"title\">\n Page Editor\n <div class=\"small\">{{contentPage.name}}</div>\n </div>\n\n <div class=\"toggles\">\n <app-editor-toggles\n [editors]=\"editors\"\n [showJs]=\"true\"\n (toggleChange)=\"editorToggleChange($event)\">\n </app-editor-toggles>\n </div>\n\n <app-editor-actions\n [saveDisabled]=\"!editor?.hasChanges || submitting\"\n [isMac]=\"isMac\"\n [previewPath]=\"contentPage.path\"\n (settings)=\"openSettings()\"\n (save)=\"save()\"\n (preview)=\"openPreview()\"\n (done)=\"close()\">\n </app-editor-actions>\n </div>\n </h1>\n <div mat-dialog-content>\n <app-editor\n [contentConfig]=\"config\"\n [showHtml]=\"editors.html\"\n [showJs]=\"editors.js\"\n [showScss]=\"editors.scss\"\n [showGlobalScss]=\"editors.globalScss\"\n [html]=\"contentPage.content\"\n [scss]=\"contentPage.styles\"\n [js]=\"contentPage.js\"\n (focused)=\"editorFocused($event)\">\n </app-editor>\n </div>\n </fs-dialog>\n</form>\n", styles: ["::ng-deep .fs-content-editor-overlay-pane .mat-dialog-container,::ng-deep .fs-content-editor-overlay-pane .mat-mdc-dialog-container{border-radius:0;height:100%}::ng-deep .fs-content-editor-overlay-pane .mdc-dialog__container,::ng-deep .fs-content-editor-overlay-pane .mat-mdc-dialog-surface{height:100%}form{height:100%}fs-dialog{display:flex;height:100%;flex-direction:column}fs-dialog ::ng-deep .mat-dialog-content,fs-dialog ::ng-deep .mat-mdc-dialog-content{max-height:none;flex:1;min-height:0;overflow:hidden;display:flex;flex-direction:column}fs-dialog ::ng-deep .mat-button-toggle-label-content{line-height:36px}h1 .toggles{display:flex;justify-content:flex-end;justify-self:baseline;font-size:13px}h1 .actions{margin-left:30px}h1 .actions .mdc-button{margin-left:5px}.title-container{display:flex;align-items:center}.title-container .title{flex:1}.title-container .title .small{line-height:normal}\n"] }]
1023
- }], propDecorators: { editor: [{
1024
- type: ViewChild,
1025
- args: [EditorComponent]
1026
- }] } });
909
+ ], template: "<form fsForm [submit]=\"submitted\" [dirtySubmitButton]=\"false\" [confirm]=\"false\">\n <fs-dialog *fsSkeletonForm=\"contentPage()\">\n <h1 mat-dialog-title>\n <div class=\"title-container\">\n <div class=\"title\">\n Page Editor\n <div class=\"small\">{{contentPage().name}}</div>\n </div>\n\n <div class=\"toggles\">\n <app-editor-toggles\n [editors]=\"editors()\"\n [showJs]=\"true\"\n (toggleChange)=\"editorToggleChange($event)\">\n </app-editor-toggles>\n </div>\n\n <app-editor-actions\n [saveDisabled]=\"!editor()?.hasChanges() || editor()?.saving()\"\n [isMac]=\"isMac\"\n [previewPath]=\"contentPage().path\"\n (settings)=\"openSettings()\"\n (save)=\"save()\"\n (preview)=\"openPreview()\"\n (done)=\"close()\">\n </app-editor-actions>\n </div>\n </h1>\n <div mat-dialog-content>\n <app-editor\n [contentConfig]=\"config\"\n [showHtml]=\"editors().html\"\n [showJs]=\"editors().js\"\n [showScss]=\"editors().scss\"\n [showGlobalScss]=\"editors().globalScss\"\n [html]=\"contentPage().content\"\n [scss]=\"contentPage().styles\"\n [js]=\"contentPage().js\">\n </app-editor>\n </div>\n </fs-dialog>\n</form>\n", styles: ["::ng-deep .fs-content-editor-overlay-pane .mat-dialog-container,::ng-deep .fs-content-editor-overlay-pane .mat-mdc-dialog-container{border-radius:0;height:100%}::ng-deep .fs-content-editor-overlay-pane .mdc-dialog__container,::ng-deep .fs-content-editor-overlay-pane .mat-mdc-dialog-surface{height:100%}form{height:100%}fs-dialog{display:flex;height:100%;flex-direction:column}fs-dialog ::ng-deep .mat-dialog-content,fs-dialog ::ng-deep .mat-mdc-dialog-content{max-height:none;flex:1;min-height:0;overflow:hidden;display:flex;flex-direction:column}fs-dialog ::ng-deep .mat-button-toggle-label-content{line-height:36px}h1 .toggles{display:flex;justify-content:flex-end;justify-self:baseline;font-size:13px}h1 .actions{margin-left:30px}h1 .actions .mdc-button{margin-left:5px}.title-container{display:flex;align-items:center}.title-container .title{flex:1}.title-container .title .small{line-height:normal}\n"] }]
910
+ }] });
1027
911
 
1028
912
  class FsContentPagesComponent {
1029
913
  _config = inject(FS_CONTENT_CONFIG);
@@ -1124,7 +1008,7 @@ class FsContentPagesComponent {
1124
1008
  };
1125
1009
  }
1126
1010
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: FsContentPagesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1127
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.7", type: FsContentPagesComponent, isStandalone: true, selector: "fs-content-pages", inputs: { htmlEditorConfig: "htmlEditorConfig" }, viewQueries: [{ propertyName: "listComponent", first: true, predicate: FsListComponent, descendants: true }], ngImport: i0, template: "<fs-list [config]=\"listConfig\">\n <fs-list-column name=\"name\" title=\"Name\" [sortable]=\"true\">\n <ng-template fs-list-cell let-row=\"row\">\n @if (row.state !== 'deleted') {\n <a (click)=\"openEditor(row)\">\n <ng-container [ngTemplateOutlet]=\"name\"></ng-container>\n </a>\n } @else {\n {{row.name}}\n }\n <ng-template #name>{{row.name}}</ng-template>\n </ng-template>\n </fs-list-column>\n <fs-list-column name=\"path\" title=\"Path\" [sortable]=\"true\">\n <ng-template fs-list-cell let-row=\"row\">\n @if (row.state !== 'deleted') {\n <a [href]=\"'/' + row.path\" target=\"_black\">\n <ng-container [ngTemplateOutlet]=\"path\"></ng-container>\n </a>\n } @else {\n /{{row.path}}\n }\n <ng-template #path>/{{row.path}}</ng-template>\n </ng-template>\n </fs-list-column>\n <fs-list-column name=\"layout\" title=\"Layout\">\n <ng-template fs-list-cell let-row=\"row\">\n {{row.contentLayout?.name}}\n </ng-template>\n </fs-list-column>\n <fs-list-column name=\"type\" title=\"Type\" [sortable]=\"true\">\n <ng-template fs-list-cell let-row=\"row\">\n {{pageTypes[row.type]}}\n </ng-template>\n </fs-list-column>\n <fs-list-column name=\"modify_date\" title=\"Modified\" [sortable]=\"true\">\n <ng-template fs-list-cell let-row=\"row\">\n {{row.modifyDate | fsDate: 'date-time-yearless'}}\n </ng-template>\n </fs-list-column>\n</fs-list>\n", styles: [":host ::ng-deep .modified,:host ::ng-deep .name{width:1%;white-space:nowrap}:host ::ng-deep .preview-content img{display:none}.preview-content{position:relative;max-height:100px;max-width:100%;overflow:hidden;-webkit-mask-image:-webkit-gradient(linear,left 60%,left bottom,from(rgb(0,0,0)),to(rgba(0,0,0,0)))}\n"], dependencies: [{ kind: "ngmodule", type: FsListModule }, { kind: "component", type: i1$2.FsListComponent, selector: "fs-list", inputs: ["config", "loaderLines"], outputs: ["filtersReady"] }, { kind: "directive", type: i1$2.FsListColumnDirective, selector: "fs-list-column", inputs: ["show", "title", "name", "customize", "sortable", "sortableDefault", "sortableDirection", "direction", "align", "width", "class"] }, { kind: "directive", type: i1$2.FsListCellDirective, selector: "[fs-list-cell]", inputs: ["colspan", "align", "class"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FsDateModule }, { kind: "pipe", type: i2$2.FsDatePipe, name: "fsDate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1011
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.7", type: FsContentPagesComponent, isStandalone: true, selector: "fs-content-pages", inputs: { htmlEditorConfig: "htmlEditorConfig" }, viewQueries: [{ propertyName: "listComponent", first: true, predicate: FsListComponent, descendants: true }], ngImport: i0, template: "<fs-list [config]=\"listConfig\">\n <fs-list-column name=\"name\" title=\"Name\" [sortable]=\"true\">\n <ng-template fs-list-cell let-row=\"row\">\n @if (row.state !== 'deleted') {\n <a (click)=\"openEditor(row)\">\n <ng-container [ngTemplateOutlet]=\"name\"></ng-container>\n </a>\n } @else {\n {{row.name}}\n }\n <ng-template #name>{{row.name}}</ng-template>\n </ng-template>\n </fs-list-column>\n <fs-list-column name=\"path\" title=\"Path\" [sortable]=\"true\">\n <ng-template fs-list-cell let-row=\"row\">\n @if (row.state !== 'deleted') {\n <a [href]=\"'/' + row.path\" target=\"_blank\">\n <ng-container [ngTemplateOutlet]=\"path\"></ng-container>\n </a>\n } @else {\n /{{row.path}}\n }\n <ng-template #path>/{{row.path}}</ng-template>\n </ng-template>\n </fs-list-column>\n <fs-list-column name=\"layout\" title=\"Layout\">\n <ng-template fs-list-cell let-row=\"row\">\n {{row.contentLayout?.name}}\n </ng-template>\n </fs-list-column>\n <fs-list-column name=\"type\" title=\"Type\" [sortable]=\"true\">\n <ng-template fs-list-cell let-row=\"row\">\n {{pageTypes[row.type]}}\n </ng-template>\n </fs-list-column>\n <fs-list-column name=\"modify_date\" title=\"Modified\" [sortable]=\"true\">\n <ng-template fs-list-cell let-row=\"row\">\n {{row.modifyDate | fsDate: 'date-time-yearless'}}\n </ng-template>\n </fs-list-column>\n</fs-list>\n", styles: [":host ::ng-deep .modified,:host ::ng-deep .name{width:1%;white-space:nowrap}:host ::ng-deep .preview-content img{display:none}.preview-content{position:relative;max-height:100px;max-width:100%;overflow:hidden;-webkit-mask-image:-webkit-gradient(linear,left 60%,left bottom,from(rgb(0,0,0)),to(rgba(0,0,0,0)))}\n"], dependencies: [{ kind: "ngmodule", type: FsListModule }, { kind: "component", type: i1$2.FsListComponent, selector: "fs-list", inputs: ["config", "loaderLines"], outputs: ["filtersReady"] }, { kind: "directive", type: i1$2.FsListColumnDirective, selector: "fs-list-column", inputs: ["show", "title", "name", "customize", "sortable", "sortableDefault", "sortableDirection", "direction", "align", "width", "class"] }, { kind: "directive", type: i1$2.FsListCellDirective, selector: "[fs-list-cell]", inputs: ["colspan", "align", "class"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FsDateModule }, { kind: "pipe", type: i2$2.FsDatePipe, name: "fsDate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1128
1012
  }
1129
1013
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: FsContentPagesComponent, decorators: [{
1130
1014
  type: Component,
@@ -1132,7 +1016,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImpor
1132
1016
  FsListModule,
1133
1017
  NgTemplateOutlet,
1134
1018
  FsDateModule,
1135
- ], template: "<fs-list [config]=\"listConfig\">\n <fs-list-column name=\"name\" title=\"Name\" [sortable]=\"true\">\n <ng-template fs-list-cell let-row=\"row\">\n @if (row.state !== 'deleted') {\n <a (click)=\"openEditor(row)\">\n <ng-container [ngTemplateOutlet]=\"name\"></ng-container>\n </a>\n } @else {\n {{row.name}}\n }\n <ng-template #name>{{row.name}}</ng-template>\n </ng-template>\n </fs-list-column>\n <fs-list-column name=\"path\" title=\"Path\" [sortable]=\"true\">\n <ng-template fs-list-cell let-row=\"row\">\n @if (row.state !== 'deleted') {\n <a [href]=\"'/' + row.path\" target=\"_black\">\n <ng-container [ngTemplateOutlet]=\"path\"></ng-container>\n </a>\n } @else {\n /{{row.path}}\n }\n <ng-template #path>/{{row.path}}</ng-template>\n </ng-template>\n </fs-list-column>\n <fs-list-column name=\"layout\" title=\"Layout\">\n <ng-template fs-list-cell let-row=\"row\">\n {{row.contentLayout?.name}}\n </ng-template>\n </fs-list-column>\n <fs-list-column name=\"type\" title=\"Type\" [sortable]=\"true\">\n <ng-template fs-list-cell let-row=\"row\">\n {{pageTypes[row.type]}}\n </ng-template>\n </fs-list-column>\n <fs-list-column name=\"modify_date\" title=\"Modified\" [sortable]=\"true\">\n <ng-template fs-list-cell let-row=\"row\">\n {{row.modifyDate | fsDate: 'date-time-yearless'}}\n </ng-template>\n </fs-list-column>\n</fs-list>\n", styles: [":host ::ng-deep .modified,:host ::ng-deep .name{width:1%;white-space:nowrap}:host ::ng-deep .preview-content img{display:none}.preview-content{position:relative;max-height:100px;max-width:100%;overflow:hidden;-webkit-mask-image:-webkit-gradient(linear,left 60%,left bottom,from(rgb(0,0,0)),to(rgba(0,0,0,0)))}\n"] }]
1019
+ ], template: "<fs-list [config]=\"listConfig\">\n <fs-list-column name=\"name\" title=\"Name\" [sortable]=\"true\">\n <ng-template fs-list-cell let-row=\"row\">\n @if (row.state !== 'deleted') {\n <a (click)=\"openEditor(row)\">\n <ng-container [ngTemplateOutlet]=\"name\"></ng-container>\n </a>\n } @else {\n {{row.name}}\n }\n <ng-template #name>{{row.name}}</ng-template>\n </ng-template>\n </fs-list-column>\n <fs-list-column name=\"path\" title=\"Path\" [sortable]=\"true\">\n <ng-template fs-list-cell let-row=\"row\">\n @if (row.state !== 'deleted') {\n <a [href]=\"'/' + row.path\" target=\"_blank\">\n <ng-container [ngTemplateOutlet]=\"path\"></ng-container>\n </a>\n } @else {\n /{{row.path}}\n }\n <ng-template #path>/{{row.path}}</ng-template>\n </ng-template>\n </fs-list-column>\n <fs-list-column name=\"layout\" title=\"Layout\">\n <ng-template fs-list-cell let-row=\"row\">\n {{row.contentLayout?.name}}\n </ng-template>\n </fs-list-column>\n <fs-list-column name=\"type\" title=\"Type\" [sortable]=\"true\">\n <ng-template fs-list-cell let-row=\"row\">\n {{pageTypes[row.type]}}\n </ng-template>\n </fs-list-column>\n <fs-list-column name=\"modify_date\" title=\"Modified\" [sortable]=\"true\">\n <ng-template fs-list-cell let-row=\"row\">\n {{row.modifyDate | fsDate: 'date-time-yearless'}}\n </ng-template>\n </fs-list-column>\n</fs-list>\n", styles: [":host ::ng-deep .modified,:host ::ng-deep .name{width:1%;white-space:nowrap}:host ::ng-deep .preview-content img{display:none}.preview-content{position:relative;max-height:100px;max-width:100%;overflow:hidden;-webkit-mask-image:-webkit-gradient(linear,left 60%,left bottom,from(rgb(0,0,0)),to(rgba(0,0,0,0)))}\n"] }]
1136
1020
  }], propDecorators: { htmlEditorConfig: [{
1137
1021
  type: Input
1138
1022
  }], listComponent: [{
@@ -1206,7 +1090,7 @@ class ContentStyleComponent {
1206
1090
  })).subscribe();
1207
1091
  }
1208
1092
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: ContentStyleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1209
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.7", type: ContentStyleComponent, isStandalone: true, selector: "fs-content-style", inputs: { height: "height" }, viewQueries: [{ propertyName: "textEditors", first: true, predicate: FsTextEditorComponent, descendants: true }, { propertyName: "form", first: true, predicate: FsFormDirective, descendants: true }], ngImport: i0, template: "<ng-container *fsSkeletonForm=\"contentStyle\">\n <fs-text-editor \n [(ngModel)]=\"contentStyle.scss\" \n name=\"contentStyle\"\n [fsModelChangeOptions]=\"{ debounce: 300 }\"\n (fsModelChange)=\"save()\"\n [config]=\"styleConfig\">\n </fs-text-editor> \n</ng-container>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: FsSkeletonModule }, { kind: "directive", type: i1$1.FsSkeletonFormDirective, selector: "[fsSkeletonForm]", inputs: ["fsSkeletonForm", "fsSkeletonFormLines"] }, { kind: "ngmodule", type: FsTextEditorModule }, { kind: "component", type: i2.FsTextEditorComponent, selector: "fs-text-editor", inputs: ["config", "scrollable"], outputs: ["ready", "blur"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: FsCommonModule }, { kind: "directive", type: i5.FsModelChangeDirective, selector: "[fsModelChange]", inputs: ["fsModelChangeOptions"], outputs: ["fsModelChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1093
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.7", type: ContentStyleComponent, isStandalone: true, selector: "fs-content-style", inputs: { height: "height" }, viewQueries: [{ propertyName: "textEditors", first: true, predicate: FsTextEditorComponent, descendants: true }, { propertyName: "form", first: true, predicate: FsFormDirective, descendants: true }], ngImport: i0, template: "<ng-container *fsSkeletonForm=\"contentStyle\">\n <fs-text-editor \n [(ngModel)]=\"contentStyle.scss\" \n name=\"contentStyle\"\n [fsModelChangeOptions]=\"{ debounce: 300 }\"\n (fsModelChange)=\"save()\"\n [config]=\"styleConfig\">\n </fs-text-editor> \n</ng-container>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: FsSkeletonModule }, { kind: "directive", type: i1$1.FsSkeletonFormDirective, selector: "[fsSkeletonForm]", inputs: ["fsSkeletonForm", "fsSkeletonFormLines"] }, { kind: "ngmodule", type: FsTextEditorModule }, { kind: "component", type: i2.FsTextEditorComponent, selector: "fs-text-editor", inputs: ["config", "scrollable"], outputs: ["ready", "blur"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: FsCommonModule }, { kind: "directive", type: i4$1.FsModelChangeDirective, selector: "[fsModelChange]", inputs: ["fsModelChangeOptions"], outputs: ["fsModelChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1210
1094
  }
1211
1095
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: ContentStyleComponent, decorators: [{
1212
1096
  type: Component,