Thread viewer for Bluesky

type safety refactors in EmbedComponent

Changed files
+46 -47
+46 -47
embed_component.js
··· 4 5 class EmbedComponent { 6 7 - /** @param {Post} post, @param {object} embed */ 8 constructor(post, embed) { 9 this.post = post; 10 this.embed = embed; ··· 13 /** @returns {AnyElement} */ 14 15 buildElement() { 16 - let wrapper, quoteView, mediaView; 17 - 18 - switch (this.embed.constructor) { 19 - case RawRecordEmbed: 20 - quoteView = this.quotedPostPlaceholder(); 21 this.loadQuotedPost(this.embed.record.uri, quoteView); 22 return quoteView; 23 24 - case RawRecordWithMediaEmbed: 25 - wrapper = $tag('div'); 26 27 - mediaView = new EmbedComponent(this.post, this.embed.media).buildElement(); 28 - quoteView = this.quotedPostPlaceholder(); 29 this.loadQuotedPost(this.embed.record.uri, quoteView); 30 31 wrapper.append(mediaView, quoteView); 32 return wrapper; 33 34 - case InlineRecordEmbed: 35 - return this.buildQuotedPostElement(); 36 37 - case InlineRecordWithMediaEmbed: 38 - wrapper = $tag('div'); 39 40 - mediaView = new EmbedComponent(this.post, this.embed.media).buildElement(); 41 - quoteView = this.buildQuotedPostElement(); 42 43 wrapper.append(mediaView, quoteView); 44 return wrapper; 45 46 - case RawImageEmbed: 47 - case InlineImageEmbed: 48 - return this.buildImagesComponent(); 49 50 - case RawLinkEmbed: 51 - case InlineLinkEmbed: 52 - return this.buildLinkComponent(); 53 54 - default: 55 return $tag('p', { text: `[${this.embed.type}]` }); 56 } 57 } ··· 64 }); 65 } 66 67 - /** @returns {AnyElement} */ 68 69 - buildQuotedPostElement() { 70 let div = $tag('div.quote-embed'); 71 72 - if (this.embed.post instanceof Post || this.embed.post instanceof BlockedPost) { 73 - let postView = new PostComponent(this.embed.post).buildElement('quote'); 74 div.appendChild(postView); 75 - } else if (this.embed.post instanceof MissingPost) { 76 - let postView = new PostComponent(this.embed.post).buildElement('quote'); 77 div.appendChild(postView); 78 - } else if (this.embed.post instanceof FeedGeneratorRecord) { 79 - return this.buildFeedGeneratorView(this.embed.post); 80 - } else if (this.embed.post instanceof UserListRecord) { 81 - return this.buildUserListView(this.embed.post); 82 } else { 83 - let p = $tag('p', { text: `[${this.embed.post.type}]` }); 84 div.appendChild(p); 85 } 86 87 return div; 88 } 89 90 - /** @returns {AnyElement} */ 91 92 - buildLinkComponent() { 93 let hostname; 94 95 try { 96 - hostname = new URL(this.embed.url).hostname; 97 } catch (error) { 98 console.log("Invalid URL:" + error); 99 100 - let a = $tag('a', { href: this.embed.url, text: this.embed.title || this.embed.url }); 101 let p = $tag('p'); 102 p.append('[Link: ', a, ']'); 103 return p; 104 } 105 106 - let a = $tag('a.link-card', { href: this.embed.url, target: '_blank' }); 107 let box = $tag('div'); 108 109 let domain = $tag('p.domain', { text: hostname }); 110 - let title = $tag('h2', { text: this.embed.title }); 111 box.append(domain, title); 112 113 - if (this.embed.description) { 114 let text; 115 116 - if (this.embed.description.length <= 300) { 117 - text = this.embed.description; 118 } else { 119 - text = this.embed.description.slice(0, 300) + '…'; 120 } 121 122 box.append($tag('p.description', { text: text })); ··· 213 return `https://bsky.app/profile/${repo}/lists/${rkey}`; 214 } 215 216 - /** @returns {AnyElement} */ 217 218 - buildImagesComponent() { 219 let wrapper = $tag('div'); 220 221 - for (let image of this.embed.images) { 222 let p = $tag('p'); 223 p.append('['); 224
··· 4 5 class EmbedComponent { 6 7 + /** @param {Post} post, @param {Embed} embed */ 8 constructor(post, embed) { 9 this.post = post; 10 this.embed = embed; ··· 13 /** @returns {AnyElement} */ 14 15 buildElement() { 16 + if (this.embed instanceof RawRecordEmbed) { 17 + let quoteView = this.quotedPostPlaceholder(); 18 this.loadQuotedPost(this.embed.record.uri, quoteView); 19 return quoteView; 20 21 + } else if (this.embed instanceof RawRecordWithMediaEmbed) { 22 + let wrapper = $tag('div'); 23 24 + let mediaView = new EmbedComponent(this.post, this.embed.media).buildElement(); 25 + let quoteView = this.quotedPostPlaceholder(); 26 this.loadQuotedPost(this.embed.record.uri, quoteView); 27 28 wrapper.append(mediaView, quoteView); 29 return wrapper; 30 31 + } else if (this.embed instanceof InlineRecordEmbed) { 32 + return this.buildQuotedPostElement(this.embed); 33 34 + } else if (this.embed instanceof InlineRecordWithMediaEmbed) { 35 + let wrapper = $tag('div'); 36 37 + let mediaView = new EmbedComponent(this.post, this.embed.media).buildElement(); 38 + let quoteView = this.buildQuotedPostElement(this.embed); 39 40 wrapper.append(mediaView, quoteView); 41 return wrapper; 42 43 + } else if (this.embed instanceof RawImageEmbed || this.embed instanceof InlineImageEmbed) { 44 + return this.buildImagesComponent(this.embed); 45 46 + } else if (this.embed instanceof RawLinkEmbed || this.embed instanceof InlineLinkEmbed) { 47 + return this.buildLinkComponent(this.embed); 48 49 + } else { 50 return $tag('p', { text: `[${this.embed.type}]` }); 51 } 52 } ··· 59 }); 60 } 61 62 + /** @param {InlineRecordEmbed | InlineRecordWithMediaEmbed} embed, @returns {AnyElement} */ 63 64 + buildQuotedPostElement(embed) { 65 let div = $tag('div.quote-embed'); 66 67 + if (embed.post instanceof Post || embed.post instanceof BlockedPost) { 68 + let postView = new PostComponent(embed.post).buildElement('quote'); 69 div.appendChild(postView); 70 + 71 + } else if (embed.post instanceof MissingPost) { 72 + let postView = new PostComponent(embed.post).buildElement('quote'); 73 div.appendChild(postView); 74 + 75 + } else if (embed.post instanceof FeedGeneratorRecord) { 76 + return this.buildFeedGeneratorView(embed.post); 77 + 78 + } else if (embed.post instanceof UserListRecord) { 79 + return this.buildUserListView(embed.post); 80 + 81 } else { 82 + let p = $tag('p', { text: `[${embed.post.type}]` }); 83 div.appendChild(p); 84 } 85 86 return div; 87 } 88 89 + /** @params {RawLinkEmbed | InlineLinkEmbed} embed, @returns {AnyElement} */ 90 91 + buildLinkComponent(embed) { 92 let hostname; 93 94 try { 95 + hostname = new URL(embed.url).hostname; 96 } catch (error) { 97 console.log("Invalid URL:" + error); 98 99 + let a = $tag('a', { href: embed.url, text: embed.title || embed.url }); 100 let p = $tag('p'); 101 p.append('[Link: ', a, ']'); 102 return p; 103 } 104 105 + let a = $tag('a.link-card', { href: embed.url, target: '_blank' }); 106 let box = $tag('div'); 107 108 let domain = $tag('p.domain', { text: hostname }); 109 + let title = $tag('h2', { text: embed.title }); 110 box.append(domain, title); 111 112 + if (embed.description) { 113 let text; 114 115 + if (embed.description.length <= 300) { 116 + text = embed.description; 117 } else { 118 + text = embed.description.slice(0, 300) + '…'; 119 } 120 121 box.append($tag('p.description', { text: text })); ··· 212 return `https://bsky.app/profile/${repo}/lists/${rkey}`; 213 } 214 215 + /** @params {RawImageEmbed | InlineImageEmbed} embed, @returns {AnyElement} */ 216 217 + buildImagesComponent(embed) { 218 let wrapper = $tag('div'); 219 220 + for (let image of embed.images) { 221 let p = $tag('p'); 222 p.append('['); 223