Skip to content
Toggle navigation
Toggle navigation
This project
Loading...
Sign in
brainfood
/
astro-wt
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Network
Create a new issue
Builds
Commits
Issue Boards
Files
Commits
Network
Compare
Branches
Tags
0d873159
authored
2024-11-22 12:29:28 -0600
by
Adam Heath
Browse Files
Options
Browse Files
Tag
Download
Email Patches
Plain Diff
Implement pseudo-elements after/before.
1 parent
acf23c75
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
48 additions
and
36 deletions
lib/html.ts
lib/match.astro
lib/node.astro
lib/replace.astro
lib/html.ts
View file @
0d87315
...
...
@@ -19,6 +19,12 @@ import {
import
{
parsedHtmlCache
,
selectorCache
,
findNodeCache
}
from
'./cache.js'
export
const
PSEUDO_ELEMENT
=
Symbol
(
'PSEUDO_ELEMENT'
)
export
const
PSEUDO_ELEMENTS
=
{
after
:
{
type
:
PSEUDO_ELEMENT
,
name
:
'::after'
},
before
:
{
type
:
PSEUDO_ELEMENT
,
name
:
'::before'
},
}
export
const
fixAttributes
=
(
attributes
,
options
=
{
mapClassname
:
true
})
=>
{
const
{
mapClassname
}
=
options
if
(
!
mapClassname
)
return
attributes
...
...
@@ -35,10 +41,10 @@ export const appendClasses = (node, extraClasses) => {
// TODO: implement a parent/child/element cache
const
nthChildPos
=
(
node
,
parent
)
=>
filterChildElements
(
parent
).
findIndex
((
child
)
=>
child
===
node
);
const
filterChildElementsMatcher
=
(
context
,
child
,
parent
,
i
)
=>
child
.
type
===
ELEMENT_NODE
const
filterChildElementsMatcher
=
(
context
,
child
,
parent
,
i
,
debug
,
special
)
=>
child
.
type
===
ELEMENT_NODE
type
NodePosition
=
number
|
'before'
|
'after'
type
Matcher
=
(
context
,
node
:
NodeType
,
parent
?:
NodeType
,
i
:
NodePosition
,
debug
:
number
)
=>
boolean
type
Matcher
=
(
context
,
node
:
NodeType
,
parent
?:
NodeType
,
i
:
NodePosition
,
debug
:
number
,
special
:
any
)
=>
boolean
type
MatcherProducer
=
()
=>
Matcher
type
AttrValueMatch
=
(
string
)
=>
string
...
...
@@ -64,16 +70,16 @@ const compileMatcher = (ast: AST, selector: string): MatcherProducer => {
const
n
=
Number
(
argument
)
if
(
!
Number
.
isNaN
(
n
))
{
// Simple variant, just a number
return
(
context
,
node
,
parent
,
i
,
debug
)
=>
{
return
(
context
,
node
,
parent
,
i
,
debug
,
special
)
=>
{
return
i
===
n
}
}
switch
(
argument
)
{
case
'odd'
:
return
(
context
,
node
,
parent
,
i
,
debug
)
=>
Math
.
abs
(
i
%
2
)
===
1
return
(
context
,
node
,
parent
,
i
,
debug
,
special
)
=>
Math
.
abs
(
i
%
2
)
===
1
case
'even'
:
return
(
context
,
node
,
parent
,
i
)
=>
i
%
2
===
0
return
(
context
,
node
,
parent
,
i
,
debug
,
special
)
=>
i
%
2
===
0
default
:
{
if
(
!
argument
)
throw
new
Error
(
`Unsupported empty nth-child selector!`
)
let
[
_
,
A
,
B
=
'0'
]
=
/^
\s
*
(?:(
-
?(?:\d
+
)?)
n
)?\s
*
\+?\s
*
(\d
+
)?\s
*$/gm
.
exec
(
argument
)
??
[]
...
...
@@ -90,7 +96,7 @@ const compileMatcher = (ast: AST, selector: string): MatcherProducer => {
nMatch
=
(
i
)
=>
{
const
n
=
(
i
-
b
)
/
a
;
return
n
!==
0
&&
Math
.
floor
(
n
)
===
n
}
}
}
return
(
context
,
node
,
parent
,
i
,
debug
)
=>
{
return
(
context
,
node
,
parent
,
i
,
debug
,
special
)
=>
{
return
nMatch
(
i
)
}
}
...
...
@@ -133,18 +139,18 @@ const compileMatcher = (ast: AST, selector: string): MatcherProducer => {
switch
(
ast
.
type
)
{
case
'list'
:
{
const
matchers
=
ast
.
list
.
map
(
s
=>
makeMatcher
(
s
))
return
(
context
,
node
,
parent
,
i
,
debug
)
=>
{
return
(
context
,
node
,
parent
,
i
,
debug
,
special
)
=>
{
for
(
const
matcher
of
matchers
)
{
if
(
!
matcher
(
context
,
node
,
parent
,
i
))
return
false
if
(
!
matcher
(
context
,
node
,
parent
,
i
,
debug
,
special
))
return
false
}
return
true
}
}
case
'compound'
:
{
const
matchers
=
ast
.
list
.
map
(
s
=>
makeMatcher
(
s
))
return
(
context
,
node
,
parent
,
i
,
debug
)
=>
{
return
(
context
,
node
,
parent
,
i
,
debug
,
special
)
=>
{
for
(
const
matcher
of
matchers
)
{
if
(
!
matcher
(
context
,
node
,
parent
,
i
))
return
false
if
(
!
matcher
(
context
,
node
,
parent
,
i
,
debug
,
special
))
return
false
}
return
true
}
...
...
@@ -155,7 +161,7 @@ const compileMatcher = (ast: AST, selector: string): MatcherProducer => {
const
leftCounter
=
counter
++
neededContext
[
leftCounter
]
=
()
=>
new
WeakSet
()
const
rightMatcher
=
makeMatcher
(
right
)
return
(
context
,
node
,
parent
,
i
,
debug
)
=>
{
return
(
context
,
node
,
parent
,
i
,
debug
,
special
)
=>
{
const
{
[
leftCounter
]:
leftMatches
}
=
context
if
(
leftMatcher
(
context
,
node
,
parent
,
i
,
debug
))
{
if
(
debug
)
console
.
log
(
'matched on left'
,
{
left
,
right
,
combinator
,
pos
,
parent
})
...
...
@@ -204,15 +210,15 @@ const compileMatcher = (ast: AST, selector: string): MatcherProducer => {
case
'type'
:
{
const
{
name
,
content
}
=
ast
if
(
content
===
'*'
)
return
(
context
,
node
,
parent
,
i
)
=>
true
return
(
context
,
node
,
parent
,
i
,
debug
)
=>
node
.
name
===
name
return
(
context
,
node
,
parent
,
i
,
debug
,
special
)
=>
node
.
name
===
name
}
case
'class'
:
{
const
{
name
}
=
ast
return
(
context
,
node
,
parent
,
i
,
debug
)
=>
node
.
attributes
?.[
'class'
]?.
split
(
/
\s
+/g
).
includes
(
name
)
return
(
context
,
node
,
parent
,
i
,
debug
,
special
)
=>
node
.
attributes
?.[
'class'
]?.
split
(
/
\s
+/g
).
includes
(
name
)
}
case
'id'
:
{
const
{
name
}
=
ast
return
(
context
,
node
,
parent
,
i
,
debug
)
=>
node
.
attributes
?.
id
===
name
return
(
context
,
node
,
parent
,
i
,
debug
,
special
)
=>
node
.
attributes
?.
id
===
name
}
case
'pseudo-class'
:
switch
(
ast
.
name
)
{
...
...
@@ -227,26 +233,26 @@ const compileMatcher = (ast: AST, selector: string): MatcherProducer => {
case
'where'
:
return
makeMatcher
(
ast
.
subtree
)
case
'root'
:
return
(
context
,
node
,
parent
,
i
)
=>
!
node
.
parent
return
(
context
,
node
,
parent
,
i
,
debug
,
special
)
=>
!
node
.
parent
case
'empty'
:
return
(
context
,
node
,
parent
,
i
,
debug
)
=>
{
return
(
context
,
node
,
parent
,
i
,
debug
,
special
)
=>
{
if
(
node
.
type
!==
ELEMENT_NODE
)
return
false
const
{
children
}
=
node
if
(
children
.
length
===
0
)
return
false
return
children
.
every
(
child
=>
child
.
type
===
TEXT_NODE
&&
child
.
value
.
trim
()
===
''
)
}
case
'first-child'
:
return
(
context
,
node
,
parent
,
i
,
debug
)
=>
{
return
(
context
,
node
,
parent
,
i
,
debug
,
special
)
=>
{
const
children
=
findChildren
(
context
,
parent
,
'ELEMENT'
,
filterChildElementsMatcher
)
return
children
[
0
]
==
node
}
case
'last-child'
:
return
(
context
,
node
,
parent
,
i
,
debug
)
=>
{
return
(
context
,
node
,
parent
,
i
,
debug
,
special
)
=>
{
const
children
=
findChildren
(
context
,
parent
,
'ELEMENT'
,
filterChildElementsMatcher
)
return
children
[
children
.
length
-
1
]
==
node
}
case
'only-child'
:
return
(
context
,
node
,
parent
,
i
,
debug
)
=>
{
return
(
context
,
node
,
parent
,
i
,
debug
,
special
)
=>
{
// TODO: This can break-early after it finds the second element
const
children
=
findChildren
(
context
,
parent
,
'ELEMENT'
,
filterChildElementsMatcher
)
return
children
.
length
===
1
...
...
@@ -269,7 +275,7 @@ const compileMatcher = (ast: AST, selector: string): MatcherProducer => {
childMatcher
=
filterChildElementsMatcher
}
return
(
context
,
node
,
parent
,
i
,
debug
)
=>
{
return
(
context
,
node
,
parent
,
i
,
debug
,
special
)
=>
{
const
children
=
findChildren
(
context
,
parent
,
childSelector
,
childMatcher
)
const
pos
=
children
.
indexOf
(
node
)
if
(
parent
?.
name
===
'body'
&&
pos
!==
-
1
)
{
...
...
@@ -281,7 +287,7 @@ const compileMatcher = (ast: AST, selector: string): MatcherProducer => {
}
case
'contains'
:
{
const
contentValueMatch
=
getAttrValueMatch
(
ast
.
argument
,
'*='
,
false
)
return
(
context
,
node
,
parent
,
i
,
debug
)
=>
{
return
(
context
,
node
,
parent
,
i
,
debug
,
special
)
=>
{
const
nodeText
=
getNodeText
(
node
)
return
contentValueMatch
(
nodeText
)
}
...
...
@@ -293,18 +299,18 @@ const compileMatcher = (ast: AST, selector: string): MatcherProducer => {
case
'attribute'
:
const
{
caseSensitive
,
name
,
value
,
operator
}
=
ast
const
attrValueMatch
=
getAttrValueMatch
(
value
,
operator
,
caseSensitive
)
return
(
context
,
node
,
parent
,
i
,
debug
)
=>
{
return
(
context
,
node
,
parent
,
i
,
debug
,
special
)
=>
{
const
{
attributes
:
{
[
name
]:
attrValue
}
=
{}
}
=
node
return
attrValueMatch
(
attrValue
)
}
case
'universal'
:
return
(
context
,
node
,
parent
,
i
,
debug
)
=>
true
return
(
context
,
node
,
parent
,
i
,
debug
,
special
)
=>
true
case
'pseudo-element'
:
switch
(
ast
.
name
)
{
case
'after'
:
return
(
context
,
node
,
parent
,
i
,
debug
)
=>
i
===
'after'
return
(
context
,
node
,
parent
,
i
,
debug
,
special
)
=>
special
===
PSEUDO_ELEMENTS
.
after
case
'before'
:
return
(
context
,
node
,
parent
,
i
,
debug
)
=>
i
===
'before'
return
(
context
,
node
,
parent
,
i
,
debug
,
special
)
=>
special
===
PSEUDO_ELEMENTS
.
before
default
:
console
.
error
(
'pseudo-class'
,
nodeUtil
.
inspect
({
selector
,
ast
},
{
depth
:
null
,
colors
:
true
}))
throw
new
Error
(
`Unknown pseudo-class:
${
ast
.
name
}
`
)
...
...
@@ -316,9 +322,9 @@ const compileMatcher = (ast: AST, selector: string): MatcherProducer => {
const
matcher
=
makeMatcher
(
ast
)
return
()
=>
{
const
context
=
neededContext
.
map
(
item
=>
item
())
const
nodeMatcher
=
(
node
,
parent
,
i
,
debug
)
=>
{
const
nodeMatcher
=
(
node
,
parent
,
i
,
debug
,
special
)
=>
{
//if (debug) console.log('starting to match', {node, context})
return
matcher
(
context
,
node
,
parent
,
i
,
debug
)
return
matcher
(
context
,
node
,
parent
,
i
,
debug
,
special
)
}
nodeMatcher
.
toString
=
()
=>
{
return
'[matcher:'
+
selector
+
']'
...
...
lib/match.astro
View file @
0d87315
...
...
@@ -2,7 +2,7 @@
import { ELEMENT_NODE, TEXT_NODE } from 'ultrahtml'
import Node from './node.astro'
const { props: { parent = null, node, index = 0, debug = 0, replacers, slotHandler } } = Astro
const { props: { parent = null, node, index = 0,
special,
debug = 0, replacers, slotHandler } } = Astro
const { name: Name, attributes } = node
if (debug) {
...
...
@@ -11,7 +11,7 @@ if (debug) {
let slotName
for (const [ matcher, handler ] of replacers) {
if (debug) console.log('attempting matcher', matcher.toString())
if (matcher(node, parent, index, false)) {
if (matcher(node, parent, index, false
, special
)) {
if (debug) console.log('matched')
slotName = handler
}
...
...
@@ -22,6 +22,6 @@ const [ Component, componentArgs ] = Array.isArray(slotName) ? slotName : []
---
{
Component ? (<Component {...componentArgs} debug={nextDebug} replacers={replacers} slotHandler={slotHandler}/>)
: slotName ? slotHandler(slotName, node)
: <Node parent={parent} node={node} index={index} debug={nextDebug} replacers={replacers} slotHandler={slotHandler}/>
: slotName ? slotHandler(slotName, node
, special
)
: <Node parent={parent} node={node} index={index} debug={nextDebug} replacers={replacers} slotHandler={slotHandler}
special={special}
/>
}
...
...
lib/node.astro
View file @
0d87315
...
...
@@ -2,6 +2,11 @@
import { COMMENT_NODE, DOCTYPE_NODE, DOCUMENT_NODE, ELEMENT_NODE, TEXT_NODE } from 'ultrahtml'
import type { NodeType } from 'ultrahtml'
import Children from './children.astro'
import Match from './match.astro'
import {
PSEUDO_ELEMENT,
PSEUDO_ELEMENTS,
} from './html.ts'
interface Props {
html?: string,
...
...
@@ -16,7 +21,7 @@ interface Props {
}
const { props: { parent = null, node, index = 0, debug = 0, replacers, slotHandler } } = Astro
const { props: { parent = null, node,
special,
index = 0, debug = 0, replacers, slotHandler } } = Astro
const { name: Name, attributes } = node
if (debug) {
...
...
@@ -25,12 +30,13 @@ if (debug) {
const nextDebug = debug ? debug - 1 : 0
---
{
node.type === DOCTYPE_NODE ? ''
special?.type === PSEUDO_ELEMENT ? ''
: node.type === DOCTYPE_NODE ? ''
: node.type === DOCUMENT_NODE ? <Children parent={node} children={node.children} debug={nextDebug} replacers={replacers} slotHandler={slotHandler}/>
: node.type === COMMENT_NODE ? <Fragment set:html={'<!-- ' + node.value + ' -->'}/>
: node.type === TEXT_NODE ? <Fragment set:html={node.value}/>
: node.type === ELEMENT_NODE ? (
node.isSelfClosingTag ? <Name {...attributes}/>
: <Name {...attributes}><
Children parent={node} children={node.children
} debug={nextDebug} replacers={replacers} slotHandler={slotHandler}/></Name>
: <Name {...attributes}><
Match parent={parent} node={node} special={PSEUDO_ELEMENTS.before} index={0} debug={nextDebug} replacers={replacers} slotHandler={slotHandler}/><Children parent={node} children={node.children} debug={nextDebug} replacers={replacers} slotHandler={slotHandler}/><Match parent={parent} node={node} special={PSEUDO_ELEMENTS.after} index={0
} debug={nextDebug} replacers={replacers} slotHandler={slotHandler}/></Name>
) : ''
}
...
...
lib/replace.astro
View file @
0d87315
...
...
@@ -19,8 +19,8 @@ const { debug = 0, replacements = {}, ...rest } = props
const replacers = Object.entries(replacements).map(([ selector, handler ]) => [ createMatcher(selector), handler ])
const slotHandler: SlotHandler = (slotName, node) => {
return Astro.slots.render(slotName, [ node, { slotHandler, replacers } ] )
const slotHandler: SlotHandler = (slotName, node
, special
) => {
return Astro.slots.render(slotName, [ node, { slotHandler, replacers
, special
} ] )
}
const nextDebug = debug ? debug - 1 : 0
---
...
...
Please
register
or
sign in
to post a comment