From 2b76e29f48b8fbc012179c732840fb6c4efdf046 Mon Sep 17 00:00:00 2001 From: chadwickmeyer Date: Fri, 27 Mar 2026 18:24:03 +0100 Subject: [PATCH] Update @stratusjs/boot package to v1.2.1 - add cacheTime busting on stratus bundle loading - IMPORTANT! don't forget to npm publish - ToDo: npm publish packages: angular, angularjs, stratusjs --- packages/angular/package.json | 2 +- .../angular/src/editor/editor.component.ts | 112 +++++++++++++++++- packages/angularjs/package.json | 2 +- packages/angularjs/src/services/model.ts | 98 ++++++++++++++- 4 files changed, 204 insertions(+), 10 deletions(-) diff --git a/packages/angular/package.json b/packages/angular/package.json index d3dff6d1..7fc710c5 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -1,6 +1,6 @@ { "name": "@stratusjs/angular", - "version": "0.13.2", + "version": "0.13.3", "description": "This is the angular package for StratusJS.", "scripts": {}, "repository": { diff --git a/packages/angular/src/editor/editor.component.ts b/packages/angular/src/editor/editor.component.ts index e968f580..71802b0c 100644 --- a/packages/angular/src/editor/editor.component.ts +++ b/packages/angular/src/editor/editor.component.ts @@ -4,6 +4,7 @@ import { ChangeDetectorRef, Component, ElementRef, + HostBinding, Input, OnInit, } from '@angular/core' @@ -246,6 +247,24 @@ export class EditorComponent extends RootComponent implements OnInit, TriggerInt @Input() property: string @Input() classes: object + // Layout Attributes + @Input() compact = false + fullscreenActive = false + + get isCompact(): boolean { + return !!this.compact && !this.fullscreenActive + } + // Add if compact + @HostBinding('class.editor-compact') + get hostCompact(): boolean { + return this.isCompact + } + + @HostBinding('class.editor-fullscreen') + get hostFullscreen(): boolean { + return this.fullscreenActive + } + // Dependencies _ = _ has = has @@ -460,9 +479,9 @@ export class EditorComponent extends RootComponent implements OnInit, TriggerInt moreMisc: [ // 'menuButton', 'html', + 'fullscreen', 'undo', 'redo', - 'fullscreen', 'print', // FIXME: This plugin doesn't detect the window.html2pdf, likely due to timing issues // 'getPDF', @@ -488,6 +507,16 @@ export class EditorComponent extends RootComponent implements OnInit, TriggerInt blur: (event: FocusEvent) => this.onBlur(event), // FIXME: Froala doesn't support focus events, so this never fires... focus: (event: FocusEvent) => this.onFocus(event), + fullscreen: () => { + const editor = (this.froalaEditorDirective as any).getEditor() + this.fullscreenActive = !!editor.fullscreen.isActive() + + setTimeout(() => { + editor.size.refresh() + editor.position.refresh() + this.refresh() + }, 30) + }, // bound to Froala Editor // contentChanged () { // const editor = this @@ -1038,7 +1067,7 @@ export class EditorComponent extends RootComponent implements OnInit, TriggerInt moreMisc: { buttons: this.froalaStandardButtons.moreMisc, align: 'right', - buttonsVisible: 1 + buttonsVisible: 2 } }, // A MD sized screen will show the default toolbarButtons @@ -1065,20 +1094,20 @@ export class EditorComponent extends RootComponent implements OnInit, TriggerInt toolbarButtonsXS: { moreText: { buttons: this.froalaStandardButtons.moreText, - buttonsVisible: 0 + buttonsVisible: 1 }, moreParagraph: { buttons: this.froalaStandardButtons.moreParagraph, - buttonsVisible: 0 + buttonsVisible: 1 }, moreRich: { buttons: this.froalaStandardButtons.moreRich, - buttonsVisible: 0 + buttonsVisible: 1 }, moreMisc: { buttons: this.froalaStandardButtons.moreMisc, align: 'right', - buttonsVisible: 0 + buttonsVisible: 1 } }, // This needs to remain false, or inline styles will be converted to froala classes. @@ -1235,6 +1264,11 @@ export class EditorComponent extends RootComponent implements OnInit, TriggerInt // @ts-ignore this.froalaConfig.inlineClasses = this.classes } + + if (this.compact) { + this.applyCompactToolbar() + } + // console.info(`${moduleName}.ngOnInit`) const dataControl = this.form.get('dataString') // This valueChanges field is an Event Emitter @@ -1270,6 +1304,72 @@ export class EditorComponent extends RootComponent implements OnInit, TriggerInt } } + applyCompactToolbar() { + // this doesn't work anyways in the sidebar + this.froalaConfig.toolbarSticky = false + // Appear above Sidebar Drawer for Live Edit + this.froalaConfig.zIndex = 101 + // This is necessary in order to make the drop down menus appear + this.froalaConfig.scrollableContainer = 'body'; + // Try to make it reset after fullscreen + (this.froalaConfig as any).toolbarResponsiveToEditor = true + const compactButtons = { + moreText: { + buttons: [ + 'bold', + 'italic', + 'underline', + 'strikeThrough', + 'subscript', + 'superscript', + // 'fontFamily', + // 'fontSize', + 'textColor', + 'clearFormatting' + ], + buttonsVisible: 3 + }, + moreParagraph: { + buttons: [ + 'formatUL', + 'formatOL', + 'alignLeft', + 'alignCenter', + 'alignRight', + 'quote' + ], + buttonsVisible: 2 + }, + moreRich: { + buttons: [ + 'linkManager', + 'insertImage', + 'insertVideo', + 'insertTable', + 'citationInsert', + 'insertHR', + 'specialCharacters' + ], + buttonsVisible: 2 + }, + moreMisc: { + buttons: [ + 'html', + 'fullscreen', + 'undo', + 'redo', + 'help' + ], + align: 'right', + buttonsVisible: 2 + } + } + + this.froalaConfig.toolbarButtons = compactButtons + this.froalaConfig.toolbarButtonsSM = compactButtons + this.froalaConfig.toolbarButtonsXS = compactButtons + } + modelSave(value: string) { if (!this.model) { console.warn('There is no model for sa-editor to save to', this) diff --git a/packages/angularjs/package.json b/packages/angularjs/package.json index 3e357cde..b10e74c7 100644 --- a/packages/angularjs/package.json +++ b/packages/angularjs/package.json @@ -1,6 +1,6 @@ { "name": "@stratusjs/angularjs", - "version": "0.11.2", + "version": "0.11.3", "description": "This is the AngularJS package for StratusJS.", "scripts": {}, "repository": { diff --git a/packages/angularjs/src/services/model.ts b/packages/angularjs/src/services/model.ts index 264f4da0..c9f27c45 100644 --- a/packages/angularjs/src/services/model.ts +++ b/packages/angularjs/src/services/model.ts @@ -342,6 +342,88 @@ export class Model extends ModelBase { return sanitizedOptions } + getReadOnlyFields(): string[] { + const fields = this.meta.get('readOnlyFields') + return isArray(fields) ? fields.filter((field: any) => isString(field) && !!field) : [] + } + + deletePath(obj: any, path: string): void { + if (!obj || !isString(path)) { + return + } + + const parts = path.split('.') + const lastKey = parts.pop() + if (!lastKey) { + return + } + + let parent = obj as Record + + for (const part of parts) { + if (!parent || !isObject(parent) || !has(parent, part)) { + parent = null + break + } + parent = parent[part] + } + + if (parent && isObject(parent) && has(parent, lastKey)) { + delete parent[lastKey] + } + } + + pruneEmptyBranches(obj: any): boolean { + if (!isObject(obj) || isArray(obj)) { + return isEmpty(obj) + } + + const target = obj as Record + + for (const key of Object.keys(target)) { + const value = target[key] + + if (isObject(value) && !isArray(value)) { + const emptyChild = this.pruneEmptyBranches(value) + if (emptyChild) { + delete target[key] + } + continue + } + + if (isUndefined(value)) { + delete target[key] + } + } + + return isEmpty(target) + } + + sanitizeReadOnlyPatchPayload(payload: any): any { + if (!isObject(payload)) { + return payload + } + + const sanitized = cloneDeep(payload) + const readOnlyFields = this.getReadOnlyFields() + + if (!readOnlyFields.length) { + return sanitized + } + + forEach(readOnlyFields, (path: string) => { + this.deletePath(sanitized, path) + }) + + this.pruneEmptyBranches(sanitized) + + return sanitized + } + + getSavablePatch(): any { + return this.sanitizeReadOnlyPatchPayload(cloneDeep(this.toPatch())) + } + // Watch for Data Changes async watcher() { // Ensure we only watch once @@ -377,6 +459,13 @@ export class Model extends ModelBase { const isUserChangeSet = isUndefined(changeSet) if (isUserChangeSet) { changeSet = super.handleChanges() + + // Remove client-computed / read-only fields before autosave logic reacts + changeSet = this.sanitizeReadOnlyPatchPayload(changeSet) + + if (!isEmpty(this.patch)) { + this.patch = this.sanitizeReadOnlyPatchPayload(this.patch) + } } // Ensure ChangeSet is valid @@ -491,6 +580,8 @@ export class Model extends ModelBase { // XHR Flags for Collection if (this.collection) { // TODO: Change to a Model ID Register + // In live edit mode when we are editing a model, we don't necessarily want to make this collection be + // considered pending if there are child elements... but there may be contexts in which that is necessary this.collection.pending = true // Dispatch Collection Change Event @@ -790,7 +881,7 @@ export class Model extends ModelBase { return this.doSave(options) } // Sanity Checks for Persisted Entities - if (!this.isNew() && (this.pending || !this.completed || isEmpty(this.toPatch()))) { + if (!this.isNew() && (this.pending || !this.completed || isEmpty(this.getSavablePatch()))) { console.warn( `Blocked attempt to save ${isEmpty(this.toPatch()) ? 'an empty payload' : 'a duplicate XHR'} to a persisted model.` ) @@ -845,7 +936,7 @@ export class Model extends ModelBase { if (this.autoSaveTimeout) { clearTimeout(this.autoSaveTimeout) } - if (this.pending || !this.completed || this.isNew() || isEmpty(this.toPatch())) { + if (this.pending || !this.completed || this.isNew() || isEmpty(this.getSavablePatch())) { return } if (this.autoSaveHalt && !this.autoSave) { @@ -882,6 +973,9 @@ export class Model extends ModelBase { } options.patch = (options.patch && !this.isNew()) let data = super.toJSON(options) + if (options.patch) { + data = this.sanitizeReadOnlyPatchPayload(data) + } const metaData = this.meta.get('api') if (metaData) { data = {