···11+# bsky_comments_widget
22+33+[](https://hex.pm/packages/bsky_comments_widget)
44+[](https://hexdocs.pm/bsky_comments_widget/)
55+66+```sh
77+gleam add bsky_comments_widget@1
88+```
99+```gleam
1010+import bsky_comments_widget
1111+1212+pub fn main() -> Nil {
1313+ // TODO: An example of the project in use
1414+}
1515+```
1616+1717+Further documentation can be found at <https://hexdocs.pm/bsky_comments_widget>.
1818+1919+## Development
2020+2121+```sh
2222+gleam run # Run the project
2323+gleam test # Run the tests
2424+```
···11+name = "bsky_comments_widget"
22+version = "1.0.0"
33+target = "javascript"
44+55+# Fill out these fields if you intend to generate HTML documentation or publish
66+# your project to the Hex package manager.
77+#
88+description = "a web component that shows the replies to a linked bsky post as comments."
99+licences = ["MIT"]
1010+repository = { type = "tangled", user = "lekkice.moe", repo = "bsky_comments_widget" }
1111+# links = [{ title = "Website", href = "" }]
1212+#
1313+# For a full reference of all the available options, you can have a look at
1414+# https://gleam.run/writing-gleam/gleam-toml/.
1515+1616+[dependencies]
1717+gleam_stdlib = ">= 0.44.0 and < 2.0.0"
1818+lustre = ">= 5.6.0 and < 6.0.0"
1919+rsvp = ">= 1.2.0 and < 2.0.0"
2020+gleam_fetch = ">= 1.3.1 and < 2.0.0"
2121+gleam_http = ">= 4.3.0 and < 5.0.0"
2222+gleam_javascript = ">= 1.0.0 and < 2.0.0"
2323+gleam_json = ">= 3.1.0 and < 4.0.0"
2424+gleam_regexp = ">= 1.1.1 and < 2.0.0"
2525+2626+[dev_dependencies]
2727+gleeunit = ">= 1.0.0 and < 2.0.0"
···11+// hacky relative path, since the path is resolved from `build/`. could be improved?
22+import widgetStyles from "../../../../../src/bsky_comments_widget.css?inline"
33+44+export function getCss() {
55+ return widgetStyles
66+}
+3
src/main.ts
···11+import { register } from "./widget.gleam"
22+33+register()
+127
src/models.gleam
···11+import gleam/dynamic/decode
22+33+pub type Post {
44+ Post(
55+ author: Author,
66+ uri: String,
77+ text: String,
88+ embed: Embed,
99+ replies: List(Post),
1010+ likes: Int,
1111+ reply_count: Int,
1212+ created_at: String,
1313+ )
1414+}
1515+1616+pub type Author {
1717+ Author(did: String, handle: String, display_name: String, avatar_url: String)
1818+}
1919+2020+pub type Embed {
2121+ Images(images: List(Image))
2222+ Video(thumb: String)
2323+ External(description: String, thumb: String, title: String, uri: String)
2424+ NoEmbed
2525+}
2626+2727+pub type Image {
2828+ Image(fullsize: String, alt: String, aspect_ratio: #(Int, Int))
2929+}
3030+3131+pub fn thread_response_decoder() -> decode.Decoder(Post) {
3232+ use thread <- decode.field("thread", thread_view_decoder())
3333+ decode.success(thread)
3434+}
3535+3636+fn thread_view_decoder() -> decode.Decoder(Post) {
3737+ use post <- decode.field("post", post_content_decoder())
3838+ use replies <- decode.optional_field(
3939+ "replies",
4040+ [],
4141+ decode.list(thread_view_decoder()),
4242+ )
4343+4444+ decode.success(Post(
4545+ author: post.author,
4646+ uri: post.uri,
4747+ text: post.text,
4848+ embed: post.embed,
4949+ replies: replies,
5050+ likes: post.likes,
5151+ reply_count: post.reply_count,
5252+ created_at: post.created_at,
5353+ ))
5454+}
5555+5656+fn post_content_decoder() -> decode.Decoder(Post) {
5757+ use author <- decode.field("author", author_decoder())
5858+ use uri <- decode.field("uri", decode.string)
5959+ use text <- decode.subfield(["record", "text"], decode.string)
6060+ use embed <- decode.optional_field("embed", NoEmbed, embed_decoder())
6161+ use likes <- decode.field("likeCount", decode.int)
6262+ use reply_count <- decode.field("replyCount", decode.int)
6363+ use created_at <- decode.subfield(["record", "createdAt"], decode.string)
6464+6565+ // replies get populated by thread_view_decoder()
6666+ decode.success(Post(
6767+ author:,
6868+ uri:,
6969+ text:,
7070+ embed:,
7171+ replies: [],
7272+ likes:,
7373+ reply_count:,
7474+ created_at:,
7575+ ))
7676+}
7777+7878+fn author_decoder() -> decode.Decoder(Author) {
7979+ use did <- decode.field("did", decode.string)
8080+ use handle <- decode.field("handle", decode.string)
8181+ use display_name <- decode.field("displayName", decode.string)
8282+ use avatar_url <- decode.field("avatar", decode.string)
8383+ decode.success(Author(did:, handle:, display_name:, avatar_url:))
8484+}
8585+8686+fn embed_decoder() -> decode.Decoder(Embed) {
8787+ use type_ <- decode.field("$type", decode.string)
8888+8989+ case type_ {
9090+ "app.bsky.embed.images#view" -> {
9191+ use images <- decode.field("images", decode.list(image_decoder()))
9292+ decode.success(Images(images:))
9393+ }
9494+9595+ "app.bsky.embed.external#view" -> {
9696+ use ext <- decode.field("external", {
9797+ use description <- decode.field("description", decode.string)
9898+ use thumb <- decode.field("thumb", decode.string)
9999+ use title <- decode.field("title", decode.string)
100100+ use uri <- decode.field("uri", decode.string)
101101+ decode.success(#(description, thumb, title, uri))
102102+ })
103103+ let #(description, thumb, title, uri) = ext
104104+ decode.success(External(description:, thumb:, title:, uri:))
105105+ }
106106+107107+ "app.bsky.embed.video#view" -> {
108108+ use thumb <- decode.field("thumbnail", decode.string)
109109+110110+ decode.success(Video(thumb:))
111111+ }
112112+113113+ // embed type that's not implemented right now
114114+ _ -> decode.success(NoEmbed)
115115+ }
116116+}
117117+118118+fn image_decoder() -> decode.Decoder(Image) {
119119+ use fullsize <- decode.field("fullsize", decode.string)
120120+ use alt <- decode.field("alt", decode.string)
121121+ use aspect_ratio <- decode.field("aspectRatio", {
122122+ use width <- decode.field("width", decode.int)
123123+ use height <- decode.field("height", decode.int)
124124+ decode.success(#(width, height))
125125+ })
126126+ decode.success(Image(fullsize:, alt:, aspect_ratio:))
127127+}