bugfix + tests + docs...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2026-07-01 16:06:25 +03:00
parent acf37a4832
commit b879049346
3 changed files with 132 additions and 28 deletions

View File

@ -7,6 +7,8 @@ Any macro can be used in any of the two forms, either _inline_ or _HTML-like_.
Inline: Inline:
``` ```
@macro-name(value) @macro-name(value)
@macro-name(value text=" ...text... ")
``` ```
HTML-style: HTML-style:
@ -27,6 +29,28 @@ The two forms exist to fill two distinct functions:
- html-like: element-like, simpler when dealing with html - html-like: element-like, simpler when dealing with html
### Positional and keyword attributes
Attributes (arguments) can be set sequentially by value, as they are
defined or by name in any order.
The following are equivalent:
```
<slot abc "some text"/>
<slot name=abc text="some text"/>
<slot "some text" name=abc/>
```
Keyword attributes are special attributes that are given by name rather
than by value by default and if no value is set explicitly it defaults
to `true` if passed, and to false if omitted.
The following are equivalent:
```
<slot some-name hidden/>
<slot some-name hidden=true/>
```
### Special attributes ### Special attributes
Two special attributes are handled differently: `text` and `body`, these Two special attributes are handled differently: `text` and `body`, these
@ -108,6 +132,7 @@ This will enable writing documents (mainly in _markdown_) that are usable
bot from within pWiki as well as outside. bot from within pWiki as well as outside.
## Macros ## Macros
### now () ### now ()
@ -132,33 +157,56 @@ use.
### slot (<name> <text> shown|hidden) ### slot (<name> <text> shown|hidden) / content
``` ```
\@slot(<name>) @slot(<name>)
\@slot(<name> <text>) @slot(<name> <text>)
\@slot(<name> <text> hidden) @slot(<name> <text> hidden)
\@slot(<name> <text> shown) @slot(<name> <text> shown)
<slot <name> ...>
...
<content/>
...
</slot>
``` ```
Define or fill a slot. Define or fill a slot.
First occurrence of a slot `name` will _define_ a slot and set its value First occurrence of a slot `name` will _define_ a slot and set its value
(fill it) with `text` if given. (fill it) with `text` if given.
Each new occurrence of a name will override slot content. Each new occurrence of a slot with the same name will override slot content.
Only the first occurance of the `name` slot macro is displayed by default. Only the first occurance of the `name` slot macro is displayed by default.
Slot display can be explicitly controlled via the `hidden` and `shown` Slot display can be explicitly controlled via the `hidden` and `shown`
keywords, if both are given `hidden` has precedence. keywords, if both are given `hidden` has precedence. All _shown_ slots of
the same name will display the same value.
Nested slots are processed in order of occurrence, i.e. a nested slot can Nested slots are processed in order of occurrence, i.e. a nested slot can
override it's parent's value. override it's parent's value.
**Example:**
``` ```
<slot X> some text <slot X "new text"/></slot> <slot X>
some text
<slot X "new text"/>
</slot>
``` ```
Will resolve to: `new text` Will resolve to: `new text`
`<content/>` / `@content()` if encountered will be replaced with previous
slot value.
**Example:**
```
<slot X text="some text"/>
<slot X>[[ <content/> ]]</slot>
```
Will resolve to `[[ some text ]]`
**Example:** **Example:**
@ -182,7 +230,7 @@ Will resolve to: `new text`
---
### filter (name) ### filter (name)

View File

@ -1309,6 +1309,13 @@ module.parser = {
// ... // ...
// </slot> // </slot>
// //
// Wrap previous value of slot
// <slot name=<name>>
// ...
// <content/>
// ...
// </slot>
//
// Force show a slot... // Force show a slot...
// <slot shown ... /> // <slot shown ... />
// //
@ -1325,12 +1332,9 @@ module.parser = {
// all other slots with <name> will replace its content, unless // all other slots with <name> will replace its content, unless
// explicit shown/hidden arguments are given. // explicit shown/hidden arguments are given.
// NOTE: hidden has precedence over shown if both are given. // NOTE: hidden has precedence over shown if both are given.
// XXX revise...
// //
// XXX do we need to be able to insert previous slot value??? // XXX can there be a situation where not all <content/> elements
// ...this was implemented via <content/>, but the naming // are cleared???
// was not obvious, should be something like <overridden/>
// or <previous/>...
// XXX revise the use of hidden/shown use mechanic and if it's // XXX revise the use of hidden/shown use mechanic and if it's
// needed... // needed...
slot: Macro( slot: Macro(
@ -1358,34 +1362,43 @@ module.parser = {
// set slot value... // set slot value...
// //
// NOTE: the slots are filled sequentially, in
// order of opening elements, rather than
// topologically, i.e. filled on the way down
// the tree vs. up.
var slot = slots[name] ??= [] var slot = slots[name] ??= []
// NOTE: the placeholder is a stand-in for our // NOTE: the placeholder is a stand-in for our
// current value that is still to be generated. // current value that is still to be generated...
var placeholder = [...(0 in slot ? slot : [])] var placeholder = [...slot]
slot.splice(0, slot.length, placeholder) slot.splice(0, slot.length, placeholder)
// expand slot body... // expand body...
body = body ? body = body ?
parser.expand(this, body ?? [], state) parser.expand(this, body ?? [], state)
: body : body
// if slot not overriden, write our value... // if slot not overriden, write our value...
if(slot[0] === placeholder){ if(slot[0] === placeholder){
slot.splice(0, 1, slot.splice(0, slot.length,
...(body != null ? ...(body != null ?
[body] [body]
: placeholder)) } : placeholder))
// placeholder -> <content/>
body = placeholder }
// <content/> -- handle slot's original value...
slot[0] =
body.flat().length > 0
&& slot[0] instanceof Array ?
slot[0]
.map(function(e){
if(e && e.name == 'content'){
return body }
return e })
: slot[0]
return hidden ? return hidden ?
'' ''
: Object.assign( : Object.assign(
// stage II: place the latest slot value... // stage II: place the latest slot value...
function(st){ function(st){
return ((st ?? state).slots ?? {})[name] return ((st ?? state).slots ?? {})[name] },
?? body },
{slot: name}) }) })), {slot: name}) }) })),
// XXX do not like this name...
content: ['slot'],
// //

View File

@ -56,7 +56,7 @@ test.Setups({
...ins.map(function(e){ ...ins.map(function(e){
return e + '<slot slot>third</slot>' }), return e + '<slot slot>third</slot>' }),
'third' ]} }, 'third' ]} },
// nested...
slot_nested: function(assert){ slot_nested: function(assert){
return {code: [ return {code: [
'<slot slot>[[ <slot slot.content/> ]]</slot>@slot(slot.content value)', '<slot slot>[[ <slot slot.content/> ]]</slot>@slot(slot.content value)',
@ -65,8 +65,7 @@ test.Setups({
return {code: [ return {code: [
'<slot slot>[[ <slot slot.content/> ]]</slot>@slot(slot value)', '<slot slot>[[ <slot slot.content/> ]]</slot>@slot(slot value)',
'value' ]} }, 'value' ]} },
// nested-self...
// recursion...
slot_nested_nested: function(assert){ slot_nested_nested: function(assert){
return {code: [ return {code: [
'<slot slot>[[ <slot slot value/> ]]</slot>', '<slot slot>[[ <slot slot value/> ]]</slot>',
@ -76,6 +75,32 @@ test.Setups({
return {code: [ return {code: [
'<slot slot>[[ <slot slot/> ]]</slot>', '<slot slot>[[ <slot slot/> ]]</slot>',
'[[ ]]' ]} }, '[[ ]]' ]} },
// slot content...
slot_content: function(assert){
return {code: [
'<slot slot>[[ <content/> ]]</slot>',
'[[ ]]' ]} },
slot_content_multi: function(assert){
return {code: [
'<slot slot "moo"/><slot slot>[[ <content/> <content/> <content/> ]]</slot>',
'[[ moo moo moo ]]' ]} },
slot_content_filled: function(assert){
var ins = this.slot_content(assert).code.slice(0, -1)
return {code: [
...ins.map(function(e){
return '<slot slot "content"/>' + e }),
'[[ content ]]' ]} },
slot_content_content_filled: function(assert){
var ins = this.slot_content_filled(assert).code
var res = ins.pop()
return {code: [
...ins.map(function(e){
return e +'<slot slot>(( <content/> ))</slot>' }),
'(( '+ res +' ))' ]} },
slot_content_nested: function(assert){
return {code: [
'<slot slot>moo<slot slot>[[ <content/> ]]</slot></slot>',
'[[ moo ]]' ]} },
/* XXX SHOWN_HIDDEN /* XXX SHOWN_HIDDEN
// XXX these need to be revised... // XXX these need to be revised...
@ -125,6 +150,24 @@ test.Modifiers({
string: 'string', string: 'string',
} }
return state }, return state },
slot: function(assert, state){
state.code = [
...state.code
.slice(0, -1)
.map(function(e){
return `[[ <slot parent>${e}</slot> ]]` }),
`[[ ${state.code.at(-1)} ]]`,
]
return state },
slot_expand: function(assert, state){
state.code = [
...state.code
.slice(0, -1)
.map(function(e){
return `[[ <slot parent/> ]]<slot parent>${e}</slot>` }),
`[[ ${state.code.at(-1)} ]]`,
]
return state },
}) })