Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/angular/package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
112 changes: 106 additions & 6 deletions packages/angular/src/editor/editor.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
ChangeDetectorRef,
Component,
ElementRef,
HostBinding,
Input,
OnInit,
} from '@angular/core'
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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',
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion packages/angularjs/package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
98 changes: 96 additions & 2 deletions packages/angularjs/src/services/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,88 @@ export class Model<T = LooseObject> extends ModelBase<T> {
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<string, any>

for (const part of parts) {
if (!parent || !isObject(parent) || !has(parent, part)) {
parent = null
break
}
parent = parent[part]
Comment on lines +364 to +368

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Resolve nested path segments when removing read-only fields

The traversal mixes lodash path-aware checks (has(parent, part)) with direct property access (parent = parent[part]), which breaks for bracket-style segments such as items[0]. In those cases the guard passes but parent[part] is undefined, so the read-only field is not removed and can still be sent in patch payloads when array-indexed read-only paths are configured.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the recommended new code block?

}

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<string, any>

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
Expand Down Expand Up @@ -377,6 +459,13 @@ export class Model<T = LooseObject> extends ModelBase<T> {
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)
Comment on lines +463 to +467

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Recompute changed flag after sanitizing read-only patch

After super.handleChanges() runs, this.changed is already derived from the unsanitized patch; if the only differences are read-only fields, lines 463-467 clear changeSet/this.patch but never reset this.changed to false. That leaves the model permanently marked dirty for non-savable edits, so consumers that gate behavior on model.changed (for example the AngularJS Froala directive’s accept() path) still react as if there is a writable change.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the recommended new lines of code please?

}
}

// Ensure ChangeSet is valid
Expand Down Expand Up @@ -491,6 +580,8 @@ export class Model<T = LooseObject> extends ModelBase<T> {
// 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
Expand Down Expand Up @@ -790,7 +881,7 @@ export class Model<T = LooseObject> extends ModelBase<T> {
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.`
)
Expand Down Expand Up @@ -845,7 +936,7 @@ export class Model<T = LooseObject> extends ModelBase<T> {
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) {
Expand Down Expand Up @@ -882,6 +973,9 @@ export class Model<T = LooseObject> extends ModelBase<T> {
}
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 = {
Expand Down
Loading