diff --git a/stack--current/3-advanced--multi/rich-text-format/src/renderers/to_actions.ts b/stack--current/3-advanced--multi/rich-text-format/src/renderers/to_actions.ts index d42388f8..f54c6707 100644 --- a/stack--current/3-advanced--multi/rich-text-format/src/renderers/to_actions.ts +++ b/stack--current/3-advanced--multi/rich-text-format/src/renderers/to_actions.ts @@ -125,11 +125,15 @@ const callbacksⵧto_actions: Partial = {}): Action[] { if (typeof $doc !== 'object') { return [] } - return walk($doc, callbacksⵧto_actions, options).actions + const full_options: RenderingOptionsⵧToActions = { + ...DEFAULT_RENDERING_OPTIONSⵧToActions, + ...options, + } + return walk($doc, callbacksⵧto_actions, full_options).actions } ///////////////////////////////////////////////// diff --git a/stack--current/3-advanced--multi/rich-text-format/src/renderers/to_debug.ts b/stack--current/3-advanced--multi/rich-text-format/src/renderers/to_debug.ts index 328c93a5..38c6047e 100644 --- a/stack--current/3-advanced--multi/rich-text-format/src/renderers/to_debug.ts +++ b/stack--current/3-advanced--multi/rich-text-format/src/renderers/to_debug.ts @@ -38,8 +38,8 @@ function debug_node_short($node: CheckedNode) { //////////////////////////////////// -interface RenderingOptions extends BaseRenderingOptions {} -const DEFAULT_RENDERING_OPTIONS= Object.freeze({ +interface RenderingOptionsⵧToDebug extends BaseRenderingOptions {} +const DEFAULT_RENDERING_OPTIONSⵧToDebug= Object.freeze({ ...DEFAULT_RENDERING_OPTIONSⵧWalk, }) @@ -65,7 +65,7 @@ const on_nodeⵧenter = ({$node, $id, depth}: OnNodeEnterParams): State => { return state } -const on_nodeⵧexit: WalkerReducer, RenderingOptions> = ({$node, $id, state, depth}) => { +const on_nodeⵧexit: WalkerReducer, RenderingOptionsⵧToDebug> = ({$node, $id, state, depth}) => { console.log(indent(depth) + `⟨ [on_nodeⵧexit] #${$id}`) console.log(indent(depth) + ` [state="${state}"]`) consoleGroupEnd() @@ -75,42 +75,42 @@ const on_nodeⵧexit: WalkerReducer, RenderingOpt // when walking inside the content -const on_concatenateⵧstr: WalkerReducer, RenderingOptions> = ({str, state, $node, depth}) => { +const on_concatenateⵧstr: WalkerReducer, RenderingOptionsⵧToDebug> = ({str, state, $node, depth}) => { console.log(indent(depth) + `+ [on_concatenateⵧstr] "${str}"`) state = state + str console.log(indent(depth) + ` [state="${state}"]`) return state } -const on_concatenateⵧsub_node: WalkerReducer, RenderingOptions> = ({state, sub_state, depth, $id, $parent_node}) => { +const on_concatenateⵧsub_node: WalkerReducer, RenderingOptionsⵧToDebug> = ({state, sub_state, depth, $id, $parent_node}) => { console.log(indent(depth) + `+ [on_concatenateⵧsub_node] "${sub_state}"`) state = state + sub_state console.log(indent(depth) + ` [state="${state}"]`) return state } -const on_filter: WalkerReducer, RenderingOptions> = ({$filter, $filters, state, $node, depth}) => { +const on_filter: WalkerReducer, RenderingOptionsⵧToDebug> = ({$filter, $filters, state, $node, depth}) => { console.log(indent(depth) + ` [on_filter] "${$filter}`) return state } -const on_classⵧbefore: WalkerReducer, RenderingOptions> = ({$class, state, $node, depth}) => { +const on_classⵧbefore: WalkerReducer, RenderingOptionsⵧToDebug> = ({$class, state, $node, depth}) => { console.log(indent(depth) + ` [⟩on_classⵧbefore] .${$class}`) return state } -const on_classⵧafter: WalkerReducer, RenderingOptions> = ({$class, state, $node, depth}) => { +const on_classⵧafter: WalkerReducer, RenderingOptionsⵧToDebug> = ({$class, state, $node, depth}) => { console.log(indent(depth) + ` [⟨on_classⵧafter] .${$class}`) return state } -const on_type: WalkerReducer, RenderingOptions> = ({$type, state, $node, depth}) => { +const on_type: WalkerReducer, RenderingOptionsⵧToDebug> = ({$type, state, $node, depth}) => { console.log(indent(depth) + ` [on_type] "${$type}" ${$node.$classes}`) return state } //////////////////////////////////// -const callbacksⵧto_debug: Partial> = { +const callbacksⵧto_debug: Partial> = { on_rootⵧenter, on_rootⵧexit, @@ -142,13 +142,18 @@ const callbacksⵧto_debug: Partial> = }, } -function renderⵧto_debug($doc: Node, options: RenderingOptions = DEFAULT_RENDERING_OPTIONS): string { - return walk($doc, callbacksⵧto_debug, options) +function renderⵧto_debug($doc: Node, options: Partial = {}): string { + const full_options: RenderingOptionsⵧToDebug = { + ...DEFAULT_RENDERING_OPTIONSⵧToDebug, + ...options, + } + return walk($doc, callbacksⵧto_debug, full_options) } ///////////////////////////////////////////////// export { + DEFAULT_RENDERING_OPTIONSⵧToDebug, callbacksⵧto_debug, renderⵧto_debug, } diff --git a/stack--current/3-advanced--multi/rich-text-format/src/renderers/to_html.ts b/stack--current/3-advanced--multi/rich-text-format/src/renderers/to_html.ts index 89c9955b..90b4d095 100644 --- a/stack--current/3-advanced--multi/rich-text-format/src/renderers/to_html.ts +++ b/stack--current/3-advanced--multi/rich-text-format/src/renderers/to_html.ts @@ -144,10 +144,18 @@ const callbacksⵧto_html: Partial\n ' - + walk($doc, callbacksⵧto_html, options).str - + '\n\n' +function renderⵧto_html($doc: Node, options: Partial = {}): string { + const full_options: RenderingOptionsⵧToHtml = { + ...DEFAULT_RENDERING_OPTIONSⵧToHtml, + ...options, + } + + // TODO review classes + return ` +
+ ${walk($doc, callbacksⵧto_html, full_options).str} +
+` } ///////////////////////////////////////////////// diff --git a/stack--current/3-advanced--multi/rich-text-format/src/renderers/to_text.ts b/stack--current/3-advanced--multi/rich-text-format/src/renderers/to_text.ts index 7e81b11d..65fdfdcd 100644 --- a/stack--current/3-advanced--multi/rich-text-format/src/renderers/to_text.ts +++ b/stack--current/3-advanced--multi/rich-text-format/src/renderers/to_text.ts @@ -241,13 +241,17 @@ const callbacksⵧto_text: Partial = {}, callback_overrides: Partial> = {}, ): string { + const full_options: RenderingOptionsⵧToText = { + ...DEFAULT_RENDERING_OPTIONSⵧToText, + ...options, + } return walk($doc, { ...callbacksⵧto_text, ...callback_overrides, - }, options).str + }, full_options).str } ///////////////////////////////////////////////// diff --git a/stack--current/3-advanced--multi/rich-text-format/src/renderers/to_text_spec.ts b/stack--current/3-advanced--multi/rich-text-format/src/renderers/to_text_spec.ts index 240dd2e0..d357bbd1 100644 --- a/stack--current/3-advanced--multi/rich-text-format/src/renderers/to_text_spec.ts +++ b/stack--current/3-advanced--multi/rich-text-format/src/renderers/to_text_spec.ts @@ -7,11 +7,9 @@ import * as RichText from '../index.js' ///////////////////////////////////////////////// describe(`${LIB} -- renderers -- to text`, () => { - let rendering_options = RichText.DEFAULT_RENDERING_OPTIONSⵧToText + let rendering_options = {} beforeEach(() => { - rendering_options = { - ...RichText.DEFAULT_RENDERING_OPTIONSⵧToText, - } + rendering_options = {} }) const $DEMOⵧSIMPLE = (() => { diff --git a/stack--current/3-advanced--multi/rich-text-format/src/renderers/walk.ts b/stack--current/3-advanced--multi/rich-text-format/src/renderers/walk.ts index 9612e55d..e8ed8c4c 100644 --- a/stack--current/3-advanced--multi/rich-text-format/src/renderers/walk.ts +++ b/stack--current/3-advanced--multi/rich-text-format/src/renderers/walk.ts @@ -10,7 +10,6 @@ import { import { normalizeꓽnode } from '../utils/normalize.js' import { promoteꓽto_node } from '../utils/promote.js' -import { Action, EmbeddedReducerAction, HyperlinkAction, ReducerAction, RenderingOptionsⵧToActions } from './to_actions' ///////////////////////////////////////////////// // "walk" is the foundation on which all the renderer are based @@ -268,8 +267,11 @@ function _walk_content( return { $content: `{{??${sub_node_id}??}}` } } - //console.error($node, { $content, sub_node_id }) - throw new Error(`${LIB}: syntax error in content "${$content}", it's referencing an unknown sub-node "${sub_node_id}"!`) + if (true) { + console.error('shouldꓽrecover_from_unknown_sub_nodes FAILURE',) + console.error($node, { $content, sub_node_id }) + } + throw new Error(`${LIB}: syntax error in content "${$content}", it's referencing an unknown sub-node "${sub_node_id}"! (recover mode = ${options.shouldꓽrecover_from_unknown_sub_nodes})`) })()) let sub_state = _walk($sub_node, callbacks, options, { @@ -437,17 +439,26 @@ function _walk( function walk( $raw_node: Readonly, raw_callbacks: Readonly>>, - options: Readonly = {} as any, + options: RenderingOptions, // this internal fn can't default unknown type, so we expect the caller to give us full options ) { - if (!Object.keys(raw_callbacks).every(k => k === 'resolve_unknown_subnode' || k.startsWith('on_'))) - console.warn(`${LIB} Unexpected unrecognized callbacks, check the API!`) + assert( + Object.keys(raw_callbacks).every(k => k === 'resolve_unknown_subnode' || k.startsWith('on_')), + `${LIB}[walk]: custom callbacks should match the expected format, check the API!` + ) const callbacks: WalkerCallbacks = { ..._getꓽcallbacksⵧdefault(), ...raw_callbacks as any as WalkerCallbacks, } + assert( + // detect incorrectly built options (actual issue before rewrite in 2024) + options.shouldꓽrecover_from_unknown_sub_nodes !== undefined, + `${LIB}[walk]: options should be a fully initialized options object!` + ) + const $root_node = normalizeꓽnode($raw_node) + return _walk($root_node, callbacks, options, { $parent_node: null, $id: 'root', diff --git a/stack--current/3-advanced--multi/rich-text-format/src/renderers/walk_spec.ts b/stack--current/3-advanced--multi/rich-text-format/src/renderers/walk_spec.ts index ade78213..352e4964 100644 --- a/stack--current/3-advanced--multi/rich-text-format/src/renderers/walk_spec.ts +++ b/stack--current/3-advanced--multi/rich-text-format/src/renderers/walk_spec.ts @@ -5,17 +5,31 @@ import { LIB } from '../consts.js' ///////////////////////////////////////////////// import * as RichText from '../index.js' -import { walk, WalkerCallbacks } from './walk.js' -import { BaseRenderingOptions, Node } from '../index.js' +import { + walk, + type WalkerCallbacks, +} from './walk.js' +import { + type BaseRenderingOptions, + type Node, +} from '../index.js' describe(`${LIB} -- renderers -- walker (internal)`, function () { - interface State {} interface Options extends BaseRenderingOptions {} const callbacks: Partial> = { on_nodeⵧenter() { return {} as State }, } + let rendering_options: Options = { + shouldꓽrecover_from_unknown_sub_nodes: false, + } + beforeEach(() => { + rendering_options = { + shouldꓽrecover_from_unknown_sub_nodes: false, + } + }) + describe('walking through all nodes in order', function () { it('should work') @@ -76,7 +90,7 @@ describe(`${LIB} -- renderers -- walker (internal)`, function () { }, } - expect(() => walk($doc, {...callbacks})).to.throw('unknown sub-node') + expect(() => walk($doc, {...callbacks}, rendering_options)).to.throw('unknown sub-node') }) it('should work -- handling missing -- auto recovery -- placeholder', () => { @@ -160,7 +174,7 @@ describe(`${LIB} -- renderers -- walker (internal)`, function () { const $doc = { $content: '⎨⎨foo', } - expect(() => walk($doc, {...callbacks})).to.throw('unmatched') + expect(() => walk($doc, {...callbacks}, rendering_options)).to.throw('unmatched') }) it('should detect unmatched ⎨⎨⎬⎬ -- ⎨⎨ 2', () => { const $doc = { @@ -169,7 +183,7 @@ describe(`${LIB} -- renderers -- walker (internal)`, function () { 'foo': {} }, } - expect(() => walk($doc, {...callbacks})).to.throw('unmatched') + expect(() => walk($doc, {...callbacks}, rendering_options)).to.throw('unmatched') }) it('should detect unmatched ⎨⎨⎬⎬ -- ⎬⎬ 1', () => { @@ -177,7 +191,7 @@ describe(`${LIB} -- renderers -- walker (internal)`, function () { const $doc = { $content: 'foo⎬⎬', } - expect(() => walk($doc, {...callbacks})).to.throw('unmatched') + expect(() => walk($doc, {...callbacks}, rendering_options)).to.throw('unmatched') }) it('should detect unmatched ⎨⎨⎬⎬ -- ⎬⎬ 2a', () => { @@ -187,7 +201,7 @@ describe(`${LIB} -- renderers -- walker (internal)`, function () { 'foo': {} }, } - expect(() => walk($doc, {...callbacks})).to.throw('unmatched') + expect(() => walk($doc, {...callbacks}, rendering_options)).to.throw('unmatched') }) it('should detect unmatched ⎨⎨⎬⎬ -- ⎬⎬ 2b', () => { @@ -197,7 +211,7 @@ describe(`${LIB} -- renderers -- walker (internal)`, function () { 'foo': {} }, } - expect(() => walk($doc, {...callbacks})).to.throw('unmatched') + expect(() => walk($doc, {...callbacks}, rendering_options)).to.throw('unmatched') }) it('should detect reversed ⎨⎨⎬⎬', () => { @@ -208,7 +222,7 @@ describe(`${LIB} -- renderers -- walker (internal)`, function () { 'foo': {} }, } - expect(() => walk($doc, {...callbacks})).to.throw('unmatched') + expect(() => walk($doc, {...callbacks}, rendering_options)).to.throw('unmatched') }) }) }) diff --git a/stack--current/3-advanced--multi/rich-text-format/src/sugar/builder.ts b/stack--current/3-advanced--multi/rich-text-format/src/sugar/builder.ts index 0e728dea..6859ddfe 100644 --- a/stack--current/3-advanced--multi/rich-text-format/src/sugar/builder.ts +++ b/stack--current/3-advanced--multi/rich-text-format/src/sugar/builder.ts @@ -27,12 +27,12 @@ interface Builder { // node ref is auto added into content pushNode(node: Node, options?: CommonOptions): Builder - // nothing is added in content + // NOTHING is added into content // useful for // 1. lists // 2. manual stuff - pushRawNode(node: Node, options?: CommonOptions): Builder - pushRawNodes(nodes: { [id: string]: Node }): Builder // batch version + pushRawNode(node: CheckedNode['$sub'][string], options?: CommonOptions): Builder + pushRawNodes(nodes: CheckedNode['$sub']): Builder // batch version pushInlineFragment(str: string, options?: CommonOptions): Builder pushBlockFragment(str: string, options?: CommonOptions): Builder diff --git a/stack--current/A-apps--core/the-boring-rpg/l2-state/src/reducers/achievements/index.ts b/stack--current/A-apps--core/the-boring-rpg/l2-state/src/reducers/achievements/index.ts index 714c62bb..3f9d632c 100644 --- a/stack--current/A-apps--core/the-boring-rpg/l2-state/src/reducers/achievements/index.ts +++ b/stack--current/A-apps--core/the-boring-rpg/l2-state/src/reducers/achievements/index.ts @@ -39,8 +39,10 @@ function _refresh_achievements(state: Immutable): Immutable { engagement = enqueueEngagement(engagement, getꓽengagement_template(EngagementTemplateKey.achievement_unlocked), { - icon, - name, + $sub: { + icon, + name, + }, }, ) } diff --git a/stack--current/A-apps--core/the-boring-rpg/x-sandbox/src/to-export-to-own-package/hateoas/utils.ts b/stack--current/A-apps--core/the-boring-rpg/x-sandbox/src/to-export-to-own-package/hateoas/utils.ts index c331bd16..1b8ef0b0 100644 --- a/stack--current/A-apps--core/the-boring-rpg/x-sandbox/src/to-export-to-own-package/hateoas/utils.ts +++ b/stack--current/A-apps--core/the-boring-rpg/x-sandbox/src/to-export-to-own-package/hateoas/utils.ts @@ -1,10 +1,12 @@ import assert from 'tiny-invariant' import { type Immutable } from '@offirmo-private/ts-types' +import { getꓽmutable_copy } from '@offirmo-private/state-utils' import { type NodeLike, type Node, - promoteꓽto_nodeⵧimmutable, + type CheckedNode, + createⵧfrom_content, } from '@offirmo-private/rich-text-format' import { type PendingEngagement, @@ -15,14 +17,47 @@ import { type HATEOASPendingEngagement } from './types.js' ///////////////////////////////////////////////// +const EXPECTED_FIELDS = new Set(['$sub', '$hints']) +function assertꓽparams__shape(params: PendingEngagement['params']): Pick { + const result: Pick = { + $sub: {}, + $hints: {}, + } + + const keys = Object.keys(params) + keys.forEach(k => { + assert(EXPECTED_FIELDS.has(k), `a HATEOAS RichText PendingEngagement params should not contain extraneous fields! ("${k}")`) + }) + + result.$sub = { + ...params?.['$sub'], + } + result.$hints = { + ...params?.['$hints'], + } + + return result +} + function resolveꓽrich_text_pending_engagement( pe: Immutable>, uid_to_ack: (uid: PendingEngagementUId) => Immutable, ): Immutable> { - const $doc = promoteꓽto_nodeⵧimmutable(pe.template.content) + const template__contentⵧmutable = getꓽmutable_copy(pe.template.content) + + const builder = createⵧfrom_content(template__contentⵧmutable) + const extras = assertꓽparams__shape(pe.params) + builder.pushRawNodes(extras.$sub) + builder.addHints(extras.$hints) + + /*console.warn(`XXX`, { + raw_params: pe.params, + extras, + content: builder.done(), + })*/ return { - content: $doc, + content: builder.done(), flow: pe.template.flow, role: pe.template.role, ...(pe.template.success !== undefined && { success: pe.template.success }),