@firestitch/content 18.0.9 → 18.0.11

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.
Files changed (18) hide show
  1. package/app/modules/content-layouts/components/content-layout/content-layout.component.d.ts +6 -15
  2. package/app/modules/content-layouts/components/content-layout-editor/content-layout-editor.component.d.ts +11 -24
  3. package/app/modules/content-pages/components/content-page/content-page.component.d.ts +13 -19
  4. package/app/modules/content-pages/components/content-page-editor/content-page-editor.component.d.ts +16 -32
  5. package/app/modules/editor/components/editor/editor.component.d.ts +41 -36
  6. package/app/modules/editor/components/editor-actions/editor-actions.component.d.ts +2 -1
  7. package/esm2022/app/modules/content-layouts/components/content-layout/content-layout.component.mjs +15 -32
  8. package/esm2022/app/modules/content-layouts/components/content-layout-editor/content-layout-editor.component.mjs +44 -95
  9. package/esm2022/app/modules/content-pages/components/content-page/content-page.component.mjs +29 -40
  10. package/esm2022/app/modules/content-pages/components/content-page-editor/content-page-editor.component.mjs +56 -103
  11. package/esm2022/app/modules/content-pages/components/content-pages/content-pages.component.mjs +3 -3
  12. package/esm2022/app/modules/editor/components/editor/editor.component.mjs +119 -127
  13. package/esm2022/app/modules/editor/components/editor-actions/editor-actions.component.mjs +11 -3
  14. package/fesm2022/firestitch-content.mjs +254 -373
  15. package/fesm2022/firestitch-content.mjs.map +1 -1
  16. package/package.json +1 -1
  17. package/app/modules/editor/components/editor/index.d.ts +0 -1
  18. package/esm2022/app/modules/editor/components/editor/index.mjs +0 -2
@@ -1,32 +1,33 @@
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';
28
+ import { MatButton, MatIconButton } from '@angular/material/button';
29
29
  import { MatIcon } from '@angular/material/icon';
30
+ import { MatTooltip } from '@angular/material/tooltip';
30
31
  import { MatFormField, MatLabel } from '@angular/material/form-field';
31
32
  import { MatInput } from '@angular/material/input';
32
33
  import { ItemType } from '@firestitch/filter';
@@ -36,6 +37,8 @@ import * as i2$2 from '@firestitch/date';
36
37
  import { FsDateModule } from '@firestitch/date';
37
38
  import { MatSelect } from '@angular/material/select';
38
39
  import { MatOption } from '@angular/material/core';
40
+ import * as i4$1 from '@firestitch/common';
41
+ import { index, FsCommonModule } from '@firestitch/common';
39
42
 
40
43
  class ContentRendererComponent {
41
44
  _sanitizer = inject(DomSanitizer);
@@ -224,116 +227,132 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImpor
224
227
  }] } });
225
228
 
226
229
  class EditorComponent {
227
- _cdRef = inject(ChangeDetectorRef);
228
- _message = inject(FsMessage);
229
- showHtml = false;
230
- showScss = false;
231
- showJs = false;
232
- showGlobalScss = false;
233
- html;
234
- scss;
235
- js;
236
- contentConfig;
237
- changed = new EventEmitter();
238
- focused = new EventEmitter();
239
- blured = new EventEmitter();
240
- 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);
241
254
  EditorType = EditorType;
242
- focusedArea;
243
- contentStyle;
244
- resizing = false;
245
- title;
246
255
  scssConfig;
247
256
  globalScssConfig;
248
257
  htmlConfig;
249
258
  jsConfig;
250
- _destroy$ = new Subject();
259
+ _saved = signal({});
260
+ _message = inject(FsMessage);
261
+ _destroyRef = inject(DestroyRef);
251
262
  ngOnInit() {
252
- this.initTextEditors();
253
- this.initGlobalContentStyle();
254
- }
255
- ngOnDestroy() {
256
- this._destroy$.next(null);
257
- 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)));
258
310
  }
259
- change(type, value) {
260
- this.changed.emit({ type, value });
261
- 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
+ }));
262
321
  }
263
- get hasChanges() {
264
- return Object.keys(this.changes)
265
- .filter((name) => !!this.changes[name])
266
- .length !== 0;
322
+ _markSaved(types, sent) {
323
+ this._saved.update((saved) => ({
324
+ ...saved,
325
+ ...Object.fromEntries(types.map((type) => [type, sent[type]])),
326
+ }));
267
327
  }
268
- clearChange(type) {
269
- this.changes[type] = undefined;
270
- 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');
271
333
  }
272
- initTextEditors() {
273
- this.scssConfig = {
274
- tabSize: 2,
275
- language: 'scss',
276
- height: '100%',
277
- focus: () => {
278
- this._onFocus(EditorType.Scss);
279
- },
280
- blur: () => {
281
- this.blured.emit(EditorType.Scss);
282
- },
283
- };
284
- this.jsConfig = {
285
- tabSize: 2,
286
- language: 'js',
287
- height: '100%',
288
- focus: () => {
289
- this._onFocus(EditorType.Js);
290
- },
291
- blur: () => {
292
- this.blured.emit(EditorType.Js);
293
- },
294
- };
295
- this.htmlConfig = {
296
- tabSize: 2,
297
- language: 'html',
298
- height: '100%',
299
- focus: () => {
300
- this._onFocus(EditorType.Html);
301
- },
302
- blur: () => {
303
- this.blured.emit(EditorType.Html);
304
- },
305
- };
306
- this.globalScssConfig = {
334
+ _createTextEditorConfig(type, language) {
335
+ return {
307
336
  tabSize: 2,
308
- language: 'scss',
337
+ language,
309
338
  height: '100%',
310
339
  focus: () => {
311
- this._onFocus(EditorType.GlobalScss);
312
- },
313
- blur: () => {
314
- this.blured.emit(EditorType.GlobalScss);
340
+ this.focusedArea.set(type);
315
341
  },
316
342
  };
317
343
  }
318
- initGlobalContentStyle() {
319
- this.contentConfig.loadContentStyle()
320
- .subscribe((contentStyle) => {
321
- this.contentStyle = contentStyle || {};
322
- this._cdRef.markForCheck();
323
- });
324
- }
325
- saveGlobalScss() {
326
- return this.contentConfig.saveContentStyle(this.contentStyle)
327
- .pipe(tap(() => {
328
- this._message.success('Saved Changes');
329
- }));
330
- }
331
- _onFocus(type) {
332
- this.focusedArea = type;
333
- 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();
334
353
  }
335
354
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: EditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
336
- 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 });
337
356
  }
338
357
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: EditorComponent, decorators: [{
339
358
  type: Component,
@@ -343,32 +362,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImpor
343
362
  FsTextEditorModule,
344
363
  FormsModule,
345
364
  FsFormModule,
346
- FsCommonModule,
347
365
  FsSkeletonModule,
348
- ], 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"] }]
349
- }], propDecorators: { showHtml: [{
350
- type: Input
351
- }], showScss: [{
352
- type: Input
353
- }], showJs: [{
354
- type: Input
355
- }], showGlobalScss: [{
356
- type: Input
357
- }], html: [{
358
- type: Input
359
- }], scss: [{
360
- type: Input
361
- }], js: [{
362
- type: Input
363
- }], contentConfig: [{
364
- type: Input
365
- }], changed: [{
366
- type: Output
367
- }], focused: [{
368
- type: Output
369
- }], blured: [{
370
- type: Output
371
- }] } });
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
+ }] });
372
368
 
373
369
  class EditorTogglesComponent {
374
370
  editors;
@@ -399,14 +395,18 @@ class EditorActionsComponent {
399
395
  settings = new EventEmitter();
400
396
  save = new EventEmitter();
401
397
  done = new EventEmitter();
398
+ preview = new EventEmitter();
402
399
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: EditorActionsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
403
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.7", type: EditorActionsComponent, isStandalone: true, selector: "app-editor-actions", inputs: { saveDisabled: "saveDisabled", isMac: "isMac", previewPath: "previewPath" }, outputs: { settings: "settings", save: "save", done: "done" }, ngImport: i0, template: "<button\n type=\"button\"\n class=\"icon-button\"\n (click)=\"settings.emit()\">\n <mat-icon>settings</mat-icon>\n</button>\n\n<div class=\"actions\">\n <button\n type=\"button\"\n class=\"action-button\"\n [disabled]=\"saveDisabled\"\n [title]=\"isMac ? 'cmd+s' : 'ctrl+s'\"\n (click)=\"save.emit()\">\n Save\n </button>\n\n @if (previewPath !== null) {\n <a\n class=\"action-button\"\n target=\"_blank\"\n [href]=\"previewPath\">\n Preview\n </a>\n }\n\n <button\n type=\"button\"\n class=\"action-button\"\n (click)=\"done.emit()\">\n Done\n </button>\n</div>\n", styles: [":host{display:flex;align-items:center}.icon-button{display:inline-flex;align-items:center;justify-content:center;width:40px;height:40px;padding:0;border:none;border-radius:50%;background:transparent;cursor:pointer;color:inherit}.icon-button:hover{background:#0000000a}.actions{display:flex;gap:5px;margin-left:30px}.action-button{display:inline-flex;align-items:center;justify-content:center;height:36px;padding:0 16px;border:1px solid rgba(0,0,0,.12);border-radius:4px;background:transparent;font:inherit;font-weight:500;color:inherit;text-decoration:none;cursor:pointer;white-space:nowrap}.action-button:hover:not([disabled]){background:#0000000a}.action-button[disabled]{opacity:.38;cursor:default}\n"], dependencies: [{ kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
400
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.7", type: EditorActionsComponent, isStandalone: true, selector: "app-editor-actions", inputs: { saveDisabled: "saveDisabled", isMac: "isMac", previewPath: "previewPath" }, outputs: { settings: "settings", save: "save", done: "done", preview: "preview" }, ngImport: i0, template: "<button\n type=\"button\"\n mat-icon-button\n (click)=\"settings.emit()\">\n <mat-icon>settings</mat-icon>\n</button>\n\n<div class=\"actions\">\n <button\n mat-stroked-button\n color=\"primary\"\n type=\"button\"\n [disabled]=\"saveDisabled\"\n [matTooltip]=\"isMac ? 'cmd+s' : 'ctrl+s'\"\n (click)=\"save.emit()\">\n Save\n </button>\n\n @if (previewPath !== null) {\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"preview.emit()\">\n Preview\n </button>\n }\n\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"done.emit()\">\n Done\n </button>\n</div>\n", styles: [":host{display:flex;align-items:center}.actions{display:flex;gap:5px;margin-left:30px}\n"], dependencies: [{ kind: "component", type: MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
404
401
  }
405
402
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: EditorActionsComponent, decorators: [{
406
403
  type: Component,
407
404
  args: [{ selector: 'app-editor-actions', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [
405
+ MatButton,
406
+ MatIconButton,
408
407
  MatIcon,
409
- ], template: "<button\n type=\"button\"\n class=\"icon-button\"\n (click)=\"settings.emit()\">\n <mat-icon>settings</mat-icon>\n</button>\n\n<div class=\"actions\">\n <button\n type=\"button\"\n class=\"action-button\"\n [disabled]=\"saveDisabled\"\n [title]=\"isMac ? 'cmd+s' : 'ctrl+s'\"\n (click)=\"save.emit()\">\n Save\n </button>\n\n @if (previewPath !== null) {\n <a\n class=\"action-button\"\n target=\"_blank\"\n [href]=\"previewPath\">\n Preview\n </a>\n }\n\n <button\n type=\"button\"\n class=\"action-button\"\n (click)=\"done.emit()\">\n Done\n </button>\n</div>\n", styles: [":host{display:flex;align-items:center}.icon-button{display:inline-flex;align-items:center;justify-content:center;width:40px;height:40px;padding:0;border:none;border-radius:50%;background:transparent;cursor:pointer;color:inherit}.icon-button:hover{background:#0000000a}.actions{display:flex;gap:5px;margin-left:30px}.action-button{display:inline-flex;align-items:center;justify-content:center;height:36px;padding:0 16px;border:1px solid rgba(0,0,0,.12);border-radius:4px;background:transparent;font:inherit;font-weight:500;color:inherit;text-decoration:none;cursor:pointer;white-space:nowrap}.action-button:hover:not([disabled]){background:#0000000a}.action-button[disabled]{opacity:.38;cursor:default}\n"] }]
408
+ MatTooltip,
409
+ ], template: "<button\n type=\"button\"\n mat-icon-button\n (click)=\"settings.emit()\">\n <mat-icon>settings</mat-icon>\n</button>\n\n<div class=\"actions\">\n <button\n mat-stroked-button\n color=\"primary\"\n type=\"button\"\n [disabled]=\"saveDisabled\"\n [matTooltip]=\"isMac ? 'cmd+s' : 'ctrl+s'\"\n (click)=\"save.emit()\">\n Save\n </button>\n\n @if (previewPath !== null) {\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"preview.emit()\">\n Preview\n </button>\n }\n\n <button\n mat-stroked-button\n type=\"button\"\n (click)=\"done.emit()\">\n Done\n </button>\n</div>\n", styles: [":host{display:flex;align-items:center}.actions{display:flex;gap:5px;margin-left:30px}\n"] }]
410
410
  }], propDecorators: { saveDisabled: [{
411
411
  type: Input
412
412
  }], isMac: [{
@@ -419,44 +419,34 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImpor
419
419
  type: Output
420
420
  }], done: [{
421
421
  type: Output
422
+ }], preview: [{
423
+ type: Output
422
424
  }] } });
423
425
 
424
426
  class ContentLayoutComponent {
427
+ contentLayout = signal(null);
425
428
  _config = inject(FS_CONTENT_CONFIG);
426
429
  _data = inject(MAT_DIALOG_DATA);
427
430
  _dialogRef = inject(MatDialogRef);
428
431
  _message = inject(FsMessage);
429
- _cdRef = inject(ChangeDetectorRef);
430
- textEditors;
431
- contentLayout = null;
432
- editors = { content: true, styles: true };
433
- _destroy$ = new Subject();
434
- ngOnInit() {
435
- this._fetchData();
436
- }
437
- ngOnDestroy() {
438
- this._destroy$.next(null);
439
- this._destroy$.complete();
432
+ constructor() {
433
+ this._init();
440
434
  }
441
435
  save = () => {
442
- 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 })
443
440
  .pipe(tap((contentLayout) => {
444
441
  this._message.success('Saved Changes');
445
442
  this._dialogRef.close(contentLayout);
446
443
  }));
447
444
  };
448
- _fetchData() {
449
- of(this._data.contentLayout)
450
- .pipe(switchMap((contentLayout) => {
451
- return of(contentLayout);
452
- }), takeUntil(this._destroy$))
453
- .subscribe((contentLayout) => {
454
- this.contentLayout = { ...contentLayout };
455
- this._cdRef.markForCheck();
456
- });
445
+ _init() {
446
+ this.contentLayout.set({ ...this._data.contentLayout });
457
447
  }
458
448
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: ContentLayoutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
459
- 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 });
460
450
  }
461
451
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: ContentLayoutComponent, decorators: [{
462
452
  type: Component,
@@ -472,102 +462,47 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImpor
472
462
  MatLabel,
473
463
  MatInput,
474
464
  MatDialogActions,
475
- ], 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" }]
476
- }], propDecorators: { textEditors: [{
477
- type: ViewChildren,
478
- args: [FsTextEditorComponent]
479
- }] } });
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: () => [] });
480
467
 
481
468
  class ContentLayoutEditorComponent {
482
- editor;
483
- contentLayout;
469
+ editor = viewChild(EditorComponent);
470
+ contentLayout = signal(null);
484
471
  get isMac() {
485
472
  return navigator.platform.toUpperCase().indexOf('MAC') >= 0;
486
473
  }
487
- submitting;
488
474
  config;
489
475
  EditorType = EditorType;
490
- focused = null;
491
- title;
492
- editors = {
476
+ editors = signal({
493
477
  [EditorType.Html]: true,
494
478
  [EditorType.Scss]: true,
495
479
  [EditorType.GlobalScss]: false,
496
- };
497
- _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 }));
498
485
  _data = inject(MAT_DIALOG_DATA);
499
486
  _dialogRef = inject(MatDialogRef);
500
- _message = inject(FsMessage);
501
487
  _dialog = inject(MatDialog);
502
- _cdRef = inject(ChangeDetectorRef);
503
488
  _prompt = inject(FsPrompt);
489
+ _destroyRef = inject(DestroyRef);
504
490
  ngOnInit() {
505
491
  this._dialogRef.addPanelClass('fs-content-editor-overlay-pane');
506
492
  this._dialogRef.disableClose = true;
507
493
  this.config = this._data.contentConfig;
508
- this._initContentLayout(this._data.contentLayout);
494
+ this._initContentLayout();
509
495
  this._initEscape();
510
496
  }
511
497
  editorToggleChange(event) {
512
- this.editors[event.value] = !this.editors[event.value];
513
- }
514
- ngOnDestroy() {
515
- this._destroy$.next(null);
516
- this._destroy$.complete();
517
- }
518
- _initContentLayout(contentLayout) {
519
- this.config.loadContentLayout(contentLayout.id)
520
- .subscribe((data) => {
521
- this.contentLayout = data;
522
- this._cdRef.markForCheck();
523
- });
524
- }
525
- editorFocused(type) {
526
- this.focused = type;
498
+ this.editors.update((editors) => ({ ...editors, [event.value]: !editors[event.value] }));
527
499
  }
528
500
  save() {
529
501
  this.submitted()
530
502
  .subscribe();
531
503
  }
532
- submitted = () => {
533
- this.submitting = true;
534
- return of(null)
535
- .pipe(filter(() => this.focused), switchMap(() => {
536
- switch (this.focused) {
537
- case EditorType.Html:
538
- case EditorType.Scss:
539
- return this.saveContentPage();
540
- case EditorType.GlobalScss:
541
- return this.editor.saveGlobalScss();
542
- }
543
- return throwError('Invalid focus');
544
- }), tap(() => {
545
- this.editor.clearChange(this.focused);
546
- this._cdRef.markForCheck();
547
- }), finalize(() => {
548
- this.submitting = false;
549
- this._cdRef.markForCheck();
550
- }));
551
- };
552
- saveContentPage() {
553
- const names = {
554
- [EditorType.Scss]: 'styles',
555
- [EditorType.Html]: 'content',
556
- };
557
- const data = {
558
- id: this.contentLayout.id,
559
- [names[this.focused]]: this.editor.changes[this.focused],
560
- };
561
- return this.config.saveContentLayout({
562
- id: this.contentLayout.id,
563
- ...data,
564
- })
565
- .pipe(tap(() => {
566
- this._message.success('Saved Changes');
567
- }));
568
- }
569
504
  close() {
570
- if (!this.editor.hasChanges) {
505
+ if (!this.editor()?.hasChanges()) {
571
506
  return this._dialogRef.close();
572
507
  }
573
508
  this._prompt.confirm({
@@ -587,40 +522,45 @@ class ContentLayoutEditorComponent {
587
522
  },
588
523
  ],
589
524
  })
590
- .pipe(takeUntil(this._destroy$))
591
- .subscribe((value) => {
592
- if (value === 'discard') {
593
- this._dialogRef.close();
594
- }
595
- });
525
+ .pipe(filter((value) => value === 'discard'), tap(() => this._dialogRef.close()), takeUntilDestroyed(this._destroyRef))
526
+ .subscribe();
596
527
  }
597
528
  openSettings() {
598
529
  this._dialog.open(ContentLayoutComponent, {
599
530
  data: {
600
- contentLayout: this.contentLayout,
531
+ contentLayout: this.contentLayout(),
601
532
  },
602
533
  })
603
534
  .afterClosed()
604
- .pipe(takeUntil(this._destroy$))
605
- .subscribe((contentLayout) => {
606
- this.contentLayout = {
607
- ...this.contentLayout,
608
- ...contentLayout,
609
- };
610
- this._cdRef.markForCheck();
611
- });
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();
612
551
  }
613
552
  _initEscape() {
614
553
  fromEvent(document, 'keydown')
615
- .pipe(filter((event) => event.code === 'Escape'), takeUntil(this._destroy$)).subscribe(() => {
554
+ .pipe(filter((event) => event.code === 'Escape'), tap(() => {
616
555
  const dialogRef = this._dialog.openDialogs.reverse()[0];
617
556
  if (dialogRef?.componentInstance === this) {
618
557
  this.close();
619
558
  }
620
- });
559
+ }), takeUntilDestroyed(this._destroyRef))
560
+ .subscribe();
621
561
  }
622
562
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: ContentLayoutEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
623
- 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"] }, { 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 });
624
564
  }
625
565
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: ContentLayoutEditorComponent, decorators: [{
626
566
  type: Component,
@@ -635,11 +575,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImpor
635
575
  CdkScrollable,
636
576
  MatDialogContent,
637
577
  EditorComponent,
638
- ], 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"] }]
639
- }], propDecorators: { editor: [{
640
- type: ViewChild,
641
- args: [EditorComponent]
642
- }] } });
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
+ }] });
643
580
 
644
581
  class FsContentLayoutsComponent {
645
582
  _config = inject(FS_CONTENT_CONFIG);
@@ -778,52 +715,45 @@ const PageTypes = [
778
715
  ];
779
716
 
780
717
  class ContentPageComponent {
718
+ contentPage = signal(null);
719
+ contentLayouts = signal(null);
720
+ PageTypes = PageTypes;
781
721
  _config = inject(FS_CONTENT_CONFIG);
782
722
  _data = inject(MAT_DIALOG_DATA);
783
723
  _dialogRef = inject(MatDialogRef);
784
724
  _message = inject(FsMessage);
785
- _cdRef = inject(ChangeDetectorRef);
786
- textEditors;
787
- contentPage = null;
788
- PageTypes = PageTypes;
789
- contentLayouts;
790
- editors = { content: true, styles: true };
791
- _destroy$ = new Subject();
725
+ _destroyRef = inject(DestroyRef);
726
+ constructor() {
727
+ this._init();
728
+ }
792
729
  ngOnInit() {
793
730
  this._dialogRef.updateSize('600px');
794
- this._fetchData();
795
- }
796
- ngOnDestroy() {
797
- this._destroy$.next(null);
798
- this._destroy$.complete();
799
731
  }
800
732
  save = () => {
801
- return this._config.saveContentPage(this.contentPage)
733
+ // Only the fields this dialog edits. Sending the whole page re-sent the Page Editor's copy
734
+ // of content, styles and js, which the server wrote over edits saved since (IEB-T292).
735
+ const { id, type, contentLayoutId, name, path, title } = this.contentPage();
736
+ return this._config.saveContentPage({ id, type, contentLayoutId, name, path, title })
802
737
  .pipe(tap((contentPage) => {
803
738
  this._message.success('Saved Changes');
804
739
  this._dialogRef.close(contentPage);
805
740
  }));
806
741
  };
807
- _fetchData() {
808
- this._config.loadContentLayouts()
809
- .subscribe((contentLayouts) => {
810
- this.contentLayouts = contentLayouts;
811
- this._cdRef.markForCheck();
812
- });
813
- of(this._data.contentPage)
814
- .pipe(switchMap((contentPage) => {
815
- return of(contentPage);
816
- }), takeUntil(this._destroy$))
817
- .subscribe((contentPage) => {
818
- this.contentPage = {
819
- ...contentPage,
820
- path: contentPage.path || '/',
821
- };
822
- this._cdRef.markForCheck();
742
+ _init() {
743
+ this.contentPage.set({
744
+ ...this._data.contentPage,
745
+ path: this._data.contentPage.path || '/',
823
746
  });
747
+ this._config.loadContentLayouts()
748
+ .pipe(
749
+ // The config answers with the { contentLayouts, paging } envelope
750
+ // (FsContentConfig.loadContentLayouts), so the select takes its array, not the
751
+ // envelope, which rendered no options at all (IEB-T294).
752
+ tap((response) => this.contentLayouts.set(response.contentLayouts)), takeUntilDestroyed(this._destroyRef))
753
+ .subscribe();
824
754
  }
825
755
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: ContentPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
826
- 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 });
756
+ 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 <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.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 <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.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 });
827
757
  }
828
758
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: ContentPageComponent, decorators: [{
829
759
  type: Component,
@@ -841,106 +771,48 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImpor
841
771
  MatOption,
842
772
  MatInput,
843
773
  MatDialogActions,
844
- ], 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" }]
845
- }], propDecorators: { textEditors: [{
846
- type: ViewChildren,
847
- args: [FsTextEditorComponent]
848
- }] } });
774
+ ], 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.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 <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" }]
775
+ }], ctorParameters: () => [] });
849
776
 
850
777
  class ContentPageEditorComponent {
851
- _data = inject(MAT_DIALOG_DATA);
852
- _dialogRef = inject(MatDialogRef);
853
- _message = inject(FsMessage);
854
- _dialog = inject(MatDialog);
855
- _cdRef = inject(ChangeDetectorRef);
856
- _prompt = inject(FsPrompt);
857
- editor;
858
- contentPage;
859
- focused = null;
778
+ editor = viewChild(EditorComponent);
779
+ contentPage = signal(null);
860
780
  config;
861
- resizing = false;
862
- title;
863
- editors = {
781
+ editors = signal({
864
782
  [EditorType.Html]: true,
865
783
  [EditorType.Scss]: true,
866
784
  [EditorType.Js]: false,
867
785
  [EditorType.GlobalScss]: false,
868
- };
786
+ });
869
787
  get isMac() {
870
788
  return navigator.platform.toUpperCase().indexOf('MAC') >= 0;
871
789
  }
872
- submitting;
873
- _destroy$ = new Subject();
790
+ submitted = () => this.editor().save$({
791
+ [EditorType.Html]: 'content',
792
+ [EditorType.Scss]: 'styles',
793
+ [EditorType.Js]: 'js',
794
+ }, (values) => this.config.saveContentPage({ id: this.contentPage().id, ...values }));
795
+ _data = inject(MAT_DIALOG_DATA);
796
+ _dialogRef = inject(MatDialogRef);
797
+ _dialog = inject(MatDialog);
798
+ _prompt = inject(FsPrompt);
799
+ _destroyRef = inject(DestroyRef);
874
800
  ngOnInit() {
875
801
  this._dialogRef.addPanelClass('fs-content-editor-overlay-pane');
876
802
  this._dialogRef.disableClose = true;
877
803
  this.config = this._data.contentConfig;
878
- this._initContentPage(this._data.contentPage);
804
+ this._initContentPage();
879
805
  this._initEscape();
880
806
  }
881
807
  editorToggleChange(event) {
882
- this.editors[event.value] = !this.editors[event.value];
883
- }
884
- ngOnDestroy() {
885
- this._destroy$.next(null);
886
- this._destroy$.complete();
887
- }
888
- _initContentPage(contentPage) {
889
- this.config.loadContentPage(contentPage.id)
890
- .subscribe((data) => {
891
- this.contentPage = data;
892
- this._cdRef.markForCheck();
893
- });
894
- }
895
- editorFocused(type) {
896
- this.focused = type;
808
+ this.editors.update((editors) => ({ ...editors, [event.value]: !editors[event.value] }));
897
809
  }
898
810
  save() {
899
811
  this.submitted()
900
812
  .subscribe();
901
813
  }
902
- submitted = () => {
903
- this.submitting = true;
904
- return of(null)
905
- .pipe(filter(() => this.focused), switchMap(() => {
906
- switch (this.focused) {
907
- case EditorType.Js:
908
- case EditorType.Html:
909
- case EditorType.Scss:
910
- return this.saveContentPage();
911
- case EditorType.GlobalScss:
912
- return this.editor.saveGlobalScss();
913
- }
914
- return throwError('Invalid focus');
915
- }), tap(() => {
916
- this.editor.clearChange(this.focused);
917
- this._cdRef.markForCheck();
918
- }), finalize(() => {
919
- this.submitting = false;
920
- this._cdRef.markForCheck();
921
- }))
922
- .pipe(takeUntil(this._destroy$));
923
- };
924
- saveContentPage() {
925
- const names = {
926
- [EditorType.Js]: 'js',
927
- [EditorType.Scss]: 'styles',
928
- [EditorType.Html]: 'content',
929
- };
930
- const data = {
931
- id: this.contentPage.id,
932
- [names[this.focused]]: this.editor.changes[this.focused],
933
- };
934
- return this.config.saveContentPage({
935
- id: this.contentPage.id,
936
- ...data,
937
- })
938
- .pipe(tap(() => {
939
- this._message.success('Saved Changes');
940
- }));
941
- }
942
814
  close() {
943
- if (!this.editor.hasChanges) {
815
+ if (!this.editor()?.hasChanges()) {
944
816
  return this._dialogRef.close();
945
817
  }
946
818
  this._prompt.confirm({
@@ -960,40 +832,52 @@ class ContentPageEditorComponent {
960
832
  },
961
833
  ],
962
834
  })
963
- .pipe(takeUntil(this._destroy$))
964
- .subscribe((value) => {
965
- if (value === 'discard') {
966
- this._dialogRef.close();
967
- }
968
- });
835
+ .pipe(filter((value) => value === 'discard'), tap(() => this._dialogRef.close()), takeUntilDestroyed(this._destroyRef))
836
+ .subscribe();
837
+ }
838
+ openPreview() {
839
+ window.open(this.contentPage().path, '_blank');
969
840
  }
970
841
  openSettings() {
971
842
  this._dialog.open(ContentPageComponent, {
972
843
  data: {
973
- contentPage: this.contentPage,
844
+ contentPage: this.contentPage(),
974
845
  },
975
846
  })
976
847
  .afterClosed()
977
- .pipe(takeUntil(this._destroy$))
978
- .subscribe((contentPage) => {
979
- this.contentPage = {
980
- ...this.contentPage,
981
- ...contentPage,
982
- };
983
- this._cdRef.markForCheck();
984
- });
848
+ .pipe(filter((contentPage) => !!contentPage),
849
+ // Only what the settings dialog edits. Merging content/styles/js from its response
850
+ // would push them through [html]/[scss]/[js] into the editors and wipe unsaved typing
851
+ // (IEB-T292). The path is merged so Preview follows a renamed page.
852
+ tap((contentPage) => {
853
+ this.contentPage.update((current) => ({
854
+ ...current,
855
+ type: contentPage.type,
856
+ contentLayoutId: contentPage.contentLayoutId,
857
+ name: contentPage.name,
858
+ path: contentPage.path,
859
+ title: contentPage.title,
860
+ }));
861
+ }), takeUntilDestroyed(this._destroyRef))
862
+ .subscribe();
863
+ }
864
+ _initContentPage() {
865
+ this.config.loadContentPage(this._data.contentPage.id)
866
+ .pipe(tap((contentPage) => this.contentPage.set(contentPage)), takeUntilDestroyed(this._destroyRef))
867
+ .subscribe();
985
868
  }
986
869
  _initEscape() {
987
870
  fromEvent(document, 'keydown')
988
- .pipe(filter((event) => event.code === 'Escape'), takeUntil(this._destroy$)).subscribe(() => {
871
+ .pipe(filter((event) => event.code === 'Escape'), tap(() => {
989
872
  const dialogRef = this._dialog.openDialogs.reverse()[0];
990
873
  if (dialogRef?.componentInstance === this) {
991
874
  this.close();
992
875
  }
993
- });
876
+ }), takeUntilDestroyed(this._destroyRef))
877
+ .subscribe();
994
878
  }
995
879
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: ContentPageEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
996
- 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 (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"] }, { kind: "component", type: EditorComponent, selector: "app-editor", inputs: ["showHtml", "showScss", "showJs", "showGlobalScss", "html", "scss", "js", "contentConfig"], outputs: ["changed", "focused", "blured"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
880
+ 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 });
997
881
  }
998
882
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: ContentPageEditorComponent, decorators: [{
999
883
  type: Component,
@@ -1008,11 +892,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImpor
1008
892
  CdkScrollable,
1009
893
  MatDialogContent,
1010
894
  EditorComponent,
1011
- ], 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 (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"] }]
1012
- }], propDecorators: { editor: [{
1013
- type: ViewChild,
1014
- args: [EditorComponent]
1015
- }] } });
895
+ ], 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"] }]
896
+ }] });
1016
897
 
1017
898
  class FsContentPagesComponent {
1018
899
  _config = inject(FS_CONTENT_CONFIG);
@@ -1113,7 +994,7 @@ class FsContentPagesComponent {
1113
994
  };
1114
995
  }
1115
996
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: FsContentPagesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1116
- 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 });
997
+ 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 });
1117
998
  }
1118
999
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: FsContentPagesComponent, decorators: [{
1119
1000
  type: Component,
@@ -1121,7 +1002,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImpor
1121
1002
  FsListModule,
1122
1003
  NgTemplateOutlet,
1123
1004
  FsDateModule,
1124
- ], 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"] }]
1005
+ ], 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"] }]
1125
1006
  }], propDecorators: { htmlEditorConfig: [{
1126
1007
  type: Input
1127
1008
  }], listComponent: [{
@@ -1195,7 +1076,7 @@ class ContentStyleComponent {
1195
1076
  })).subscribe();
1196
1077
  }
1197
1078
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: ContentStyleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1198
- 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 });
1079
+ 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 });
1199
1080
  }
1200
1081
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: ContentStyleComponent, decorators: [{
1201
1082
  type: Component,