···11name = "chilp"
22description = "Allows you to use Mastodon comments on your Lustre blog."
33version = "1.0.0"
44+gleam = ">= 1.15.0"
45licences = ["Apache-2.0"]
56repository = { type = "tangled", user = "did:plc:jgtfsmv25thfs4zmydtbccnn", repo = "chilp" }
66-# links = [{ title = "Website", href = "" }]
77+documentation.pages = [{title = "Changelog", path = "changelog.html",source = "./changelog.md" }]
88+79810[dependencies]
911gleam_stdlib = ">= 0.44.0 and < 2.0.0"
+1-5
src/chilp.gleam
···33/// Widget component!
44/// Before adding this component make sure to call `widget.register()` to register it!
55///
66-/// This component adds in inline CSS, to not do this, use `widget.element("bla", "111", False)` instead.
77-pub fn widget(instance instance: String, post_id post: String) {
88- todo
99- // widget.element(instance:, post_id: post, with_styles: True)
1010-}
66+pub const widget = widget.element
+213-15
src/chilp/widget.gleam
···33import chilp/api_typing/new.{type BskyThreadReply, type MastodonDescendant}
44import chilp/widget/anchors
55import gleam/bool
66+import gleam/dynamic/decode
67import gleam/option.{type Option, None, Some}
78import gleam/result
89import gleam/string
···1112import lustre/component
1213import lustre/effect.{type Effect}
1314import lustre/element.{type Element}
1515+import lustre/element/html
1416import rsvp
15171618// MAIN ------------------------------------------------------------------------
···2325 component.on_attribute_change("mastodon-anchor", fn(value) {
2426 use <- bool.guard(when: value == "", return: Ok(MastodonUnAnchored))
2527 value
2626- |> string.split_once(":")
2828+ |> string.split_once("\\")
2729 |> result.map(fn(a) { MastodonAnchored(a.0, a.1) })
2830 }),
2931 component.on_attribute_change("bluesky-anchor", fn(value) {
3032 use <- bool.guard(when: value == "", return: Ok(BskyUnAnchored))
3133 value
3232- |> string.split_once(":")
3434+ |> string.split_once("\\")
3335 |> result.map(fn(a) { BskyAnchored(a.0, a.1) })
3436 }),
3537 ])
···4547 "comment-widget",
4648 [
4749 attribute.attribute("mastodon-anchor", case mastodon_anchor {
4848- Some(anchor) -> anchor.instance <> ":" <> anchor.postid
5050+ Some(anchor) -> anchor.instance <> "\\" <> anchor.postid
4951 None -> ""
5052 }),
5153 attribute.attribute("bluesky-anchor", case bsky_anchor {
5252- Some(anchor) -> anchor.did <> ":" <> anchor.postid
5454+ Some(anchor) -> anchor.did <> "\\" <> anchor.postid
5355 None -> ""
5456 }),
5557 ],
···6365 Model(
6466 mastodon_anchor: Option(anchors.Mastodon),
6567 cached_mastodon_descendants: List(MastodonDescendant),
6868+ mastodon_op_username: String,
6669 bluesky_anchor: Option(anchors.Bluesky),
6770 cached_bluesky_replies: List(BskyThreadReply),
7171+ bsky_op_handle: String,
6872 all_stopping_error: Option(String),
6973 cached_coalesced_view: List(new.CoalescedView),
7074 /// For those not using DaisyUI, using it's hacky way of creating tabs is... hacky.
···7983 Model(
8084 mastodon_anchor: None,
8185 cached_mastodon_descendants: [],
8686+ mastodon_op_username: "username",
8287 bluesky_anchor: None,
8388 cached_bluesky_replies: [],
8989+ bsky_op_handle: "",
8490 all_stopping_error: None,
8591 cached_coalesced_view: [],
8692 open_tab: 1,
···100106 BskyIncomingThreadView(new.BskyThreadView)
101107 IncomingCoalescedView(List(new.CoalescedView))
102108 MastodonIncomingStatus(new.MastodonStatusContext)
109109+ MastodonIncomingOpUsername(String)
103110}
104111105112fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) {
···112119 Model(..model, mastodon_anchor: None),
113120 effect.none(),
114121 )
115115- MastodonAnchored(instance:, post:) -> #(
116116- Model(
117117- ..model,
118118- mastodon_anchor: Some(anchors.Mastodon(instance:, postid: post)),
119119- ),
120120- refresh_mastodon(model),
122122+ MastodonAnchored(instance:, post:) -> {
123123+ let model =
124124+ Model(
125125+ ..model,
126126+ mastodon_anchor: Some(anchors.Mastodon(instance:, postid: post)),
127127+ )
128128+ #(
129129+ model,
130130+ effect.batch([get_username_mastodon(model), refresh_mastodon(model)]),
131131+ )
132132+ }
133133+ MastodonIncomingOpUsername(mastodon_op_username) -> #(
134134+ Model(..model, mastodon_op_username: echo mastodon_op_username),
135135+ effect.none(),
121136 )
122137 MastodonIncomingStatus(status) -> {
123138 #(
···133148 ..model,
134149 bluesky_anchor: Some(anchors.Bluesky(did:, postid: post)),
135150 )
136136- #(model, refresh_bsky(model))
151151+ #(model, effect.batch([refresh_bsky(model)]))
137152 }
138153 BskyIncomingThreadView(threadview) -> {
139154 #(
···150165 effect.none(),
151166 )
152167 }
168168+ |> echo
153169}
154170155171// VIEW ------------------------------------------------------------------------
156172157173fn view(model: Model) -> Element(Msg) {
158158- todo
174174+ case model.all_stopping_error {
175175+ None -> view_normal(model)
176176+ Some(error) -> error_view(error)
177177+ }
178178+}
179179+180180+fn view_normal(model: Model) -> Element(Msg) {
181181+ html.div([attribute.class("chilp-widget")], [
182182+ html.div([attribute.class("widget ")], [
183183+ html.h1([attribute.class("widget-header h1 ")], [html.text("Comments")]),
184184+ html.p([attribute.class("subheader ")], [
185185+ html.text("Linked to "),
186186+ case model.bluesky_anchor, model.mastodon_anchor {
187187+ Some(bsky), Some(masto) -> {
188188+ [
189189+ html.a(
190190+ [
191191+ attribute.class("text-purple"),
192192+ // https://pony.social/@strawmelonjuice/115911235653686237
193193+ attribute.href(
194194+ "https://"
195195+ <> masto.instance
196196+ <> "/@"
197197+ <> model.mastodon_op_username
198198+ <> "/"
199199+ <> masto.postid,
200200+ ),
201201+ attribute.class("post-link "),
202202+ ],
203203+ [html.text("this post")],
204204+ ),
205205+ html.text(" on Mastodon, and to "),
206206+ html.a(
207207+ [
208208+ attribute.href(
209209+ "https://bsky.app/profile/"
210210+ <> bsky.did
211211+ <> "/post/"
212212+ <> bsky.postid,
213213+ ),
214214+ attribute.class("post-link "),
215215+ ],
216216+ [html.text("this post")],
217217+ ),
218218+ html.text(" on Bluesky."),
219219+ ]
220220+ }
221221+ None, Some(masto) -> {
222222+ [
223223+ html.a(
224224+ [
225225+ attribute.href(
226226+ "https://"
227227+ <> masto.instance
228228+ <> "/"
229229+ <> model.mastodon_op_username
230230+ <> "/"
231231+ <> masto.postid,
232232+ ),
233233+ attribute.class("post-link "),
234234+ ],
235235+ [html.text("this post")],
236236+ ),
237237+ html.text(" on Mastodon."),
238238+ ]
239239+ }
240240+ Some(bsky), None -> {
241241+ [
242242+ html.a(
243243+ [
244244+ attribute.href(
245245+ "https://bsky.app/profile/"
246246+ <> bsky.did
247247+ <> "/post/"
248248+ <> bsky.postid,
249249+ ),
250250+ attribute.class("post-link "),
251251+ ],
252252+ [html.text("this post")],
253253+ ),
254254+ html.text(" on Bluesky."),
255255+ ]
256256+ }
257257+ None, None -> [
258258+ error_view("No comment backends are configured for this widget."),
259259+ ]
260260+ }
261261+ |> element.fragment,
262262+ ]),
263263+ html.form([attribute.class("go-reply-form ")], [
264264+ html.div([attribute.class("input-group")], [
265265+ html.label(
266266+ [
267267+ attribute.for("userinstance"),
268268+ attribute.class("go-reply-label"),
269269+ ],
270270+ [
271271+ html.text("Enter your instance adress to reply or "),
272272+ html.a(
273273+ [
274274+ attribute.href("https://mastodon.social/auth/sign_up"),
275275+ attribute.class("post-link "),
276276+ ],
277277+ [html.text("create an account")],
278278+ ),
279279+ html.text("!"),
280280+ ],
281281+ ),
282282+ html.p(
283283+ [
284284+ attribute.for("userinstance"),
285285+ attribute.class("or-create-an-account-disclaimer"),
286286+ ],
287287+ [
288288+ html.text(
289289+ "on an instance reccommended by this site... or one you pick yourself!",
290290+ ),
291291+ ],
292292+ ),
293293+ html.div([attribute.class("form-controls")], [
294294+ html.input([
295295+ attribute.type_("text"),
296296+ attribute.required(True),
297297+ attribute.placeholder("todon.nl"),
298298+ attribute.pattern("^([a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,}$"),
299299+ attribute.name("userinstance"),
300300+ attribute.class("go-reply-form-input "),
301301+ ]),
302302+ html.button(
303303+ [
304304+ attribute.type_("submit"),
305305+ attribute.class("go-reply-form-button "),
306306+ ],
307307+ [html.text("Go reply")],
308308+ ),
309309+ ]),
310310+ ]),
311311+ ]),
312312+ html.section([], []),
313313+ ]),
314314+ ])
315315+}
316316+317317+fn error_view(error: String) {
318318+ // todo: Style dis.
319319+ element.text("AN ERROR OCCURED:" <> error)
159320}
160321161322// EFFECTS ---------------------------------------------------------------------
···204365 }
205366}
206367368368+fn get_username_mastodon(model: Model) -> Effect(Msg) {
369369+ case model.mastodon_anchor {
370370+ Some(anchor) -> {
371371+ let url =
372372+ "https://" <> anchor.instance <> "/api/v1/statuses/" <> anchor.postid
373373+374374+ let handler =
375375+ rsvp.expect_json(
376376+ {
377377+ use user <- decode.subfield(["account", "username"], decode.string)
378378+ decode.success(user)
379379+ },
380380+ fn(response) {
381381+ case response {
382382+ Ok(username) -> MastodonIncomingOpUsername(username)
383383+ Error(rsvperror) ->
384384+ case rsvperror {
385385+ rsvp.UnhandledResponse(_) | rsvp.JsonError(_) | rsvp.BadBody ->
386386+ AllStoppingError(
387387+ "The response body we got back from Mastodon was misformed.",
388388+ )
389389+ rsvp.BadUrl(_) ->
390390+ AllStoppingError(
391391+ "The API call to Mastodon failed. Did you enter the instance and post id correctly?",
392392+ )
393393+ rsvp.HttpError(_) | rsvp.NetworkError ->
394394+ AllStoppingError("Could not fetch comments from Mastodon.")
395395+ }
396396+ }
397397+ },
398398+ )
399399+ rsvp.get(url, handler)
400400+ }
401401+ None -> effect.none()
402402+ }
403403+}
404404+207405fn refresh_mastodon(model: Model) -> Effect(Msg) {
208406 case model.mastodon_anchor {
209407 Some(anchor) -> {
···224422 case rsvperror {
225423 rsvp.UnhandledResponse(_) | rsvp.JsonError(_) | rsvp.BadBody ->
226424 AllStoppingError(
227227- "The response body we got back from Bluesky was misformed.",
425425+ "The response body we got back from Mastodon was misformed.",
228426 )
229427 rsvp.BadUrl(_) ->
230428 AllStoppingError(
231231- "The API call to Bluesky failed. Did you enter the DID and post id correctly?",
429429+ "The API call to Mastodon failed. Did you enter the instance and post id correctly?",
232430 )
233431 rsvp.HttpError(_) | rsvp.NetworkError ->
234234- AllStoppingError("Could not fetch comments from Bluesky.")
432432+ AllStoppingError("Could not fetch comments from Mastodon.")
235433 }
236434 }
237435 },