@praxisui/rich-content 8.0.0-beta.11 → 8.0.0-beta.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -3
- package/fesm2022/praxisui-rich-content.mjs +158 -36
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -109,13 +109,21 @@ through `PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST`.
|
|
|
109
109
|
|
|
110
110
|
The manifest treats rich content as structured `RichContentDocument` data, not
|
|
111
111
|
HTML or markdown patches. It governs document replacement, block add/remove,
|
|
112
|
-
block order, text updates, canonical link nodes,
|
|
113
|
-
|
|
114
|
-
|
|
112
|
+
block order, text updates, canonical link nodes, common node metadata,
|
|
113
|
+
`mediaBlock` fields, `timeline.items[]`, preset refs and sanitization policy.
|
|
114
|
+
Security-sensitive policy edits and destructive removals require confirmation
|
|
115
|
+
before a patch can be compiled.
|
|
115
116
|
|
|
116
117
|
Link authoring uses a first-class `link` node with `label`, `href`, `target`
|
|
117
118
|
and `rel`; unsafe protocols are rejected by `validateRichContentDocument()`.
|
|
118
119
|
|
|
120
|
+
Each operation declares its own editable target resolver, ambiguity policy,
|
|
121
|
+
preconditions, validators, affected paths, effects and typed submission impact.
|
|
122
|
+
Document, block, text, link, media, timeline, preset, metadata and sanitization
|
|
123
|
+
operations are `config-only` because they edit the structured widget input
|
|
124
|
+
contract. `display.mode.set` is `visual-only` because it changes `layout` and
|
|
125
|
+
`rootClassName` without mutating the `RichContentDocument`.
|
|
126
|
+
|
|
119
127
|
## Layout Modes
|
|
120
128
|
|
|
121
129
|
The component defaults to `layout="block"` to preserve document-style rendering.
|
|
@@ -3982,6 +3982,8 @@ const RICH_CONTENT_AI_CAPABILITIES = {
|
|
|
3982
3982
|
{ path: 'document.nodes[].icon', category: 'richNode', valueKind: 'string', description: 'Nome do icone Material Symbols para node icon ou badge.' },
|
|
3983
3983
|
{ path: 'document.nodes[].label', category: 'richNode', valueKind: 'string', description: 'Label literal para badge, metric ou progress.' },
|
|
3984
3984
|
{ path: 'document.nodes[].labelExpr', category: 'richNode', valueKind: 'string', description: 'Path simples resolvido contra o contexto para label.' },
|
|
3985
|
+
{ path: 'document.nodes[].title', category: 'richNode', valueKind: 'string', description: 'Titulo literal para card, mediaBlock ou timeline.' },
|
|
3986
|
+
{ path: 'document.nodes[].subtitle', category: 'richNode', valueKind: 'string', description: 'Subtitulo literal para card ou mediaBlock.' },
|
|
3985
3987
|
{ path: 'document.nodes[].src', category: 'richNode', valueKind: 'string', description: 'URL literal para image.' },
|
|
3986
3988
|
{ path: 'document.nodes[].srcExpr', category: 'richNode', valueKind: 'string', description: 'Path simples resolvido contra o contexto para image src.' },
|
|
3987
3989
|
{ path: 'document.nodes[].alt', category: 'richNode', valueKind: 'string', description: 'Texto alternativo literal para image.' },
|
|
@@ -3992,7 +3994,12 @@ const RICH_CONTENT_AI_CAPABILITIES = {
|
|
|
3992
3994
|
{ path: 'document.nodes[].nameExpr', category: 'richNode', valueKind: 'string', description: 'Path simples resolvido contra o contexto para avatar.' },
|
|
3993
3995
|
{ path: 'document.nodes[].valueExpr', category: 'richNode', valueKind: 'string', description: 'Path simples resolvido contra o contexto para metric/progress.' },
|
|
3994
3996
|
{ path: 'document.nodes[].items', category: 'richNode', valueKind: 'array', description: 'Itens internos de compose.' },
|
|
3997
|
+
{ path: 'document.nodes[].items[].title', category: 'richNode', valueKind: 'string', description: 'Titulo de item de timeline quando document.nodes[].type for timeline.' },
|
|
3998
|
+
{ path: 'document.nodes[].items[].subtitle', category: 'richNode', valueKind: 'string', description: 'Subtitulo de item de timeline quando document.nodes[].type for timeline.' },
|
|
3999
|
+
{ path: 'document.nodes[].items[].badge', category: 'richNode', valueKind: 'string', description: 'Badge de item de timeline quando document.nodes[].type for timeline.' },
|
|
4000
|
+
{ path: 'document.nodes[].items[].icon', category: 'richNode', valueKind: 'string', description: 'Icone de item de timeline quando document.nodes[].type for timeline.' },
|
|
3995
4001
|
{ path: 'document.nodes[].content', category: 'richNode', valueKind: 'array', description: 'Conteudo interno de card.' },
|
|
4002
|
+
{ path: 'document.nodes[].avatar', category: 'richNode', valueKind: 'object', description: 'Avatar estruturado de mediaBlock.' },
|
|
3996
4003
|
{ path: 'document.nodes[].trailing', category: 'richNode', valueKind: 'array', description: 'Conteudo trailing de mediaBlock.' },
|
|
3997
4004
|
{ path: 'document.nodes[].ref', category: 'richNode', valueKind: 'object', description: 'Referencia registravel de preset rich-block.' },
|
|
3998
4005
|
{ path: 'context', category: 'richContent', valueKind: 'object', description: 'Contexto externo injetado pelo host ou page-builder.' },
|
|
@@ -4002,23 +4009,6 @@ const RICH_CONTENT_AI_CAPABILITIES = {
|
|
|
4002
4009
|
],
|
|
4003
4010
|
};
|
|
4004
4011
|
|
|
4005
|
-
const nodeTypeEnum = [
|
|
4006
|
-
'text',
|
|
4007
|
-
'icon',
|
|
4008
|
-
'image',
|
|
4009
|
-
'link',
|
|
4010
|
-
'badge',
|
|
4011
|
-
'avatar',
|
|
4012
|
-
'metric',
|
|
4013
|
-
'progress',
|
|
4014
|
-
'compose',
|
|
4015
|
-
'card',
|
|
4016
|
-
'mediaBlock',
|
|
4017
|
-
'timeline',
|
|
4018
|
-
'preset',
|
|
4019
|
-
];
|
|
4020
|
-
const layoutEnum = ['block', 'inline'];
|
|
4021
|
-
const linkTargetEnum = ['_self', '_blank'];
|
|
4022
4012
|
const richContentDocumentSchema = {
|
|
4023
4013
|
type: 'object',
|
|
4024
4014
|
required: ['kind', 'version', 'nodes'],
|
|
@@ -4043,7 +4033,7 @@ const blockPatchSchema = {
|
|
|
4043
4033
|
label: { type: 'string' },
|
|
4044
4034
|
labelExpr: { type: 'string' },
|
|
4045
4035
|
href: { type: 'string' },
|
|
4046
|
-
target: { enum:
|
|
4036
|
+
target: { enum: ['_self', '_blank'] },
|
|
4047
4037
|
rel: { type: 'string' },
|
|
4048
4038
|
src: { type: 'string' },
|
|
4049
4039
|
alt: { type: 'string' },
|
|
@@ -4062,7 +4052,7 @@ const PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST = {
|
|
|
4062
4052
|
{ name: 'nodes', type: 'RichBlockNode[] | null', description: 'Direct node list used only when the host controls the document envelope.' },
|
|
4063
4053
|
{ name: 'context', type: 'JsonLogicDataRecord | null', description: 'External context used by expression paths and Json Logic rules.' },
|
|
4064
4054
|
{ name: 'hostCapabilities', type: 'RichBlockHostCapabilities | null', description: 'Host-mediated preset, action, data and embed capabilities; functions are not serialized in public contracts.' },
|
|
4065
|
-
{ name: 'layout', type: "'block' | 'inline'", allowedValues:
|
|
4055
|
+
{ name: 'layout', type: "'block' | 'inline'", allowedValues: ['block', 'inline'], description: 'Renderer layout mode.' },
|
|
4066
4056
|
{ name: 'rootClassName', type: 'string', description: 'CSS class applied to the renderer root.' },
|
|
4067
4057
|
],
|
|
4068
4058
|
editableTargets: [
|
|
@@ -4071,6 +4061,8 @@ const PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST = {
|
|
|
4071
4061
|
{ kind: 'text', resolver: 'rich-text-node-by-id-or-path', description: 'Text-bearing nodes, including text, badge, metric, progress, card and timeline labels.' },
|
|
4072
4062
|
{ kind: 'link', resolver: 'rich-link-node-by-id-or-path', description: 'Canonical link node with label, href, target and rel.' },
|
|
4073
4063
|
{ kind: 'media', resolver: 'rich-media-node-by-id-or-path', description: 'Image, avatar image and mediaBlock child nodes.' },
|
|
4064
|
+
{ kind: 'timelineItem', resolver: 'rich-timeline-item-by-block-id-and-item-id', description: 'Timeline items nested inside timeline nodes; stable item id is preferred over index.' },
|
|
4065
|
+
{ kind: 'metadata', resolver: 'rich-block-common-metadata', description: 'Common node metadata such as testId, className, visibleWhen and safe style.' },
|
|
4074
4066
|
{ kind: 'preset', resolver: 'rich-block-preset-ref', description: 'Preset nodes resolved through PRAXIS_RICH_BLOCK_PRESETS or hostCapabilities.resolvePreset.' },
|
|
4075
4067
|
{ kind: 'sanitizationPolicy', resolver: 'rich-content-validation-policy', description: 'Document validation policy enforced by validateRichContentDocument.' },
|
|
4076
4068
|
{ kind: 'display', resolver: 'rich-content-layout-and-root-class', description: 'Layout and rootClassName runtime inputs.' },
|
|
@@ -4084,9 +4076,11 @@ const PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST = {
|
|
|
4084
4076
|
target: { kind: 'document', resolver: 'rich-content-document-root', ambiguityPolicy: 'fail', required: false },
|
|
4085
4077
|
inputSchema: richContentDocumentSchema,
|
|
4086
4078
|
effects: [{ kind: 'set-value', path: 'document' }],
|
|
4079
|
+
destructive: false,
|
|
4080
|
+
requiresConfirmation: false,
|
|
4087
4081
|
validators: ['document-shape-canonical', 'document-version-supported', 'node-types-supported', 'unsafe-url-rejected', 'unsafe-style-rejected', 'editor-runtime-round-trip'],
|
|
4088
4082
|
affectedPaths: ['document'],
|
|
4089
|
-
submissionImpact:
|
|
4083
|
+
submissionImpact: 'config-only',
|
|
4090
4084
|
preconditions: ['config-initialized'],
|
|
4091
4085
|
},
|
|
4092
4086
|
{
|
|
@@ -4099,7 +4093,7 @@ const PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST = {
|
|
|
4099
4093
|
type: 'object',
|
|
4100
4094
|
required: ['type'],
|
|
4101
4095
|
properties: {
|
|
4102
|
-
type: { enum:
|
|
4096
|
+
type: { enum: ['text', 'icon', 'image', 'link', 'badge', 'avatar', 'metric', 'progress', 'compose', 'card', 'mediaBlock', 'timeline', 'preset'] },
|
|
4103
4097
|
node: { type: 'object' },
|
|
4104
4098
|
afterBlockId: { type: 'string' },
|
|
4105
4099
|
beforeBlockId: { type: 'string' },
|
|
@@ -4109,13 +4103,15 @@ const PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST = {
|
|
|
4109
4103
|
reads: ['document.nodes[]'],
|
|
4110
4104
|
writes: ['document.nodes[]'],
|
|
4111
4105
|
identityKeys: ['document.nodes[].id'],
|
|
4112
|
-
inputSchema: { type: 'object', required: ['type'], properties: { type: { enum:
|
|
4106
|
+
inputSchema: { type: 'object', required: ['type'], properties: { type: { enum: ['text', 'icon', 'image', 'link', 'badge', 'avatar', 'metric', 'progress', 'compose', 'card', 'mediaBlock', 'timeline', 'preset'] }, node: { type: 'object' }, afterBlockId: { type: 'string' }, beforeBlockId: { type: 'string' } } },
|
|
4113
4107
|
failureModes: ['unsupported-node-type', 'duplicate-node-id', 'invalid-node-shape', 'unsafe-url', 'unsafe-style'],
|
|
4114
4108
|
description: 'Creates a supported RichBlockNode, validates it with validateRichContentDocument semantics and inserts it by stable neighbor id when provided.',
|
|
4115
4109
|
} }],
|
|
4110
|
+
destructive: false,
|
|
4111
|
+
requiresConfirmation: false,
|
|
4116
4112
|
validators: ['node-types-supported', 'block-id-unique', 'document-shape-canonical', 'unsafe-url-rejected', 'unsafe-style-rejected', 'editor-runtime-round-trip'],
|
|
4117
4113
|
affectedPaths: ['document.nodes[]'],
|
|
4118
|
-
submissionImpact:
|
|
4114
|
+
submissionImpact: 'config-only',
|
|
4119
4115
|
preconditions: ['config-initialized'],
|
|
4120
4116
|
},
|
|
4121
4117
|
{
|
|
@@ -4130,7 +4126,7 @@ const PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST = {
|
|
|
4130
4126
|
requiresConfirmation: true,
|
|
4131
4127
|
validators: ['block-exists', 'destructive-removal-confirmed', 'document-not-empty-when-required', 'editor-runtime-round-trip'],
|
|
4132
4128
|
affectedPaths: ['document.nodes[]'],
|
|
4133
|
-
submissionImpact:
|
|
4129
|
+
submissionImpact: 'config-only',
|
|
4134
4130
|
preconditions: ['config-initialized', 'target-block-exists', 'confirmation-collected'],
|
|
4135
4131
|
},
|
|
4136
4132
|
{
|
|
@@ -4141,9 +4137,11 @@ const PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST = {
|
|
|
4141
4137
|
target: { kind: 'block', resolver: 'rich-block-by-id-or-index', ambiguityPolicy: 'fail', required: true },
|
|
4142
4138
|
inputSchema: { type: 'object', required: ['blockId'], properties: { blockId: { type: 'string' }, beforeBlockId: { type: 'string' }, afterBlockId: { type: 'string' } } },
|
|
4143
4139
|
effects: [{ kind: 'reorder-by-key', path: 'document.nodes[]', key: 'id' }],
|
|
4140
|
+
destructive: false,
|
|
4141
|
+
requiresConfirmation: false,
|
|
4144
4142
|
validators: ['block-exists', 'block-order-deterministic', 'document-shape-canonical', 'editor-runtime-round-trip'],
|
|
4145
4143
|
affectedPaths: ['document.nodes[]'],
|
|
4146
|
-
submissionImpact:
|
|
4144
|
+
submissionImpact: 'config-only',
|
|
4147
4145
|
preconditions: ['config-initialized', 'target-block-exists'],
|
|
4148
4146
|
},
|
|
4149
4147
|
{
|
|
@@ -4154,9 +4152,11 @@ const PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST = {
|
|
|
4154
4152
|
target: { kind: 'text', resolver: 'rich-text-node-by-id-or-path', ambiguityPolicy: 'fail', required: true },
|
|
4155
4153
|
inputSchema: { type: 'object', minProperties: 1, properties: { text: { type: 'string' }, textExpr: { type: 'string' }, label: { type: 'string' }, labelExpr: { type: 'string' }, title: { type: 'string' }, subtitle: { type: 'string' } } },
|
|
4156
4154
|
effects: [{ kind: 'merge-by-key', path: 'document.nodes[]', key: 'id' }],
|
|
4155
|
+
destructive: false,
|
|
4156
|
+
requiresConfirmation: false,
|
|
4157
4157
|
validators: ['block-exists', 'text-target-supports-field', 'expression-path-safe', 'document-shape-canonical', 'editor-runtime-round-trip'],
|
|
4158
4158
|
affectedPaths: ['document.nodes[].text', 'document.nodes[].textExpr', 'document.nodes[].label', 'document.nodes[].labelExpr', 'document.nodes[].title', 'document.nodes[].subtitle'],
|
|
4159
|
-
submissionImpact:
|
|
4159
|
+
submissionImpact: 'config-only',
|
|
4160
4160
|
preconditions: ['config-initialized', 'target-block-exists'],
|
|
4161
4161
|
},
|
|
4162
4162
|
{
|
|
@@ -4165,11 +4165,13 @@ const PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST = {
|
|
|
4165
4165
|
scope: 'templating',
|
|
4166
4166
|
targetKind: 'link',
|
|
4167
4167
|
target: { kind: 'link', resolver: 'document-nodes-array', ambiguityPolicy: 'fail', required: false },
|
|
4168
|
-
inputSchema: { type: 'object', required: ['label', 'href'], properties: { id: { type: 'string' }, label: { type: 'string' }, labelExpr: { type: 'string' }, href: { type: 'string' }, target: { enum:
|
|
4168
|
+
inputSchema: { type: 'object', required: ['label', 'href'], properties: { id: { type: 'string' }, label: { type: 'string' }, labelExpr: { type: 'string' }, href: { type: 'string' }, target: { enum: ['_self', '_blank'] }, rel: { type: 'string' } } },
|
|
4169
4169
|
effects: [{ kind: 'append-unique', path: 'document.nodes[]', key: 'id' }],
|
|
4170
|
+
destructive: false,
|
|
4171
|
+
requiresConfirmation: false,
|
|
4170
4172
|
validators: ['link-url-safe', 'link-policy-explicit', 'node-types-supported', 'block-id-unique', 'editor-runtime-round-trip'],
|
|
4171
4173
|
affectedPaths: ['document.nodes[]', 'document.nodes[].href', 'document.nodes[].target', 'document.nodes[].rel'],
|
|
4172
|
-
submissionImpact:
|
|
4174
|
+
submissionImpact: 'config-only',
|
|
4173
4175
|
preconditions: ['config-initialized'],
|
|
4174
4176
|
},
|
|
4175
4177
|
{
|
|
@@ -4191,7 +4193,7 @@ const PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST = {
|
|
|
4191
4193
|
requiresConfirmation: true,
|
|
4192
4194
|
validators: ['link-target-exists', 'destructive-removal-confirmed', 'document-shape-canonical', 'editor-runtime-round-trip'],
|
|
4193
4195
|
affectedPaths: ['document.nodes[]'],
|
|
4194
|
-
submissionImpact:
|
|
4196
|
+
submissionImpact: 'config-only',
|
|
4195
4197
|
preconditions: ['config-initialized', 'target-link-exists', 'confirmation-collected'],
|
|
4196
4198
|
},
|
|
4197
4199
|
{
|
|
@@ -4209,11 +4211,116 @@ const PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST = {
|
|
|
4209
4211
|
failureModes: ['preset-not-found', 'preset-kind-invalid', 'preset-document-invalid', 'preset-cycle-detected'],
|
|
4210
4212
|
description: 'Resolves a rich-block preset through the governed registry/host mediation and inserts or replaces a preset reference without serializing functions.',
|
|
4211
4213
|
} }],
|
|
4214
|
+
destructive: false,
|
|
4215
|
+
requiresConfirmation: false,
|
|
4212
4216
|
validators: ['preset-ref-valid', 'preset-exists-or-host-mediated', 'host-capabilities-serializable', 'document-shape-canonical', 'editor-runtime-round-trip'],
|
|
4213
|
-
affectedPaths: ['document.nodes[]', 'document.nodes[].ref', 'document.nodes[].inputs'
|
|
4214
|
-
submissionImpact:
|
|
4217
|
+
affectedPaths: ['document.nodes[]', 'document.nodes[].ref', 'document.nodes[].inputs'],
|
|
4218
|
+
submissionImpact: 'config-only',
|
|
4215
4219
|
preconditions: ['config-initialized'],
|
|
4216
4220
|
},
|
|
4221
|
+
{
|
|
4222
|
+
operationId: 'metadata.update',
|
|
4223
|
+
title: 'Update rich block metadata',
|
|
4224
|
+
scope: 'templating',
|
|
4225
|
+
targetKind: 'metadata',
|
|
4226
|
+
target: { kind: 'metadata', resolver: 'rich-block-by-id-or-index', ambiguityPolicy: 'fail', required: true },
|
|
4227
|
+
inputSchema: { type: 'object', minProperties: 1, properties: { blockId: { type: 'string' }, testId: { type: 'string' }, className: { type: 'string' }, visibleWhen: { type: 'object' }, style: { type: 'object' } } },
|
|
4228
|
+
effects: [{ kind: 'merge-by-key', path: 'document.nodes[]', key: 'id' }],
|
|
4229
|
+
destructive: false,
|
|
4230
|
+
requiresConfirmation: false,
|
|
4231
|
+
validators: ['block-exists', 'class-name-safe', 'visible-when-json-logic', 'unsafe-style-rejected', 'document-shape-canonical', 'editor-runtime-round-trip'],
|
|
4232
|
+
affectedPaths: ['document.nodes[].testId', 'document.nodes[].className', 'document.nodes[].visibleWhen', 'document.nodes[].style'],
|
|
4233
|
+
submissionImpact: 'config-only',
|
|
4234
|
+
preconditions: ['config-initialized', 'target-block-exists'],
|
|
4235
|
+
},
|
|
4236
|
+
{
|
|
4237
|
+
operationId: 'mediaBlock.update',
|
|
4238
|
+
title: 'Update media block fields',
|
|
4239
|
+
scope: 'templating',
|
|
4240
|
+
targetKind: 'media',
|
|
4241
|
+
target: { kind: 'media', resolver: 'rich-media-node-by-id-or-path', ambiguityPolicy: 'fail', required: true },
|
|
4242
|
+
inputSchema: { type: 'object', minProperties: 1, properties: { blockId: { type: 'string' }, avatarName: { type: 'string' }, avatarImageSrc: { type: 'string' }, title: { type: 'string' }, titleExpr: { type: 'string' }, subtitle: { type: 'string' }, subtitleExpr: { type: 'string' } } },
|
|
4243
|
+
effects: [{ kind: 'compile-domain-patch', handler: 'rich-content-media-block-update', handlerContract: {
|
|
4244
|
+
reads: ['document.nodes[]', 'document.nodes[].id', 'document.nodes[].type'],
|
|
4245
|
+
writes: ['document.nodes[].avatar', 'document.nodes[].title', 'document.nodes[].subtitle'],
|
|
4246
|
+
identityKeys: ['document.nodes[].id'],
|
|
4247
|
+
inputSchema: { type: 'object', minProperties: 1, properties: { blockId: { type: 'string' }, avatarName: { type: 'string' }, avatarImageSrc: { type: 'string' }, title: { type: 'string' }, titleExpr: { type: 'string' }, subtitle: { type: 'string' }, subtitleExpr: { type: 'string' } } },
|
|
4248
|
+
failureModes: ['media-block-not-found', 'target-not-media-block', 'unsafe-url', 'invalid-text-node'],
|
|
4249
|
+
description: 'Updates the structured mediaBlock avatar/title/subtitle fields while preserving meta and trailing content for advanced JSON authoring.',
|
|
4250
|
+
} }],
|
|
4251
|
+
destructive: false,
|
|
4252
|
+
requiresConfirmation: false,
|
|
4253
|
+
validators: ['block-exists', 'media-block-target-valid', 'unsafe-url-rejected', 'expression-path-safe', 'document-shape-canonical', 'editor-runtime-round-trip'],
|
|
4254
|
+
affectedPaths: ['document.nodes[].avatar', 'document.nodes[].title', 'document.nodes[].subtitle'],
|
|
4255
|
+
submissionImpact: 'config-only',
|
|
4256
|
+
preconditions: ['config-initialized', 'target-block-exists'],
|
|
4257
|
+
},
|
|
4258
|
+
{
|
|
4259
|
+
operationId: 'timeline.item.add',
|
|
4260
|
+
title: 'Add timeline item',
|
|
4261
|
+
scope: 'templating',
|
|
4262
|
+
targetKind: 'timelineItem',
|
|
4263
|
+
target: { kind: 'timelineItem', resolver: 'rich-timeline-node-by-id-or-path', ambiguityPolicy: 'fail', required: true },
|
|
4264
|
+
inputSchema: { type: 'object', required: ['timelineBlockId', 'item'], properties: { timelineBlockId: { type: 'string' }, item: { type: 'object' }, afterItemId: { type: 'string' }, beforeItemId: { type: 'string' } } },
|
|
4265
|
+
effects: [{ kind: 'compile-domain-patch', handler: 'rich-content-timeline-item-add', handlerContract: {
|
|
4266
|
+
reads: ['document.nodes[]', 'document.nodes[].items[]'],
|
|
4267
|
+
writes: ['document.nodes[].items[]'],
|
|
4268
|
+
identityKeys: ['document.nodes[].id', 'document.nodes[].items[].id'],
|
|
4269
|
+
inputSchema: { type: 'object', required: ['timelineBlockId', 'item'], properties: { timelineBlockId: { type: 'string' }, item: { type: 'object' }, afterItemId: { type: 'string' }, beforeItemId: { type: 'string' } } },
|
|
4270
|
+
failureModes: ['timeline-not-found', 'duplicate-timeline-item-id', 'invalid-timeline-item'],
|
|
4271
|
+
description: 'Adds a timeline item to a timeline node using stable timeline and item ids when present; array index is a resolver fallback only.',
|
|
4272
|
+
} }],
|
|
4273
|
+
destructive: false,
|
|
4274
|
+
requiresConfirmation: false,
|
|
4275
|
+
validators: ['block-exists', 'timeline-target-valid', 'timeline-item-valid', 'timeline-item-id-unique', 'document-shape-canonical', 'editor-runtime-round-trip'],
|
|
4276
|
+
affectedPaths: ['document.nodes[].items[]'],
|
|
4277
|
+
submissionImpact: 'config-only',
|
|
4278
|
+
preconditions: ['config-initialized', 'target-block-exists'],
|
|
4279
|
+
},
|
|
4280
|
+
{
|
|
4281
|
+
operationId: 'timeline.item.update',
|
|
4282
|
+
title: 'Update timeline item',
|
|
4283
|
+
scope: 'templating',
|
|
4284
|
+
targetKind: 'timelineItem',
|
|
4285
|
+
target: { kind: 'timelineItem', resolver: 'rich-timeline-item-by-block-id-and-item-id', ambiguityPolicy: 'fail', required: true },
|
|
4286
|
+
inputSchema: { type: 'object', required: ['timelineBlockId'], minProperties: 2, properties: { timelineBlockId: { type: 'string' }, itemId: { type: 'string' }, itemIndex: { type: 'number' }, field: { enum: ['title', 'titleExpr', 'subtitle', 'subtitleExpr', 'meta', 'metaExpr', 'icon', 'iconExpr', 'badge', 'badgeExpr'] }, value: { type: 'string' }, patch: { type: 'object' } } },
|
|
4287
|
+
effects: [{ kind: 'compile-domain-patch', handler: 'rich-content-timeline-item-update', handlerContract: {
|
|
4288
|
+
reads: ['document.nodes[]', 'document.nodes[].items[]'],
|
|
4289
|
+
writes: ['document.nodes[].items[]'],
|
|
4290
|
+
identityKeys: ['document.nodes[].id', 'document.nodes[].items[].id'],
|
|
4291
|
+
inputSchema: { type: 'object', required: ['timelineBlockId'], properties: { timelineBlockId: { type: 'string' }, itemId: { type: 'string' }, itemIndex: { type: 'number' }, field: { enum: ['title', 'titleExpr', 'subtitle', 'subtitleExpr', 'meta', 'metaExpr', 'icon', 'iconExpr', 'badge', 'badgeExpr'] }, value: { type: 'string' }, patch: { type: 'object' } } },
|
|
4292
|
+
failureModes: ['timeline-not-found', 'timeline-item-not-found', 'unsupported-timeline-field', 'invalid-timeline-item'],
|
|
4293
|
+
description: 'Updates a timeline item by stable item id when available, preserving the item object shape accepted by RichTimelineItem.',
|
|
4294
|
+
} }],
|
|
4295
|
+
destructive: false,
|
|
4296
|
+
requiresConfirmation: false,
|
|
4297
|
+
validators: ['block-exists', 'timeline-target-valid', 'timeline-item-exists', 'timeline-item-field-supported', 'document-shape-canonical', 'editor-runtime-round-trip'],
|
|
4298
|
+
affectedPaths: ['document.nodes[].items[].title', 'document.nodes[].items[].subtitle', 'document.nodes[].items[].meta', 'document.nodes[].items[].icon', 'document.nodes[].items[].badge'],
|
|
4299
|
+
submissionImpact: 'config-only',
|
|
4300
|
+
preconditions: ['config-initialized', 'target-block-exists', 'target-timeline-item-exists'],
|
|
4301
|
+
},
|
|
4302
|
+
{
|
|
4303
|
+
operationId: 'timeline.item.remove',
|
|
4304
|
+
title: 'Remove timeline item',
|
|
4305
|
+
scope: 'templating',
|
|
4306
|
+
targetKind: 'timelineItem',
|
|
4307
|
+
target: { kind: 'timelineItem', resolver: 'rich-timeline-item-by-block-id-and-item-id', ambiguityPolicy: 'fail', required: true },
|
|
4308
|
+
inputSchema: { type: 'object', required: ['timelineBlockId'], properties: { timelineBlockId: { type: 'string' }, itemId: { type: 'string' }, itemIndex: { type: 'number' } } },
|
|
4309
|
+
effects: [{ kind: 'compile-domain-patch', handler: 'rich-content-timeline-item-remove', handlerContract: {
|
|
4310
|
+
reads: ['document.nodes[]', 'document.nodes[].items[]'],
|
|
4311
|
+
writes: ['document.nodes[].items[]'],
|
|
4312
|
+
identityKeys: ['document.nodes[].id', 'document.nodes[].items[].id'],
|
|
4313
|
+
inputSchema: { type: 'object', required: ['timelineBlockId'], properties: { timelineBlockId: { type: 'string' }, itemId: { type: 'string' }, itemIndex: { type: 'number' } } },
|
|
4314
|
+
failureModes: ['timeline-not-found', 'timeline-item-not-found', 'ambiguous-timeline-item'],
|
|
4315
|
+
description: 'Removes a timeline item from a timeline node after explicit confirmation; stable item id is preferred over index.',
|
|
4316
|
+
} }],
|
|
4317
|
+
destructive: true,
|
|
4318
|
+
requiresConfirmation: true,
|
|
4319
|
+
validators: ['timeline-target-valid', 'timeline-item-exists', 'destructive-removal-confirmed', 'document-shape-canonical', 'editor-runtime-round-trip'],
|
|
4320
|
+
affectedPaths: ['document.nodes[].items[]'],
|
|
4321
|
+
submissionImpact: 'config-only',
|
|
4322
|
+
preconditions: ['config-initialized', 'target-timeline-item-exists', 'confirmation-collected'],
|
|
4323
|
+
},
|
|
4217
4324
|
{
|
|
4218
4325
|
operationId: 'sanitizationPolicy.set',
|
|
4219
4326
|
title: 'Set rich content sanitization policy',
|
|
@@ -4223,7 +4330,7 @@ const PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST = {
|
|
|
4223
4330
|
inputSchema: { type: 'object', properties: { allowHtml: { const: false }, allowedUrlProtocols: { type: 'array', items: { type: 'string' } }, allowImageDataUrls: { type: 'boolean' }, maxNodeDepth: { type: 'number' }, maxNodeCount: { type: 'number' } } },
|
|
4224
4331
|
effects: [{ kind: 'compile-domain-patch', handler: 'rich-content-sanitization-policy', handlerContract: {
|
|
4225
4332
|
reads: ['document', 'validateRichContentDocument'],
|
|
4226
|
-
writes: ['
|
|
4333
|
+
writes: ['document'],
|
|
4227
4334
|
identityKeys: ['document.kind', 'document.version'],
|
|
4228
4335
|
inputSchema: { type: 'object', properties: { allowHtml: { const: false }, allowedUrlProtocols: { type: 'array', items: { type: 'string' } }, allowImageDataUrls: { type: 'boolean' }, maxNodeDepth: { type: 'number' }, maxNodeCount: { type: 'number' } } },
|
|
4229
4336
|
failureModes: ['html-not-supported', 'unsafe-protocol', 'max-depth-too-high', 'max-node-count-too-high'],
|
|
@@ -4232,8 +4339,8 @@ const PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST = {
|
|
|
4232
4339
|
destructive: true,
|
|
4233
4340
|
requiresConfirmation: true,
|
|
4234
4341
|
validators: ['sanitization-policy-explicit', 'unsafe-html-rejected', 'unsafe-url-rejected', 'security-change-confirmed', 'editor-runtime-round-trip'],
|
|
4235
|
-
affectedPaths: ['document'
|
|
4236
|
-
submissionImpact:
|
|
4342
|
+
affectedPaths: ['document'],
|
|
4343
|
+
submissionImpact: 'config-only',
|
|
4237
4344
|
preconditions: ['config-initialized', 'confirmation-collected'],
|
|
4238
4345
|
},
|
|
4239
4346
|
{
|
|
@@ -4242,11 +4349,13 @@ const PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST = {
|
|
|
4242
4349
|
scope: 'global',
|
|
4243
4350
|
targetKind: 'display',
|
|
4244
4351
|
target: { kind: 'display', resolver: 'rich-content-layout-and-root-class', ambiguityPolicy: 'fail', required: false },
|
|
4245
|
-
inputSchema: { type: 'object', properties: { layout: { enum:
|
|
4352
|
+
inputSchema: { type: 'object', properties: { layout: { enum: ['block', 'inline'] }, rootClassName: { type: 'string' } } },
|
|
4246
4353
|
effects: [{ kind: 'set-value', path: 'layout' }, { kind: 'set-value', path: 'rootClassName' }],
|
|
4354
|
+
destructive: false,
|
|
4355
|
+
requiresConfirmation: false,
|
|
4247
4356
|
validators: ['layout-valid', 'root-class-safe', 'editor-runtime-round-trip'],
|
|
4248
4357
|
affectedPaths: ['layout', 'rootClassName'],
|
|
4249
|
-
submissionImpact:
|
|
4358
|
+
submissionImpact: 'visual-only',
|
|
4250
4359
|
preconditions: ['config-initialized'],
|
|
4251
4360
|
},
|
|
4252
4361
|
],
|
|
@@ -4275,12 +4384,21 @@ const PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST = {
|
|
|
4275
4384
|
{ validatorId: 'layout-valid', level: 'error', code: 'PRC022', description: 'Layout must be block or inline.' },
|
|
4276
4385
|
{ validatorId: 'root-class-safe', level: 'warning', code: 'PRC023', description: 'rootClassName must contain safe CSS class tokens.' },
|
|
4277
4386
|
{ validatorId: 'editor-runtime-round-trip', level: 'error', code: 'PRC024', description: 'Config editor, Settings Panel payload and runtime renderer must preserve document structure, layout and rootClassName.' },
|
|
4387
|
+
{ validatorId: 'class-name-safe', level: 'error', code: 'PRC025', description: 'Node className values must contain safe CSS class tokens.' },
|
|
4388
|
+
{ validatorId: 'visible-when-json-logic', level: 'error', code: 'PRC026', description: 'visibleWhen must be a Json Logic expression object and not a string DSL.' },
|
|
4389
|
+
{ validatorId: 'media-block-target-valid', level: 'error', code: 'PRC027', description: 'Media operations must target a mediaBlock node and preserve structured avatar/text child nodes.' },
|
|
4390
|
+
{ validatorId: 'timeline-target-valid', level: 'error', code: 'PRC028', description: 'Timeline item operations must target a timeline node with an items array.' },
|
|
4391
|
+
{ validatorId: 'timeline-item-valid', level: 'error', code: 'PRC029', description: 'Timeline items must be objects matching RichTimelineItem fields.' },
|
|
4392
|
+
{ validatorId: 'timeline-item-id-unique', level: 'warning', code: 'PRC030', description: 'Timeline item ids should be unique inside a timeline node when present.' },
|
|
4393
|
+
{ validatorId: 'timeline-item-exists', level: 'error', code: 'PRC031', description: 'Timeline item updates and removals must resolve an existing item by stable id or explicit fallback index.' },
|
|
4394
|
+
{ validatorId: 'timeline-item-field-supported', level: 'error', code: 'PRC032', description: 'Timeline item updates may only target supported RichTimelineItem fields.' },
|
|
4278
4395
|
],
|
|
4279
4396
|
roundTripRequirements: [
|
|
4280
4397
|
'The editor must produce the same widget input envelope consumed by page-builder: inputs.document, inputs.layout and inputs.rootClassName.',
|
|
4281
4398
|
'Rich content must remain structured RichContentDocument JSON; arbitrary HTML and script URL patches are rejected before persistence.',
|
|
4282
4399
|
'Block identity should prefer document.nodes[].id; array index is a resolver fallback only and cannot be the canonical identity for generated patches.',
|
|
4283
4400
|
'Link authoring uses canonical link nodes with safe href/target/rel instead of markdown or raw HTML embedded in text nodes.',
|
|
4401
|
+
'MediaBlock, timeline item, metadata, visibleWhen and safe style authoring use the same structured paths exposed by the visual config editor.',
|
|
4284
4402
|
'Preset authoring persists only rich-block refs and inputs; host capability functions remain runtime-mediated and are not serialized.',
|
|
4285
4403
|
],
|
|
4286
4404
|
examples: [
|
|
@@ -4293,6 +4411,10 @@ const PRAXIS_RICH_CONTENT_AUTHORING_MANIFEST = {
|
|
|
4293
4411
|
{ id: 'reject-script-link', request: 'Add a link to javascript:alert(1).', operationId: 'link.add', params: { label: 'Bad link', href: 'javascript:alert(1)' }, isPositive: false },
|
|
4294
4412
|
{ id: 'remove-docs-link', request: 'Remove the documentation link but keep its label as text.', operationId: 'link.remove', target: 'docs-link', params: { linkId: 'docs-link', preserveLabelAsText: true }, isPositive: true },
|
|
4295
4413
|
{ id: 'apply-profile-preset', request: 'Apply the profile summary rich-block preset.', operationId: 'preset.apply', params: { ref: { kind: 'rich-block', namespace: 'praxis.rich-content', presetId: 'profile-summary', version: '1.0.0' } }, isPositive: true },
|
|
4414
|
+
{ id: 'show-only-active-row', request: 'Show the status block only when row.active is true.', operationId: 'metadata.update', target: 'status', params: { blockId: 'status', visibleWhen: { '===': [{ var: 'row.active' }, true] } }, isPositive: true },
|
|
4415
|
+
{ id: 'update-media-title', request: 'Update the profile media block title and avatar name.', operationId: 'mediaBlock.update', target: 'profile-media', params: { blockId: 'profile-media', avatarName: 'Ana Silva', title: 'Profile summary' }, isPositive: true },
|
|
4416
|
+
{ id: 'add-created-timeline-item', request: 'Add a Created item to the activity timeline.', operationId: 'timeline.item.add', target: 'activity', params: { timelineBlockId: 'activity', item: { id: 'created', title: 'Created', badge: 'Done' } }, isPositive: true },
|
|
4417
|
+
{ id: 'reject-unknown-timeline-field', request: 'Set a custom script field on a timeline item.', operationId: 'timeline.item.update', target: 'activity.created', params: { timelineBlockId: 'activity', itemId: 'created', field: 'script', value: 'alert(1)' }, isPositive: false },
|
|
4296
4418
|
{ id: 'reject-html-policy', request: 'Allow arbitrary HTML in this rich content document.', operationId: 'sanitizationPolicy.set', params: { allowHtml: true }, isPositive: false },
|
|
4297
4419
|
{ id: 'compact-inline-layout', request: 'Render this rich content inline with root class status-line.', operationId: 'display.mode.set', params: { layout: 'inline', rootClassName: 'status-line' }, isPositive: true },
|
|
4298
4420
|
],
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@praxisui/rich-content",
|
|
3
|
-
"version": "8.0.0-beta.
|
|
3
|
+
"version": "8.0.0-beta.12",
|
|
4
4
|
"peerDependencies": {
|
|
5
5
|
"@angular/common": "^20.3.0",
|
|
6
6
|
"@angular/core": "^20.3.0",
|
|
7
|
-
"@praxisui/core": "^8.0.0-beta.
|
|
7
|
+
"@praxisui/core": "^8.0.0-beta.12"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"tslib": "^2.3.0"
|