Allows you to use Mastodon and Bluesky comments on your Lustre blog hexdocs.pm/chilp/
blog gleam lustre indieweb mastodon bluesky comments
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Preparing for a release?!

+521 -270
+268 -9
README.md
··· 6 6 ```sh 7 7 gleam add chilp@1 8 8 ``` 9 - ```gleam 10 - import chilp 11 9 12 - pub fn main() -> Nil { 13 - // TODO: An example of the project in use 14 - } 15 - ``` 10 + ## Examples 11 + 12 + - [lustre_chilp_app](https://forge.strawmelonjuice.com/strawmelonjuice/chilp/src/branch/main/examples/lustre_chilp_app) 13 + - [lustre_chilp_app_nocomponent](https://forge.strawmelonjuice.com/strawmelonjuice/chilp/src/branch/main/examples/lustre_chilp_app_nocomponent) 16 14 17 15 Further documentation can be found at <https://hexdocs.pm/chilp>. 18 16 19 17 ## Styling 20 18 21 - <!--You may want to look at [styles.css](https://forge.strawmelonjuice.com/strawmelonjuice/chilp/src/branch/main/examples/lustre_chilp_app_autoloading/assets/styles.css) to see how to style your own comment sections!--> 19 + When using `chilp.widget()`, CSS to style it is automatically embedded. To say no to that use `widget.element` or create your own widget from `chilp/widget/base`! 20 + 21 + That said, 22 + ```gleam 23 + io.print(widget.css) 24 + ``` 25 + 26 + Yields you: 27 + 28 + ```css 29 + .chilp-widget { 30 + --highlight: #595aff; 31 + transition: all 0.5s ease; 32 + overflow: hidden; 33 + ::selection { 34 + background-color: rgba(89, 90, 255, 0.2); 35 + color: var(--highlight); 36 + } 37 + 38 + .widget::-webkit-scrollbar { 39 + width: 6px; 40 + } 41 + .widget::-webkit-scrollbar-thumb { 42 + background-color: #e2e8f0; 43 + border-radius: 10px; 44 + } 45 + 46 + .widget { 47 + max-width: 600px; 48 + margin: 2rem auto; 49 + background-color: floralwhite; 50 + font-family: sans-serif; 51 + padding: 2rem; 52 + border-radius: 12px; 53 + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); 54 + } 55 + 56 + .widget > .btn-get-comments { 57 + display: block; 58 + width: 100%; 59 + max-width: 200px; 60 + margin: 2rem auto 0 auto; 61 + padding: 12px 24px; 62 + background-color: transparent; 63 + color: #595aff; 64 + border: 2px solid #595aff; 65 + border-radius: 8px; 66 + font-weight: 600; 67 + cursor: pointer; 68 + transition: all 0.2s ease-in-out; 69 + } 70 + 71 + .widget > .btn-get-comments:hover { 72 + background-color: #595aff; 73 + color: white; 74 + box-shadow: 0 4px 12px rgba(89, 90, 255, 0.3); 75 + transform: translateY(-1px); 76 + } 77 + 78 + h1.widget-header { 79 + font-size: 1.75rem; 80 + font-weight: 800; 81 + color: #1a202c; 82 + margin: 0 0 0.5rem 0; 83 + letter-spacing: -0.025em; 84 + } 85 + 86 + .subheader { 87 + font-size: 0.95rem; 88 + color: #718096; 89 + margin-bottom: 1.5rem; 90 + display: flex; 91 + align-items: center; 92 + gap: 6px; 93 + } 94 + 95 + .post-link { 96 + color: #595aff; 97 + text-decoration: none; 98 + font-weight: 500; 99 + border-bottom: 1px solid transparent; 100 + transition: border-color 0.2s; 101 + } 102 + 103 + .post-link:hover { 104 + border-bottom-color: #595aff; 105 + } 106 + 107 + .or-create-an-account-disclaimer { 108 + font-size: 0.73rem; 109 + color: #a0aec0; 110 + margin: -4px 0 12px 2px; 111 + font-style: italic; 112 + margin-bottom: 1.5rem; 113 + display: flex; 114 + align-items: center; 115 + gap: 6px; 116 + } 117 + 118 + .go-reply-form { 119 + display: flex; 120 + gap: 0; 121 + border-bottom: 1px solid #edf2f7; 122 + padding-bottom: 1.5rem; 123 + margin-bottom: 2rem; 124 + max-width: 500px; 125 + filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1)); 126 + } 127 + 128 + .go-reply-form-input { 129 + flex: 1; 130 + padding: 10px 15px; 131 + border: 2px solid #e2e8f0; 132 + flex: 1; 133 + border-right: none; 134 + border-radius: 8px 0 0 8px; 135 + font-size: 0.95rem; 136 + outline: none; 137 + transition: border-color 0.2s; 138 + } 139 + 140 + .go-reply-form-input:focus { 141 + border-color: var(--highlight); 142 + } 143 + 144 + .go-reply-form-input::placeholder { 145 + color: #a0aec0; 146 + } 147 + .input-group { 148 + display: flex; 149 + flex-direction: column; 150 + width: 100%; 151 + gap: 6px; 152 + } 153 + 154 + .go-reply-label { 155 + font-size: 0.85rem; 156 + font-weight: 600; 157 + color: #4a5568; 158 + letter-spacing: 0.05em; 159 + margin-left: 2px; 160 + } 161 + 162 + .form-controls { 163 + display: flex; 164 + width: 100%; 165 + } 166 + 167 + .go-reply-form-button { 168 + padding: 10px 20px; 169 + border: 2px solid var(--highlight); 170 + border-radius: 0 8px 8px 0; 171 + background-color: var(--highlight); 172 + color: white; 173 + font-weight: 600; 174 + cursor: pointer; 175 + transition: all 0.2s; 176 + white-space: nowrap; 177 + } 178 + 179 + .go-reply-form-button:hover { 180 + background-color: var(--highlight); 181 + border-color: var(--highlight); 182 + } 183 + 184 + @media (max-width: 480px) { 185 + .go-reply-form { 186 + flex-direction: column; 187 + gap: 8px; 188 + } 189 + .go-reply-form-input, 190 + .go-reply-form-button { 191 + border-radius: 8px; 192 + border: 2px solid #e2e8f0; 193 + } 194 + } 195 + 196 + .comment-widget form { 197 + display: flex; 198 + gap: 10px; 199 + margin-bottom: 1.5rem; 200 + } 201 + 202 + .comment-widget input[name="userinstance"] { 203 + flex-grow: 1; 204 + padding: 8px 12px; 205 + border: 1px solid #ccc; 206 + border-radius: 4px; 207 + } 208 + 209 + .comment { 210 + border-left: 2px solid #eee; 211 + padding-left: 1rem; 212 + margin-top: 1.5rem; 213 + display: flex; 214 + flex-direction: column; 215 + } 216 + 217 + .comment header { 218 + display: flex; 219 + align-items: center; 220 + gap: 12px; 221 + margin-bottom: 8px; 222 + } 223 + 224 + .comment .avatar { 225 + width: 36px; 226 + height: 36px; 227 + border-radius: 4px; 228 + object-fit: cover; 229 + background: #eee; 230 + border: 1px solid rgba(0, 0, 0, 0.05); 231 + } 232 + 233 + .comment .display-name { 234 + font-weight: bold; 235 + display: block; 236 + } 237 + 238 + .comment time { 239 + font-size: 0.85rem; 240 + color: #666; 241 + } 242 + 243 + .comment .content { 244 + line-height: 1.5; 245 + } 246 + 247 + .comment .content p { 248 + margin: 0.5rem 0; 249 + } 250 + 251 + .comment .mention { 252 + color: var(--highlight); 253 + text-decoration: none; 254 + } 255 + 256 + .comment footer { 257 + margin-top: 8px; 258 + } 259 + 260 + .comment footer a { 261 + font-size: 0.8rem; 262 + color: #888; 263 + text-decoration: none; 264 + } 265 + 266 + .comment footer a:hover { 267 + text-decoration: underline; 268 + } 269 + 270 + .comment .comment { 271 + margin-left: 10px; 272 + border-left: 2px solid #ddd; 273 + } 274 + 275 + .error { 276 + color: red; 277 + font-size: smaller; 278 + } 279 + } 280 + ``` 22 281 23 282 ## Development 24 283 25 284 ```sh 26 - gleam run # Run the project 27 - gleam test # Run the tests 285 + cd examples/lustre_chilp_app 286 + gleam run -m lustre/dev start 28 287 ```
+1 -1
gleam.toml
··· 1 1 name = "chilp" 2 2 description = "Allows you to use Mastodon comments on your Lustre blog." 3 - version = "1.0.0" 3 + version = "1.0.0-pre" 4 4 licences = ["Apache-2.0"] 5 5 repository = { type = "forgejo", host = "forge.strawmelonjuice.com", user = "strawmelonjuice", repo = "chilp" } 6 6 # links = [{ title = "Website", href = "" }]
-256
src/chilp/ffi.mjs
··· 2 2 window.location.assign(url); 3 3 } 4 4 5 - export function inline_styles() { 6 - return ` 7 - .chilp-widget { 8 - --highlight: #595aff; 9 - transition: all 0.5s ease; 10 - overflow: hidden; 11 - ::selection { 12 - background-color: rgba(89, 90, 255, 0.2); 13 - color: var(--highlight); 14 - } 15 - 16 - .widget::-webkit-scrollbar { 17 - width: 6px; 18 - } 19 - .widget::-webkit-scrollbar-thumb { 20 - background-color: #e2e8f0; 21 - border-radius: 10px; 22 - } 23 - 24 - .widget { 25 - max-width: 600px; 26 - margin: 2rem auto; 27 - background-color: floralwhite; 28 - font-family: sans-serif; 29 - padding: 2rem; 30 - border-radius: 12px; 31 - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); 32 - } 33 - 34 - .widget > .btn-get-comments { 35 - display: block; 36 - width: 100%; 37 - max-width: 200px; 38 - margin: 2rem auto 0 auto; /* Centers it and adds space above */ 39 - padding: 12px 24px; 40 - background-color: transparent; 41 - color: #595aff; 42 - border: 2px solid #595aff; 43 - border-radius: 8px; 44 - font-weight: 600; 45 - cursor: pointer; 46 - transition: all 0.2s ease-in-out; 47 - } 48 - 49 - .widget > .btn-get-comments:hover { 50 - background-color: #595aff; 51 - color: white; 52 - box-shadow: 0 4px 12px rgba(89, 90, 255, 0.3); 53 - transform: translateY(-1px); 54 - } 55 - 56 - h1.widget-header { 57 - font-size: 1.75rem; 58 - font-weight: 800; 59 - color: #1a202c; 60 - margin: 0 0 0.5rem 0; 61 - letter-spacing: -0.025em; 62 - } 63 - 64 - .subheader { 65 - font-size: 0.95rem; 66 - color: #718096; 67 - margin-bottom: 1.5rem; 68 - display: flex; 69 - align-items: center; 70 - gap: 6px; 71 - } 72 - 73 - .post-link { 74 - color: #595aff; 75 - text-decoration: none; 76 - font-weight: 500; 77 - border-bottom: 1px solid transparent; 78 - transition: border-color 0.2s; 79 - } 80 - 81 - .post-link:hover { 82 - border-bottom-color: #595aff; 83 - } 84 - 85 - .or-create-an-account-disclaimer { 86 - font-size: 0.73rem; 87 - color: #a0aec0; 88 - margin: -4px 0 12px 2px; 89 - font-style: italic; 90 - margin-bottom: 1.5rem; 91 - display: flex; 92 - align-items: center; 93 - gap: 6px; 94 - } 95 - 96 - .go-reply-form { 97 - display: flex; 98 - gap: 0; 99 - border-bottom: 1px solid #edf2f7; 100 - padding-bottom: 1.5rem; 101 - margin-bottom: 2rem; 102 - max-width: 500px; 103 - filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1)); 104 - } 105 - 106 - .go-reply-form-input { 107 - flex: 1; 108 - padding: 10px 15px; 109 - border: 2px solid #e2e8f0; 110 - flex: 1; 111 - border-right: none; 112 - border-radius: 8px 0 0 8px; 113 - font-size: 0.95rem; 114 - outline: none; 115 - transition: border-color 0.2s; 116 - } 117 - 118 - .go-reply-form-input:focus { 119 - border-color: var(--highlight); 120 - } 121 - 122 - .go-reply-form-input::placeholder { 123 - color: #a0aec0; 124 - } 125 - .input-group { 126 - display: flex; 127 - flex-direction: column; 128 - width: 100%; 129 - gap: 6px; 130 - } 131 - 132 - .go-reply-label { 133 - font-size: 0.85rem; 134 - font-weight: 600; 135 - color: #4a5568; 136 - /*text-transform: uppercase;*/ 137 - letter-spacing: 0.05em; 138 - margin-left: 2px; 139 - } 140 - 141 - .form-controls { 142 - display: flex; 143 - width: 100%; 144 - } 145 - 146 - .go-reply-form-button { 147 - padding: 10px 20px; 148 - border: 2px solid var(--highlight); 149 - border-radius: 0 8px 8px 0; 150 - background-color: var(--highlight); 151 - color: white; 152 - font-weight: 600; 153 - cursor: pointer; 154 - transition: all 0.2s; 155 - white-space: nowrap; 156 - } 157 - 158 - .go-reply-form-button:hover { 159 - background-color: var(--highlight); 160 - border-color: var(--highlight); 161 - } 162 - 163 - @media (max-width: 480px) { 164 - .go-reply-form { 165 - flex-direction: column; 166 - gap: 8px; 167 - } 168 - .go-reply-form-input, 169 - .go-reply-form-button { 170 - border-radius: 8px; 171 - border: 2px solid #e2e8f0; 172 - } 173 - } 174 - 175 - .comment-widget form { 176 - display: flex; 177 - gap: 10px; 178 - margin-bottom: 1.5rem; 179 - } 180 - 181 - .comment-widget input[name="userinstance"] { 182 - flex-grow: 1; 183 - padding: 8px 12px; 184 - border: 1px solid #ccc; 185 - border-radius: 4px; 186 - } 187 - 188 - .comment { 189 - border-left: 2px solid #eee; 190 - padding-left: 1rem; 191 - margin-top: 1.5rem; 192 - display: flex; 193 - flex-direction: column; 194 - } 195 - 196 - .comment header { 197 - display: flex; 198 - align-items: center; 199 - gap: 12px; 200 - margin-bottom: 8px; 201 - } 202 - 203 - .comment .avatar { 204 - width: 36px; 205 - height: 36px; 206 - border-radius: 4px; 207 - object-fit: cover; 208 - background: #eee; 209 - border: 1px solid rgba(0, 0, 0, 0.05); 210 - } 211 - 212 - .comment .display-name { 213 - font-weight: bold; 214 - display: block; 215 - } 216 - 217 - .comment time { 218 - font-size: 0.85rem; 219 - color: #666; 220 - } 221 - 222 - .comment .content { 223 - line-height: 1.5; 224 - } 225 - 226 - .comment .content p { 227 - margin: 0.5rem 0; 228 - } 229 - 230 - .comment .mention { 231 - color: var(--highlight); 232 - text-decoration: none; 233 - } 234 - 235 - .comment footer { 236 - margin-top: 8px; 237 - } 238 - 239 - .comment footer a { 240 - font-size: 0.8rem; 241 - color: #888; 242 - text-decoration: none; 243 - } 244 - 245 - .comment footer a:hover { 246 - text-decoration: underline; 247 - } 248 - 249 - .comment .comment { 250 - margin-left: 10px; 251 - border-left: 2px solid #ddd; 252 - } 253 - 254 - .error { 255 - color: red; 256 - font-size: smaller; 257 - } 258 - } 259 - `; 260 - } 261 5 import DOMPurify from "https://esm.sh/dompurify@3.2.7"; 262 6 263 7 export function sanitize(html) {
+252 -4
src/chilp/widget.gleam
··· 97 97 Nil 98 98 } 99 99 100 - @external(javascript, "./ffi.mjs", "inline_styles") 101 - fn js_inline_styles() -> String { 102 - "" 100 + const css = ".chilp-widget { 101 + --highlight: #595aff; 102 + transition: all 0.5s ease; 103 + overflow: hidden; 104 + ::selection { 105 + background-color: rgba(89, 90, 255, 0.2); 106 + color: var(--highlight); 107 + } 108 + 109 + .widget::-webkit-scrollbar { 110 + width: 6px; 111 + } 112 + .widget::-webkit-scrollbar-thumb { 113 + background-color: #e2e8f0; 114 + border-radius: 10px; 115 + } 116 + 117 + .widget { 118 + max-width: 600px; 119 + margin: 2rem auto; 120 + background-color: floralwhite; 121 + font-family: sans-serif; 122 + padding: 2rem; 123 + border-radius: 12px; 124 + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); 125 + } 126 + 127 + .widget > .btn-get-comments { 128 + display: block; 129 + width: 100%; 130 + max-width: 200px; 131 + margin: 2rem auto 0 auto; 132 + padding: 12px 24px; 133 + background-color: transparent; 134 + color: #595aff; 135 + border: 2px solid #595aff; 136 + border-radius: 8px; 137 + font-weight: 600; 138 + cursor: pointer; 139 + transition: all 0.2s ease-in-out; 140 + } 141 + 142 + .widget > .btn-get-comments:hover { 143 + background-color: #595aff; 144 + color: white; 145 + box-shadow: 0 4px 12px rgba(89, 90, 255, 0.3); 146 + transform: translateY(-1px); 147 + } 148 + 149 + h1.widget-header { 150 + font-size: 1.75rem; 151 + font-weight: 800; 152 + color: #1a202c; 153 + margin: 0 0 0.5rem 0; 154 + letter-spacing: -0.025em; 155 + } 156 + 157 + .subheader { 158 + font-size: 0.95rem; 159 + color: #718096; 160 + margin-bottom: 1.5rem; 161 + display: flex; 162 + align-items: center; 163 + gap: 6px; 164 + } 165 + 166 + .post-link { 167 + color: #595aff; 168 + text-decoration: none; 169 + font-weight: 500; 170 + border-bottom: 1px solid transparent; 171 + transition: border-color 0.2s; 172 + } 173 + 174 + .post-link:hover { 175 + border-bottom-color: #595aff; 176 + } 177 + 178 + .or-create-an-account-disclaimer { 179 + font-size: 0.73rem; 180 + color: #a0aec0; 181 + margin: -4px 0 12px 2px; 182 + font-style: italic; 183 + margin-bottom: 1.5rem; 184 + display: flex; 185 + align-items: center; 186 + gap: 6px; 187 + } 188 + 189 + .go-reply-form { 190 + display: flex; 191 + gap: 0; 192 + border-bottom: 1px solid #edf2f7; 193 + padding-bottom: 1.5rem; 194 + margin-bottom: 2rem; 195 + max-width: 500px; 196 + filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1)); 197 + } 198 + 199 + .go-reply-form-input { 200 + flex: 1; 201 + padding: 10px 15px; 202 + border: 2px solid #e2e8f0; 203 + flex: 1; 204 + border-right: none; 205 + border-radius: 8px 0 0 8px; 206 + font-size: 0.95rem; 207 + outline: none; 208 + transition: border-color 0.2s; 209 + } 210 + 211 + .go-reply-form-input:focus { 212 + border-color: var(--highlight); 213 + } 214 + 215 + .go-reply-form-input::placeholder { 216 + color: #a0aec0; 217 + } 218 + .input-group { 219 + display: flex; 220 + flex-direction: column; 221 + width: 100%; 222 + gap: 6px; 223 + } 224 + 225 + .go-reply-label { 226 + font-size: 0.85rem; 227 + font-weight: 600; 228 + color: #4a5568; 229 + letter-spacing: 0.05em; 230 + margin-left: 2px; 231 + } 232 + 233 + .form-controls { 234 + display: flex; 235 + width: 100%; 236 + } 237 + 238 + .go-reply-form-button { 239 + padding: 10px 20px; 240 + border: 2px solid var(--highlight); 241 + border-radius: 0 8px 8px 0; 242 + background-color: var(--highlight); 243 + color: white; 244 + font-weight: 600; 245 + cursor: pointer; 246 + transition: all 0.2s; 247 + white-space: nowrap; 248 + } 249 + 250 + .go-reply-form-button:hover { 251 + background-color: var(--highlight); 252 + border-color: var(--highlight); 253 + } 254 + 255 + @media (max-width: 480px) { 256 + .go-reply-form { 257 + flex-direction: column; 258 + gap: 8px; 259 + } 260 + .go-reply-form-input, 261 + .go-reply-form-button { 262 + border-radius: 8px; 263 + border: 2px solid #e2e8f0; 264 + } 265 + } 266 + 267 + .comment-widget form { 268 + display: flex; 269 + gap: 10px; 270 + margin-bottom: 1.5rem; 271 + } 272 + 273 + .comment-widget input[name=\"userinstance\"] { 274 + flex-grow: 1; 275 + padding: 8px 12px; 276 + border: 1px solid #ccc; 277 + border-radius: 4px; 278 + } 279 + 280 + .comment { 281 + border-left: 2px solid #eee; 282 + padding-left: 1rem; 283 + margin-top: 1.5rem; 284 + display: flex; 285 + flex-direction: column; 286 + } 287 + 288 + .comment header { 289 + display: flex; 290 + align-items: center; 291 + gap: 12px; 292 + margin-bottom: 8px; 293 + } 294 + 295 + .comment .avatar { 296 + width: 36px; 297 + height: 36px; 298 + border-radius: 4px; 299 + object-fit: cover; 300 + background: #eee; 301 + border: 1px solid rgba(0, 0, 0, 0.05); 302 + } 303 + 304 + .comment .display-name { 305 + font-weight: bold; 306 + display: block; 307 + } 308 + 309 + .comment time { 310 + font-size: 0.85rem; 311 + color: #666; 312 + } 313 + 314 + .comment .content { 315 + line-height: 1.5; 316 + } 317 + 318 + .comment .content p { 319 + margin: 0.5rem 0; 320 + } 321 + 322 + .comment .mention { 323 + color: var(--highlight); 324 + text-decoration: none; 325 + } 326 + 327 + .comment footer { 328 + margin-top: 8px; 329 + } 330 + 331 + .comment footer a { 332 + font-size: 0.8rem; 333 + color: #888; 334 + text-decoration: none; 335 + } 336 + 337 + .comment footer a:hover { 338 + text-decoration: underline; 339 + } 340 + 341 + .comment .comment { 342 + margin-left: 10px; 343 + border-left: 2px solid #ddd; 344 + } 345 + 346 + .error { 347 + color: red; 348 + font-size: smaller; 349 + } 103 350 } 351 + " 104 352 105 353 fn browse(to: String) { 106 354 use _ <- effect.from ··· 113 361 case model { 114 362 WrappingModelSet(chilp_model:, widget:, styles: True) -> 115 363 html.div([attribute.class("chilp-widget-component")], [ 116 - html.style([], js_inline_styles()), 364 + html.style([], css), 117 365 widget.show(from: widget, data: chilp_model), 118 366 ]) 119 367 WrappingModelSet(chilp_model:, widget:, styles: False) ->