1+ const html = String . raw
12const styles = new CSSStyleSheet ( )
23styles . replaceSync ( `
3- :host {
4+ :host(:not(:--loading)) {
45 display: grid;
5- border: 1px solid grey;
6+ max-inline-size: 36em;
7+ padding: 0.5em;
8+ gap: 0.5em;
9+ border: 0.0625em solid grey;
10+ border-radius: 0.5em;
611 grid-template:
7- "avatar author_name author_name" 25px
8- "avatar handle handle" 25px
9- "content content content" max-content
10- "backlink backlink backlink" 25px
11- / 60px auto auto;
12+ "avatar author-link author-link" max-content
13+ "content content content" max-content
14+ "backlink backlink backlink" max-content
15+ / min-content auto auto;
1216}
1317
1418[part="avatar"] {
19+ max-inline-size: 3.125em;
20+ aspect-ratio: 1;
1521 grid-area: avatar;
16- border-radius: 4px ;
22+ border-radius: 0.25em ;
1723}
18- [part="author_name"] {
19- grid-area: author_name
24+ [part="author-link"] {
25+ display: grid;
26+ align-self: center;
27+ grid-area: author-link
2028}
2129[part="handle"] {
2230 grid-area: handle
2331}
2432[part="content"] {
25- grid-area: content
33+ grid-area: content;
34+ line-height: 1.5;
35+ }
36+ [part="content"] > * {
37+ margin-block: 0;
38+ }
39+ [part="content"] > * + * {
40+ margin-block-start: 0.5em;
2641}
2742[part="backlink"] {
2843 grid-area: backlink
@@ -41,6 +56,7 @@ styles.replaceSync(`
4156 */
4257class TootEmbedElement extends HTMLElement {
4358 static observeAttributes = [ 'src' ]
59+ #internals! : ElementInternals
4460 #renderRoot! : ShadowRoot
4561
4662 get src ( ) {
@@ -55,12 +71,16 @@ class TootEmbedElement extends HTMLElement {
5571 return this . #renderRoot. querySelector ( '[part=content]' )
5672 }
5773
74+ get #authorLinkPart( ) {
75+ return this . #renderRoot. querySelector ( '[part=author-link]' )
76+ }
77+
5878 get #authorNamePart( ) {
59- return this . #renderRoot. querySelector ( '[part=author_name ]' )
79+ return this . #renderRoot. querySelector ( '[part=author-name ]' )
6080 }
6181
6282 get #authorHandlePart( ) {
63- return this . #renderRoot. querySelector ( '[part=author_handle ]' )
83+ return this . #renderRoot. querySelector ( '[part=author-handle ]' )
6484 }
6585
6686 get #avatarPart( ) {
@@ -71,17 +91,11 @@ class TootEmbedElement extends HTMLElement {
7191 return this . #renderRoot. querySelector ( '[part=backlink]' )
7292 }
7393
74-
7594 connectedCallback ( ) : void {
76- this . #renderRoot = this . attachShadow ( { mode : 'open' } )
95+ this . #internals = this . attachInternals ( )
96+ this . #internals. role = 'article'
97+ this . #renderRoot = this . attachShadow ( { mode : 'open' } )
7798 this . #renderRoot. adoptedStyleSheets . push ( styles )
78- this . #renderRoot. innerHTML = `
79- <img part="avatar" width="50">
80- <span part="author_name"></span>
81- <span part="author_handle"></span>
82- <div part="content"></div>
83- <a part="backlink" href="">Original Toot</a>
84- `
8599 if ( this . querySelector ( 'script[type="application/json"]' ) ) {
86100 return this . #render( JSON . parse ( this . querySelector ( 'script[type="application/json"]' ) . textContent ) )
87101 }
@@ -96,21 +110,49 @@ class TootEmbedElement extends HTMLElement {
96110 }
97111
98112 async load ( ) {
99- const src = new URL ( this . src , window . location . origin )
100- const response = await fetch ( src )
113+ this . #internals. states . add ( '--loading' )
114+ const { tootId } = this . #useParams( ) ;
115+ const apiURL = new URL ( `/api/v1/statuses/${ tootId } ` , this . src )
116+ const response = await fetch ( apiURL )
101117 console . log ( response )
118+
102119 this . #render( await response . json ( ) )
120+ this . #internals. states . delete ( '--loading' )
103121 }
104122
105- #render( json : unknown ) {
106- const { account, url, content} = json
123+ #render( json ) {
124+ const { account, url, content } = json
107125 console . log ( json )
108- this . #authorNamePart. innerHTML = account . display_name
109126 const handleURL = new URL ( account . url )
110- this . #authorHandlePart. innerHTML = `${ handleURL . pathname . slice ( 1 ) } @${ handleURL . hostname } `
111- this . #avatarPart. src = account . avatar
112- this . #contentPart. innerHTML = content
113- this . #backlinkPart. href = url
127+ const { handle} = this . #useParams( )
128+ this . #renderRoot. innerHTML = html `
129+ < img part ="avatar " src ="${ account . avatar } " alt ="">
130+ < a part ="author-link " href ="${ handleURL . href } ">
131+ < span part ="author-name "> ${ account . display_name } </ span >
132+ < span part ="author-handle "> @${ handle } @${ handleURL . hostname } </ span >
133+ </ a >
134+ < div part ="content ">
135+ ${ content }
136+ </ div >
137+ < a part ="backlink " href ="${ url } " rel ="bookmark "> Original Toot</ a >
138+ `
139+ this . #internals. states . add ( '--ready' )
140+ this . #internals. ariaLabel = `${ this . #authorLinkPart. textContent } ${ this . #contentPart. textContent } `
141+ }
142+
143+ // URLPattern only works in Chromium right now.
144+ // Could refactor this to use RegExp for compatibility
145+ /* @ts -ignore */
146+ #shortPattern = new URLPattern ( { pathname : '/@:handle/:tootId(\\d+)' } )
147+ /* @ts -ignore */
148+ #longPattern = new URLPattern ( { pathname : '/users/:handle/statuses/:tootId(\\d+)' } )
149+ // Toot URLs can have two different formats:
150+ // 1. https://indieweb.social/@keithamus/109524390152251545
151+ // 2. https://indieweb.social/users/keithamus/statuses/109524390152251545
152+ #useParams( ) : { [ key : string ] : string } {
153+ const groups = ( this . #shortPattern. exec ( this . src ) ?? this . #longPattern. exec ( this . src ) ) ?. pathname . groups ;
154+ if ( groups ) return groups ;
155+ throw `This doesn’t seem to be a toot URL: ${ this . src } ` ;
114156 }
115157}
116158
0 commit comments