my website
0
fork

Configure Feed

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

add content

+4199 -330
+3
public/.well-known/org.flathub.VerifiedApps.txt
··· 1 + 7f1264c2-31bc-49aa-a75b-7bf68cd84d75 2 + 7bb94706-842d-4b02-a66c-3e0a35b198b8 3 + a98b7786-5f96-47ed-bdae-f2d4c6281073
public/Resume.pdf

This is a binary file and will not be displayed.

public/blog-placeholder-1.jpg

This is a binary file and will not be displayed.

public/blog-placeholder-2.jpg

This is a binary file and will not be displayed.

public/blog-placeholder-3.jpg

This is a binary file and will not be displayed.

public/blog-placeholder-4.jpg

This is a binary file and will not be displayed.

public/blog-placeholder-5.jpg

This is a binary file and will not be displayed.

public/blog-placeholder-about.jpg

This is a binary file and will not be displayed.

public/favicon.ico

This is a binary file and will not be displayed.

-9
public/favicon.svg
··· 1 - <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128"> 2 - <path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" /> 3 - <style> 4 - path { fill: #000; } 5 - @media (prefers-color-scheme: dark) { 6 - path { fill: #FFF; } 7 - } 8 - </style> 9 - </svg>
public/images/icons/explosiv.webp

This is a binary file and will not be displayed.

public/images/posts/2022/banner.webp

This is a binary file and will not be displayed.

public/images/posts/2022/contributions-type.webp

This is a binary file and will not be displayed.

public/images/posts/2022/contributions.webp

This is a binary file and will not be displayed.

public/images/posts/2022/midjourney.webp

This is a binary file and will not be displayed.

public/images/posts/an-alpine-setup/banner.webp

This is a binary file and will not be displayed.

public/images/posts/burundi/beach.webp

This is a binary file and will not be displayed.

public/images/posts/burundi/broken-down-car.webp

This is a binary file and will not be displayed.

public/images/posts/burundi/bugesera.webp

This is a binary file and will not be displayed.

public/images/posts/burundi/bujumbura-square.webp

This is a binary file and will not be displayed.

public/images/posts/burundi/bujumbura-view.webp

This is a binary file and will not be displayed.

public/images/posts/burundi/burundi.webp

This is a binary file and will not be displayed.

public/images/posts/burundi/chippy.webp

This is a binary file and will not be displayed.

public/images/posts/burundi/croc.webp

This is a binary file and will not be displayed.

public/images/posts/burundi/flags.webp

This is a binary file and will not be displayed.

public/images/posts/burundi/leopard.webp

This is a binary file and will not be displayed.

public/images/posts/burundi/mountains.webp

This is a binary file and will not be displayed.

public/images/posts/burundi/ngozi.webp

This is a binary file and will not be displayed.

public/images/posts/burundi/park-map.webp

This is a binary file and will not be displayed.

public/images/posts/burundi/rusizi.webp

This is a binary file and will not be displayed.

public/images/posts/burundi/rwanda.webp

This is a binary file and will not be displayed.

public/images/posts/burundi/snake.webp

This is a binary file and will not be displayed.

public/images/posts/burundi/tanganyika.webp

This is a binary file and will not be displayed.

public/images/posts/burundi/tree.webp

This is a binary file and will not be displayed.

public/images/posts/deno-blog/banner.webp

This is a binary file and will not be displayed.

public/images/posts/deno-blog/du-after-cache.webp

This is a binary file and will not be displayed.

public/images/posts/deno-blog/du-after.webp

This is a binary file and will not be displayed.

public/images/posts/deno-blog/du-before.webp

This is a binary file and will not be displayed.

public/images/posts/deno-blog/files-after.webp

This is a binary file and will not be displayed.

public/images/posts/deno-blog/files-before.webp

This is a binary file and will not be displayed.

public/images/posts/deno-blog/layout-after.webp

This is a binary file and will not be displayed.

public/images/posts/deno-blog/layout-before.webp

This is a binary file and will not be displayed.

public/images/posts/deno-blog/perf-after.webp

This is a binary file and will not be displayed.

public/images/posts/deno-blog/perf-before.webp

This is a binary file and will not be displayed.

public/images/posts/explosiv-blog/blog-homepage-with-posts.webp

This is a binary file and will not be displayed.

public/images/posts/explosiv-blog/blog-homepage.webp

This is a binary file and will not be displayed.

public/images/posts/explosiv-blog/blog-post.webp

This is a binary file and will not be displayed.

public/images/posts/explosiv-blog/nextjs-waterfall.webp

This is a binary file and will not be displayed.

public/images/posts/explosiv-blog/styled-blog-homepage.webp

This is a binary file and will not be displayed.

public/images/posts/remix-in-the-cosmos/cosmos.webp

This is a binary file and will not be displayed.

public/images/posts/remix-in-the-cosmos/storybook.webp

This is a binary file and will not be displayed.

public/images/posts/river/art.jimmac.eu.png

This is a binary file and will not be displayed.

public/images/posts/river/banner (copy).png

This is a binary file and will not be displayed.

public/images/posts/river/banner.png

This is a binary file and will not be displayed.

public/images/posts/river/deno-config.png

This is a binary file and will not be displayed.

public/images/posts/river/my-vsco.png

This is a binary file and will not be displayed.

public/images/posts/river/river.png

This is a binary file and will not be displayed.

public/images/posts/river/vsco-requests.png

This is a binary file and will not be displayed.

public/images/posts/setup-git/banner.webp

This is a binary file and will not be displayed.

public/images/posts/workbench-typescript/demos.png

This is a binary file and will not be displayed.

public/images/posts/workbench-typescript/typechecking-in-workbench.png

This is a binary file and will not be displayed.

public/images/posts/workbench-typescript/typechecking-javascript.png

This is a binary file and will not be displayed.

public/images/posts/workbench-typescript/typescript-in-workbench.png

This is a binary file and will not be displayed.

public/images/posts/workbench-typescript/workbench-main-window.png

This is a binary file and will not be displayed.

+2
public/robots.txt
··· 1 + User-agent: * 2 + Disallow:
+1 -1
src/components/BaseHead.astro
··· 17 17 <!-- Global Metadata --> 18 18 <meta charset="utf-8" /> 19 19 <meta name="viewport" content="width=device-width,initial-scale=1" /> 20 - <link rel="icon" type="image/svg+xml" href="/favicon.svg" /> 20 + <link rel="shortcut icon" href="/favicon.ico" /> 21 21 <meta name="generator" content={Astro.generator} /> 22 22 23 23 <!-- Font preloads -->
+4 -4
src/components/PostInfo.astro
··· 5 5 6 6 type Props = Pick< 7 7 CollectionEntry<"blog">["data"], 8 - "title" | "description" | "pubDate" 8 + "title" | "description" | "publish_date" | "tags" 9 9 > 10 10 11 - const { title, description, pubDate } = Astro.props; 11 + const { title, description, publish_date, tags } = Astro.props; 12 12 --- 13 13 14 14 <> 15 - <Intro title={title} created={pubDate} author="Angelo Verlain" /> 15 + <Intro title={title} created={publish_date} author="Angelo Verlain" /> 16 16 <p> 17 17 {description} 18 18 <!-- {state.readtime && ( ··· 22 22 </span> 23 23 )} --> 24 24 <br /> 25 - <Tags tags={[]} /> 25 + <Tags tags={tags} /> 26 26 </p> 27 27 </>
+8 -11
src/components/PostsIndex.astro
··· 5 5 import type { CollectionEntry } from "astro:content"; 6 6 7 7 const posts = (await getCollection("blog")).sort( 8 - (a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(), 8 + (a, b) => b.data.publish_date.valueOf() - a.data.publish_date.valueOf(), 9 9 ); 10 10 11 11 type Post = CollectionEntry<"blog">; ··· 13 13 const postsByYear = Object.entries( 14 14 posts.reduce( 15 15 (acc, post) => { 16 - const year = post.data.pubDate.getFullYear().toString(); 16 + const year = post.data.publish_date.getFullYear().toString(); 17 17 acc[year] ??= []; 18 18 acc[year].push(post); 19 19 return acc; ··· 21 21 {} as Record<string, Post[]>, 22 22 ), 23 23 ).sort(([year1], [year2]) => year2.localeCompare(year1)); 24 - 25 - const author = "Angelo Verlain", 26 - tags: string[] = []; 27 24 --- 28 25 29 26 <div class="posts"> ··· 36 33 <h3>{year}</h3> 37 34 {posts.map( 38 35 ({ 39 - body, 40 36 id, 41 37 data: { 42 38 title, 43 39 description: snippet, 44 - updatedDate: publishDate, 45 - heroImage, 40 + publish_date, 41 + hero_image, 42 + tags, 46 43 }, 47 44 }) => ( 48 45 <p> 49 - {heroImage ? ( 46 + {hero_image ? ( 50 47 <a href={`/blog/${id}`} class="post-image" aria-label={title}> 51 - <img src={heroImage} alt={title} /> 48 + <img src={hero_image} alt={title} /> 52 49 </a> 53 50 ) : null} 54 51 <a href={`/blog/${id}`} class="post-title"> ··· 57 54 <br /> 58 55 <small class="intro-meta"> 59 56 {/* {author && <span>{author || ""} at </span>} */} 60 - <PrettyDate date={publishDate ?? new Date()} /> 57 + <PrettyDate date={publish_date ?? new Date()} /> 61 58 </small> 62 59 <br /> 63 60 <span>{snippet}</span>
+9 -7
src/content.config.ts
··· 1 - import { glob } from 'astro/loaders'; 2 - import { defineCollection, z } from 'astro:content'; 1 + import { glob } from "astro/loaders"; 2 + import { defineCollection, z } from "astro:content"; 3 3 4 4 const blog = defineCollection({ 5 5 // Load Markdown and MDX files in the `src/content/blog/` directory. 6 - loader: glob({ base: './src/content/blog', pattern: '**/*.{md,mdx}' }), 6 + loader: glob({ base: "./src/content/blog", pattern: "**/*.{md,mdx}" }), 7 7 // Type-check frontmatter using a schema 8 8 schema: z.object({ 9 - title: z.string(), 9 + title: z.coerce.string(), 10 10 description: z.string(), 11 11 // Transform string to Date object 12 - pubDate: z.coerce.date(), 13 - updatedDate: z.coerce.date().optional(), 14 - heroImage: z.string().optional(), 12 + publish_date: z.coerce.date(), 13 + updated_date: z.coerce.date().optional(), 14 + hero_image: z.string().optional(), 15 + invert: z.boolean().default(false), 16 + tags: z.array(z.string()).default([]), 15 17 }), 16 18 }); 17 19
+405
src/content/blog/2022.md
··· 1 + --- 2 + title: "2022" 3 + description: A year of change and adaption. 4 + publish_date: 2022-12-23 5 + hero_image: /images/posts/2022/banner.webp 6 + invert: true 7 + tags: [life] 8 + --- 9 + 10 + Hello everyone! It's almost the end of the year 2022, and I hope you had a 11 + wonderful year. As we look ahead to 2023, I wish you an even better year ahead! 12 + 13 + ## Overview 14 + 15 + The past year was one of change, with many important events taking place around 16 + the world. Some of these events, such as the February Russian Invasion of 17 + Ukraine and the continued impact of COVID-19, had significant and far-reaching 18 + consequences. Other notable events included the global population reaching 8 19 + billion people and the death of Queen Elizabeth II. 20 + 21 + I won't be trying to list every single event that happened this year, but 22 + instead will focus on what happened in my own life and what I learned from it. 23 + I'll also interpret some events that happened and provide my thoughts on what 24 + may happen in the future. This blog post is divided into three sections: 25 + Personal, News, and Development. 26 + 27 + ## Personal 28 + 29 + In this section, I'll share some of the important events that happened for me 30 + this year, and my thoughts and feelings about them. 31 + 32 + ### Graduating 33 + 34 + I graduated from high school in August, and looking back, I realize that I'm 35 + going to miss school. It's not just about the fun and memories, but also about 36 + the people. I've made so many wonderful connections and experiences, and it's 37 + hard to think about moving on and starting a new chapter in my life. I know that 38 + I won't always have the same group of friends and colleagues, and that can be 39 + sad. 40 + 41 + ### Work 42 + 43 + After graduating, I started working at [RWARRI], an NGO that helps people in 44 + poverty and refugees, as a web developer and IT specialist. It's been a 45 + challenging and busy year, with less time for personal pursuits, friends, and 46 + hobbies. But on the positive side, I've met some great people and gained some 47 + valuable experience in a work environment and with socializing. 48 + 49 + ### Work Travel/Missions 50 + 51 + As part of my job at [RWARRI], I've had the opportunity to travel and meet 52 + people in different areas who are facing difficult circumstances. These 53 + experiences have really helped me appreciate how lucky we are, even when we have 54 + very little. It's easy to take our basic needs and comforts for granted, but 55 + seeing firsthand how others struggle to meet their basic needs has made me more 56 + grateful for what I have. 57 + 58 + In addition to helping me appreciate what I have, these experiences have also 59 + reminded me of the importance of giving more than I receive. This can be as 60 + simple as volunteering my time or resources to help others, or simply being 61 + there to listen and offer support. As Jesus said: 62 + 63 + > It is more blessed to give than to receive. (Acts 20:35) 64 + 65 + I've learned that these values are important not just in our personal lives, but 66 + also in our work and relationships with others. At RWARRI, we strive to serve 67 + others with compassion and generosity, and I feel fortunate to be a part of that 68 + mission. If you're interested in learning more about RWARRI and how you can get 69 + involved, you can visit their website [here][rwarri]. 70 + 71 + ### Money 72 + 73 + > All is vanity. (Ecclesiastes 1:1) 74 + 75 + This verse reminds us that everything is fleeting and temporary, and that we 76 + shouldn't place too much value on material possessions. This is a lesson that 77 + I've learned more deeply this year as I've started working and earning a salary. 78 + 79 + While I've never been one to care excessively about money, my work has given me 80 + a greater appreciation for the role that it plays in our lives. It's not just 81 + about being able to afford the things we want, but also about having the 82 + resources to meet our basic needs. Seeing others who struggle to put food on the 83 + table has made me more grateful for the financial security that I have. 84 + 85 + At the same time, I've learned to be mindful about how I spend my money. I try 86 + to focus on buying the things that I really need, and save the rest for future 87 + needs. I think the key is to prioritize spending on important things and not get 88 + caught up in materialism. 89 + 90 + ### University/College 91 + 92 + I just received my results for my final exams a few days ago, and I'm happy to 93 + report that I passed. Now, I'm looking forward to the next stage of my education 94 + and considering my options for university or college. I'm hoping to secure a 95 + scholarship and, even more hopefully, study abroad. I know that university will 96 + be a different experience, with more challenges and demands, but I'm excited for 97 + the opportunity to learn and grow. 98 + 99 + I'm still in the process of researching and deciding on a major, so if you have 100 + any useful tips or advice, please don't hesitate to reach out. I'm open to 101 + hearing from others who have gone through this process and can share their 102 + insights. 103 + 104 + ### Digital Interactions 105 + 106 + In today's world, it's almost impossible to avoid having digital interactions 107 + with others, whether through social media, group chats, or online communities 108 + like Discord or Matrix. While these platforms can be a great way to connect with 109 + others and share ideas, I've also learned that people can be more rude and 110 + vicious online than they would be in real life. This has led me to be more 111 + selective about who I engage with online, and to prioritize building trust and 112 + relationships in person. 113 + 114 + That being said, I'm not suggesting that you should abandon your online friends 115 + or delete your social media accounts. But I do think it can be beneficial to 116 + limit your digital interactions and focus on more meaningful activities. It's 117 + easy to get sucked into the endless scroll of social media and waste valuable 118 + time that could be spent on things that are more meaningful and fulfilling. Next 119 + year, consider setting limits on your digital interactions and prioritizing 120 + face-to-face connections. 121 + 122 + ### Depression 123 + 124 + This year, I struggled with depression as a result of work-related stress and 125 + other events in my life. At times, I felt overwhelmed, lost all motivation and 126 + will to live, and struggled with impostor syndrome. These feelings were 127 + compounded by external events that made things worse. As a web developer, my 128 + productivity was severely impacted, and I found it difficult to complete tasks 129 + and contribute to my team. 130 + 131 + Depression is a serious issue that can last for years or even a lifetime, and 132 + it's not something that can simply be brushed off or dismissed. Unfortunately, 133 + there are still many people who don't understand or take depression seriously, 134 + and this can make it harder for those who are struggling to get the support and 135 + help they need. 136 + 137 + If you're struggling with depression, I encourage you to speak to someone about 138 + it. This could be a friend, family member, therapist, or healthcare 139 + professional. It's important to have someone to talk to and share your feelings 140 + with. For me, speaking out about my depression really helped, and it made a big 141 + difference to know that I wasn't alone. If you ever need someone to talk to, 142 + feel free to reach out to me. 143 + 144 + There are also many other resources available to help with depression, including 145 + counseling, therapy, medication, and self-care techniques. It's important to 146 + find the right combination of treatments that work for you and to be proactive 147 + in seeking help. Remember, you don't have to suffer in silence – there is help 148 + and hope available. 149 + 150 + ### Music 151 + 152 + Music has always been an important part of my life, and this year was no 153 + different. I found myself constantly plugged into my earphones or headphones, 154 + listening to music to help me focus and stay motivated while programming or 155 + completing routine tasks. Music has a way of filling in the silence and 156 + providing a sense of calm and therapy, at least for me. 157 + 158 + I've also been experimenting with using white noise to help me sleep better. A 159 + friend recommended rain sounds, and I have to say, they work wonders for me. If 160 + you have trouble sleeping, you might want to try incorporating some white noise 161 + into your bedtime routine. 162 + 163 + In terms of music genres, I really enjoyed listening to amaPiano this year. I 164 + usually use Spotify, but I'm slowly switching over to YouTube Music. Here's a 165 + playlist with 10 tracks I can recommend easily. 166 + 167 + <iframe 168 + title="Embedded Spotify playlist for 2022" 169 + style="border-radius:12px" 170 + src="https://open.spotify.com/embed/playlist/2dRHDlx5a2o8oXMq049HOi?utm_source=generator" 171 + width="100%" 172 + height="380" 173 + frameBorder="0" 174 + allowfullscreen="" 175 + allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"> 176 + </iframe> 177 + <br/> 178 + <br/> 179 + 180 + ### Hobbies 181 + 182 + This year, I discovered a new hobby: cycling. It's been a great activity for me, 183 + not only because it's fun and enjoyable, but also because it helps me stay fit 184 + and explore new places. If you're looking for a new hobby, I highly recommend 185 + giving cycling a try. 186 + 187 + In addition to cycling, I've also been listening to a lot of informative 188 + podcasts and reading more this year. While I've enjoyed these activities, I 189 + haven't been able to practice my hobbies as much as I'd like due to work and 190 + other commitments. I'm hoping to have more time for my hobbies next year, and to 191 + find ways to balance work and leisure more effectively. 192 + 193 + ## News/Opinions 194 + 195 + In this section, I want to discuss some of the major events that happened in 196 + 2022 and share my (unprofessional) thoughts and opinions on them. Please keep in 197 + mind that some of these topics are sensitive, and I will try to be as neutral as 198 + possible. 199 + 200 + ### Covid-19 201 + 202 + The [COVID-19][covid] pandemic has had a significant impact on the world this 203 + year, and we are finally starting to see some progress in terms of vaccination 204 + efforts and returning to pre-COVID situations. While it's sad to think about all 205 + the time and opportunities that were lost during the pandemic, I'm hopeful that 206 + we are on the path to recovery and normalcy. 207 + 208 + ### Russian Invasion of Ukraine 209 + 210 + In February, [Russia invaded Ukraine][ukraine-invasion] and sparked a conflict 211 + with other Western countries. While Ukraine has been able to resist Russia's 212 + efforts, thanks in part to support from NATO countries, the situation remains 213 + tense and unresolved. It's important to respect the sovereignty of all countries 214 + and to strive for peaceful relations with our neighbors. 215 + 216 + One aspect of this conflict that has particularly concerned me is the treatment 217 + of Russian citizens. The war is largely being orchestrated by Putin and his 218 + allies, rather than the average Russian person. It's wrong to view all Russians 219 + as enemies and to discriminate against them, especially in fields like software 220 + development where Russian developers have faced job loss and account 221 + suspensions. 222 + 223 + In my opinion, unity is always preferable to conflict, and war should be avoided 224 + at all costs. It's important to remember that ordinary people on both sides are 225 + affected by these conflicts, and we should strive for peaceful resolutions 226 + whenever possible. 227 + 228 + ### Elon Musk's acquisition of Twitter 229 + 230 + In 2022, Elon Musk made headlines when [he acquired Twitter for $44 231 + billion][twitter-aquisiton]. This acquisition has had a significant impact on 232 + the social media platform and on the world at large. Musk has claimed that there 233 + were shady deals happening with the leadership of Twitter and the US government, 234 + and he has fired a significant number of employees (from around 7,500 to around 235 + 2,900). 236 + 237 + The mass layoffs at Twitter have had a domino effect, with other companies 238 + following suit and also reducing their workforce. This has led to a decrease in 239 + demand for software development jobs and has caused many people to question if 240 + it's still a viable career path. 241 + 242 + It's important to remember that these changes at Twitter and other companies 243 + have far-reaching consequences, and they can have a significant impact on the 244 + employment prospects of individuals in the tech industry. It's always a good 245 + idea to stay informed about developments in your field and to be proactive in 246 + building a strong career. 247 + 248 + ### AI 249 + 250 + ![Mechanical dove created with Midjourney V4](/images/posts/2022/midjourney.webp "By Deviationsz1 - Own work, Public Domain --- https://commons.wikimedia.org/w/index.php?curid=125105397") 251 + 252 + This year, OpenAI released [ChatGPT], an AI system that can engage in real-time 253 + conversation and provide almost-perfect answers to any question you ask. While 254 + this technology is impressive and has raised questions about the future of 255 + humanity and the potential dominance of AI, it's important to remember that it's 256 + still in its early stages and has limitations. For now, ChatGPT is available for 257 + free, but it's unclear how long that will remain the case. 258 + 259 + OpenAI also released [DALL-E 2][dalle2], a deep learning model that can generate 260 + digital images based on natural language descriptions or "prompts." This model 261 + and others like it, such as [Stable Diffusion][stable-diffusion] and 262 + [Midjourney], demonstrate the increasing power and capabilities of AI. However, 263 + the use of AI for image generation has also sparked a movement among artists who 264 + feel that their work has been used without their consent. This has led to a 265 + global conversation about the ethical implications of AI art and the need for 266 + consent and proper attribution. 267 + 268 + ## Development 269 + 270 + ![Github graph showing my contributions](/images/posts/2022/contributions.webp) 271 + 272 + ![Github graph showing the type of my contributions](/images/posts/2022/contributions-type.webp) 273 + 274 + In this section, I want to share some of my thoughts and experiences as a web 275 + developer. This year, I've been interested in Linux systems, particularly 276 + [GNOME] and [GTK], and have been exploring alternative technologies and 277 + approaches. 278 + 279 + ### Contributions 280 + 281 + This year, I discovered many open source libraries that I really enjoyed, 282 + especially those that I have used in my own projects. The scope of these 283 + libraries is quite broad, but some of the ones that stand out to me include: 284 + 285 + - [Rabbet], a small link aggregator that I developed (similar to Linktr.ee) 286 + - [Deno], an alternative runtime to Node.JS 287 + - [Libvips], an image processing library 288 + 289 + It's worth noting that, as a developer, it's often common to contribute to these 290 + types of libraries, especially if you rely on them in your daily work. 291 + 292 + ### New Technology/Skills 293 + 294 + I am in the process of rewriting a small link aggregator I made, [Rabbet] using 295 + Deno and hope it'll be ready early next year. 296 + 297 + I also started using new software/alternatives: 298 + 299 + - [Gitlab] instead of Github (mainly for GNOME-related contributions) 300 + - [Notion] for note-taking, finance management, and more 301 + - [Supabase], an open source alternative to Firebase 302 + - [Linux], specifically [Arch Linux][arch] as an alternative to Windows 303 + - [Mastodon], an open source alternative to Twitter 304 + - and more I probably forgot... 305 + 306 + ### Deno 307 + 308 + One technology that I've been particularly excited about is [Deno], a runtime 309 + for JavaScript and TypeScript that is an alternative to [Node.JS][node]. I've 310 + had a chance to work with Deno this year and have really enjoyed it. In fact, I 311 + even used Deno to [rewrite my blog](/blog/deno-blog). Deno has matured 312 + significantly in recent years, and I think it has a bright future ahead of it. 313 + I've also attempted to create Deno bindings for 314 + [GObject](https://docs.gtk.org/gobject/), a library for creating and using 315 + GObjects, which are used to create object-oriented code in C, with the goal of 316 + allowing GTK programs to be written with Deno. This project is called 317 + [Geno](#geno). 318 + 319 + ### GNOME Mobile 320 + 321 + In the third quarter of this year, I lost my phone and had to purchase a new 322 + one. I was struck by the limited options available: it's either Android or 323 + iPhone. I believe that 324 + [GNOME Mobile](https://blogs.gnome.org/shell-dev/2022/09/09/gnome-shell-on-mobile-an-update/), 325 + a mobile operating system based on Linux, could bring significant improvements 326 + and potentially challenge the dominant players in the market. There are other 327 + Linux-based mobile OSes such as [Plasma Mobile](https://www.plasma-mobile.org/), 328 + [sxmo](https://sxmo.org/), and others, but GNOME Mobile particularly caught my 329 + attention. I hope that it becomes a viable option in the future. 330 + 331 + ### Developing with GTK 332 + 333 + In addition to working with Deno and GObjects, I've also started learning to 334 + make GTK apps (using [Vala], with the goal of learning 335 + [Rust](https://www.rust-lang.org/) in the future). It's been an interesting 336 + journey, but one that has had its challenges, particularly the lack of good 337 + documentation. However, efforts are underway to improve the documentation, and I 338 + hope that it will become less of an issue in the near future. 339 + 340 + ### Geno 341 + 342 + [Geno] is a project I started to create GObject bindings for Deno. It is still 343 + in the early stages, but I hope to complete it soon and potentially create a GTK 344 + app with it. 345 + 346 + ### Linux and the Community 347 + 348 + I have been using [GNU]/[Linux] for some time now and have grown to really 349 + appreciate it. One of the best things about it is the community of people who 350 + share a passion for GNU/Linux. Many of the people who work on projects such as 351 + [GNU], [Linux], [KDE], [Fedora], [Arch], and others related to GNU/Linux are not 352 + paid for their efforts. They do it because they are passionate about it and I 353 + think that is truly amazing. 354 + 355 + In addition to the strong sense of community in the GNU/Linux world, it is also 356 + important to address the issue of mental health. If you are struggling with 357 + depression, it can be helpful to seek support from friends, family, or a mental 358 + health professional. There are also online resources and support groups 359 + available that can provide a sense of community and assistance during difficult 360 + times. It is important to remember that you are not alone and that help is 361 + available. 362 + 363 + ## Closing 364 + 365 + In conclusion, it is clear that the year 2022 has been a year of significant 366 + events and challenges, both globally and personally. From the impact of COVID-19 367 + and the Russian invasion of Ukraine, to the release of powerful AI systems and 368 + the emergence of new technologies, it has been a year of change and adaptation. 369 + As we move into 2023, it will be important to continue learning and growing, and 370 + to prioritize the things that truly matter in our lives. 371 + 372 + I finally wish you all a Merry Christmas and Happy new year! 373 + 374 + <br/> 375 + 376 + Banner Photo by 377 + [Kelly Sikkema](https://unsplash.com/es/@kellysikkema?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) 378 + on 379 + [Unsplash](https://unsplash.com/photos/-64OzuZ8ThE?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) 380 + 381 + [gitlab]: https://gitlab.com/ 382 + [notion]: https://www.notion.so/ 383 + [supabase]: https://supabase.io/ 384 + [mastodon]: https://joinmastodon.org/ 385 + [rabbet]: https://rabbet.me/ 386 + [deno]: https://deno.land/ 387 + [libvips]: https://libvips.github.io/libvips/ 388 + [node]: https://nodejs.org/ 389 + [gnu]: https://www.gnu.org/gnu/the-gnu-project.en.html 390 + [linux]: https://www.kernel.org/ 391 + [kde]: https://www.kde.org/ 392 + [fedora]: https://getfedora.org/ 393 + [arch]: https://www.archlinux.org/ 394 + [geno]: https://githu.com/vixalien/geno 395 + [dalle2]: https://en.wikipedia.org/wiki/DALL-E 396 + [stable-diffusion]: https://stability.ai/ 397 + [midjourney]: https://www.midjourney.com/ 398 + [chatgpt]: https://chat.openai.com 399 + [rwarri]: https://rwarri.com 400 + [vala]: https://vala.dev 401 + [gtk]: https://www.gtk.org/ 402 + [gnome]: https://www.gnome.org/ 403 + [ukraine-invasion]: https://en.wikipedia.org/wiki/2022_Russian_invasion_of_Ukraine 404 + [twitter-aquisition]: https://en.wikipedia.org/wiki/Acquisition_of_Twitter_by_Elon_Musk 405 + [covid]: https://en.wikipedia.org/wiki/COVID-19
+85
src/content/blog/2024.md
··· 1 + --- 2 + title: "2024" 3 + description: One of the years ever. 4 + publish_date: 2025-01-09 5 + tags: [life] 6 + --- 7 + 8 + Hello, reader. 9 + 10 + This is my latest attempt at trying to write a wrap-up of my year, and also a last-ditch attempt to save this blog from all the tumbleweeds, dust and spiderwebs. 11 + 12 + I'll be writing about what I did this year, and this year it's going to be a bit more structured and brief, atleast that's what I'm aiming for. Please read this, and maybe give me some feedback! 13 + 14 + ## Life 15 + 16 + I'd say that life has been pretty okay. I've only got sick once towards the end of the year, and only for a few days. 17 + 18 + ### memories.vixalien.com 19 + 20 + I now host a simple website at [memories.vixalien.com], which you can visit to see a few pictures I took along this journey of life. I wish to improve it, possibly using the new [Masonry layout in CSS](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_grid_layout/Masonry_layout), so that the images can display better, and faster. I might also try rewriting the site in Astro, to get rid of all the JavaScript. You can read more about how it works [in this blog post I wrote earlier this year about `river`](https://www.vixalien.com/blog/river), the name of the metaframework it uses under the hood. 21 + 22 + ### Budgeting 23 + 24 + I started using [YNAB](https://ynab.com/) to make budgets and track my personal expenses. I wanted to do it to reduce spending on luxuries instead of saving up, and I would say it paid a huge role. Actually tracking your expenses and knowing where your money goes is such an eye-opening experience, and you will quickly realise how much money you spend on the "small things" and on the "I need to take care of myself" expenses. 25 + 26 + The next step of YNAB is actually budgeting, instead of just tracking expenses. To be totally frank with you, this is not my strongest suit, as I still adjust the budget from time to time to cover overspending in a _wants_ category. 27 + 28 + If you want to try out budgeting for yourself, I recommend you follow YNAB's 4 rules: 29 + 30 + 1. Give every dollar a job 31 + 2. Embrace your true expenses 32 + 3. Roll with the punches 33 + 4. Age Your Money 34 + 35 + You can [learn more about the 4 rules with Hannah](https://www.youtube.com/watch?v=R4VbZCxDDvY). 36 + 37 + ## Social 38 + 39 + ### Twitter/X 40 + 41 + This year, I broke away from Twitter/X, as it has been... well, unusable recently. Instead, I switched to Mastodon, where you can follow me as [@vixalientoots@mas.to](https://mas.to/@vixalientoots). My fediverse client of choice is [Phanpy](https://phanpy.social/) on desktop and mobile linux, and [Ice Cubes](https://apps.apple.com/us/app/ice-cubes-for-mastodon/id6444915884) on my phone. 42 + 43 + ### Instagram 44 + 45 + Instagram is probably the social media I use the most. I use it to sink a lot of my important hours watching reels, chatting with friends, and seldomly posting about pictures I take. 46 + 47 + I took a brief break in the timeframe northern hemisphere folks call summer. We call that season the long dry season, because we are close to the equator, and our seasons are not influenced by the tilt of the earth, but rather due to the rainfall patterns. Anyway, during that break I accidentally deleted my account instead of disabling it, and made the shocking realisation a few days after the 30-day window Instagram gives you before deleting your account for good. This was bittersweet, but I made the (bad) decision to come back to Instagram again, as my life was honestly feeling very empty. 48 + 49 + ## School 50 + 51 + ### Uni 52 + 53 + This year I also tried being more serious and attentive to classes, instead of the usual lazy and unproductive self I am. This work paid off very well, and I'm no longer in the lower ranks and actually make an effort to understand course concepts before moving on. If you are in University too, or doing something else, I would recommend you give it your all. 54 + 55 + This came after a rather stoic realisation that I'm actually paying to be at University, and that when I'm not giving it my all, I'm actually losing time and money, and you know what they say: « Time wasted does not come back ». Surely enough, money spent does not come back either!! 56 + 57 + ## Open Source 58 + 59 + ### GNOME 60 + 61 + I've been contributing to GNOME Projects for a while now, usually making bug reports, contributing to some libraries and applications (sometimes unsucessfully), writing some of my own GNOME apps, and more. 62 + 63 + The year started of well, [because I became a GNOME Foundation Member](https://discourse.gnome.org/t/new-gnome-foundation-and-emeritus-members-2024-1/18779) which is a very notable title (at least for me), and I feel like my contributions are now more valued. 64 + 65 + I did apply and get accepted for Google Summer of Code in the... summer! It was quite a nice experience initially, since I got to work on Workbench, specifically adding TypeScript support to the app. I would implore you to check the application out if you want to make desktop applications (linux, mac, windows and [android soon?](https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/7555)) using GTK. The application allows you to write applications in many languages, including Rust, Vala, JavaScript, Python and now TypeScript. The application was developed by Sonny Piers, an amazing person, who was also my mentor for the Google Summer of Code intern, at least initially. I wrote about [my experience in GSoC in a blog post you can also read](https://www.vixalien.com/blog/workbench-typescript), if you're interested. 66 + 67 + Things took a weird turn when Sonny was inexplicably and mysteriously banned from the GNOME Foundation, a sudden move that left me without a mentor for a while before they found a replacement. We (GNOME Contributors) have not yet got closure about the ban, even after we voted for new GNOME Foundation Board members, and this honestly led to the reduced trust and faith I now have in the GNOME Foundation. A side effect of this is that I've reduced my contributions to GNOME, as I'm no longer ecstatic to be a contributor anymore. 68 + 69 + ### My Linux Apps 70 + 71 + I write and maintain(ed) about 3 linux GTK applications, which have had limited contributions because of said previous issues with the GNOME Foundation. 72 + 73 + The first, [Sticky Notes](https://flathub.org/apps/com.vixalien.sticky), is basically on life support, as I didn't really work on it much this year. Nevertheless, I'm still sending out maintenance updates, usually just updating the app for the new GNOME SDK, but never implementing any new features. I did plan and work on a new branch targetting markdown for a while, but never finalised it, and I don't think I have the energy, will and time to work on it anymore. Sorry folks, maybe next year! 74 + 75 + The second application I wrote, [Muzika](https://github.com/vixalien/muzika) is now officially dead, [as I wrote about in detail in a mastodon post](https://mas.to/@vixalientoots/113555145244776471). This is due to a lot of factors, but mainly due to YouTube breaking the current mechanism we used to log users in, and me not willing to play the cat and mouse game anymore. This is very unfortunate, as Muzika was basically my most famous application, and I've sunk a lot of hours into it, which made it sad to go. Anyway, we move! 76 + 77 + The last, and newest application is called [Decibels](https://apps.gnome.org/en-GB/Decibels/), and as you can probably guess from the name, is an audio (NOT music!!) player. It is a minimal application and requires minimal maintenance, which I like. I also have a co-maintainer (hey David!), which makes maintaining the app a breeze. It's already a GNOME Circle (basically recognised as a good GNOME application), and it's incubating to be part of the Core apps, which means you might soon have it installed by default in a GNOME installation! 78 + 79 + ## Work 80 + 81 + ### Karabo 82 + 83 + It's that time in University where you have to make a project, partly to get grades, and partly to validate all the hours you've sunk doing coursework or attending lectures. 84 + 85 + My venture is called Karabo, and I've registered the company and website here: [karabo.io](https://karabo.io). I will probably write about it more, as time goes on.
+717
src/content/blog/an-alpine-setup.md
··· 1 + --- 2 + title: My Alpine Setup 3 + description: Exploring a minimal but nice alpine setup. 4 + publish_date: 2024-02-18 5 + tags: [linux] 6 + hero_image: /images/posts/an-alpine-setup/banner.webp 7 + --- 8 + 9 + This is a guide to install alpine, based on my own likings. It is a relatively 10 + easy install. 11 + 12 + This installation guide is very inspired and based on 13 + [Hugo's Installation Guide][hugo-guide]. 14 + 15 + ## (Some) security considerations 16 + 17 + This install is a bit more secure because it uses an encrypted filesystem (LUKS 18 + on top of LVM). 19 + 20 + The whole is configured to work with UEFI + Secureboot, and the disk is 21 + automatically decrypted with the TPM (this secure chip inside your laptop) using 22 + [Clevis], meaning, if you boot on your laptop, your file contents will be 23 + decrypted automatically. You will need to manually enter your password if the 24 + storage drive is used outside of your computer (evil maid attack). 25 + 26 + To secure your laptop better, enable Secure Boot, and set a very strong 27 + BIOS/UEFI password. 28 + 29 + A more secure approach would be to use something like [systemd-homed] that 30 + encrypts each user's home directory separately, but that requires systemd... 31 + There's a similar tool for alpine called [pam_mount], but it's not compatible 32 + with home directories created with systemd-homed, and I haven't figured a very 33 + nice way to make it work on Alpine. 34 + 35 + ## Setup the installer 36 + 37 + Download the alpine installation disk from the 38 + [Alpine downloads page][alpine-downloads], flash it to a USB and boot it up. 39 + 40 + ## Configuring Alpine 41 + 42 + Alpine has a very nice `setup-alpine` script that setups the Alpine 43 + installation. This is different from Arch where you'd need to do everything 44 + manually. We are going to do it to configure most stup, until we arrive to the 45 + Partitioning stage. 46 + 47 + - Keyboard: `us`. 48 + - Keyboard variation: `us`. 49 + - Hostname: `name.vixalien.com`. 50 + - Network adapter: `wlan0`. 51 + - Wifi network. 52 + - Wifi password. 53 + - IP address: `dhcp`. 54 + - Networks: `done`. 55 + - Manual network configuration: `n`. 56 + - root password: `stronkpassword` 57 + - Time zone: `Africa/Kigali`. 58 + - User account: `alien`. This user will automatically be a member of the wheel 59 + group, and has (by default) privileges to use `doas` (alpine's alternative to 60 + `sudo`). 61 + - The defaults are fine for all remaining steps. 62 + 63 + > Make Sure to stop the `setup-alpine` tool when they ask for disk, as we will 64 + > do it manually. 65 + 66 + ## Partitioning 67 + 68 + Firstly, we will need to create a boot partition (aka EFI System Partition) to 69 + hold all our kernel information and bootloader (optional). Please make the 70 + partition 500 MB large, otherwise some distros like Fedora might refuse it, and 71 + the space might run out faster than you think if you decide to make backups of 72 + your UKIs or go distro-hopping, so 1GB is actually a better recommendation. 73 + 74 + Then we are going to create an [LVM] partition that will be encrypted and have 75 + volumes for all our other partitions (alpine, home, swap...). I ('d) like to 76 + setup one partition called `linux` so it can be reused by my other linux 77 + installations. 78 + 79 + ### Installing the necessary packages. 80 + 81 + For partitioning, we will use gdisk. Let's also install other packages for ext4 82 + and btrfs filesystems. 83 + 84 + ```sh 85 + apk add lsblk gptfdisk btrfs-progs e2fsprogs 86 + ``` 87 + 88 + Activate the btrfs kernel module: 89 + 90 + ```sh 91 + modprobe btrfs 92 + ``` 93 + 94 + cryptsetup is needed for LUKS encryption 95 + 96 + ```sh 97 + apk add cryptsetup 98 + ``` 99 + 100 + LVM 101 + 102 + ```sh 103 + apk add lvm2 104 + ``` 105 + 106 + ### Overwriting the disk 107 + 108 + It might be a good idea to overwrite the disk using a tool like `haveged` to 109 + clear any leftover data. 110 + 111 + > TODO: there is probably an alternative for SSDs 112 + 113 + ### Setup Disks 114 + 115 + ```sh 116 + gdisk /dev/nvme0n1 117 + ``` 118 + 119 + Use `n` to create 2 partitions: 120 + 121 + 1. EFI partition. size: `512M`. GUID: `ef00` (EFI System Partition) 122 + 2. LVM partition. size: `(leave blank)`. GUID: `8309` (Linux LUKS) 123 + 124 + > For more info about gdisk Hex codes see 125 + > https://wiki.archlinux.org/title/GPT_fdisk#Partition_type 126 + 127 + Populate `/dev` with new partitions: 128 + 129 + `partprobe /dev/nvme0n1` 130 + 131 + ### Identify your partitions 132 + 133 + Find your partition names using `lsblk`: 134 + 135 + ``` 136 + NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS 137 + nvme0n1 259:0 0 511.9G 0 disk 138 + ├─nvme0n1p1 259:1 0 500M 0 part 139 + └─nvme0n1p2 259:2 0 511.5M 0 part 140 + ``` 141 + 142 + In this case, `/dev/nvme0n1p1` is the EFI partition, while `/dev/nvme0n1p2` will 143 + be our LUKS partition. 144 + 145 + ### Configuring LUKS 146 + 147 + This step will ask for a password to encrypt your whole disk with. Remember it, 148 + and make it strong. 149 + 150 + ```sh 151 + cryptsetup luksFormat /dev/nvme0n1p2 152 + ``` 153 + 154 + > You might want to familiarise yourself with 155 + > [different LUKS options][luks-options]. I find the defaults okay. 156 + 157 + Open the LUKS partition: 158 + 159 + ```sh 160 + cryptsetup luksOpen /dev/nvme0n1p2 lvmcrypt 161 + ``` 162 + 163 + ### LVM Physical and Logical Volumes 164 + 165 + Create a physical volume 166 + 167 + ```sh 168 + pvcreate /dev/vg0/lvmcrypt 169 + ``` 170 + 171 + Create a virtual volume named `vg0` (or something else, this one is memorable) 172 + 173 + ```sh 174 + vgcreate vg0 /dev/vg0/lvmcrypt 175 + ``` 176 + 177 + #### Create partitions 178 + 179 + Swap partition. I have a 32GB RAM laptop: 180 + 181 + ```sh 182 + lvcreate -L 32G vg0 -n swap # I have a 32GB RAM laptop 183 + lvcreate -L 50G vg0 -n alpine # root 184 + lvcreate l 100%FREE vg0 -n home 185 + ``` 186 + 187 + #### Create file systems 188 + 189 + ```sh 190 + mkfs.exfat /dev/nvme0n1p1 191 + mkfs.btrfs /dev/vg0/alpine 192 + mkfs.ext4 /dev/vg0/home 193 + ``` 194 + 195 + #### Activate swap 196 + 197 + ```sh 198 + mkswap /dev/vg0/swap 199 + swapon /dev/vg0/swap 200 + ``` 201 + 202 + #### Create Btrfs Subvolumes 203 + 204 + Temporarily mount the `alpine` partition to create subvolumes. 205 + 206 + ```sh 207 + mount /dev/vg0/alpine /mnt 208 + ``` 209 + 210 + Create the subvolumes adapted from 211 + [Snapper's Suggested filesystem layout][snapper-layout] 212 + 213 + ```sh 214 + btrfs subvolume create /mnt/@ # / 215 + btrfs subvolume create /mnt/@var_log # /var/log 216 + ``` 217 + 218 + Find the id of the `@` subvolume. Note it as `<root-subvol-id>`. It will 219 + probably be 256 or something. 220 + 221 + ```sh 222 + btrfs subvolume list /mnt 223 + ``` 224 + 225 + Change the default subvolume to `@` (Replace `<root-subvol-id>` with the ID you 226 + got from the previous step). 227 + 228 + ```sh 229 + btrfs subvolume set-default <root-subvol-id> / 230 + ``` 231 + 232 + Unmount the partition 233 + 234 + ```sh 235 + umount /mnt 236 + ``` 237 + 238 + #### Mount partitions 239 + 240 + Create mountpoints and mount our partitions and subvolumes 241 + 242 + ```sh 243 + mount /dev/vg0/alpine -o subvol=@ /mnt/ 244 + 245 + # Create mountpoints 246 + mkdir -p /mnt/boot /mnt/boot /mnt/var/log 247 + 248 + # Mount the remaining subvolumes 249 + mount /dev/vg0/alpine -o subvol=@var_log /mnt/var/log 250 + 251 + # Mount the efi system partition 252 + mount /dev/nvme0n1p1 /mnt/boot 253 + 254 + # Mount the home partition 255 + mount /dev/vg0/home /mnt/home 256 + ``` 257 + 258 + ## Installation 259 + 260 + ### Install a base alpine system 261 + 262 + ```sh 263 + BOOTLOADER=none setup-disk -k edge /mnt 264 + ``` 265 + 266 + The `BOOTLOADER=none` tells the script to not install any bootloader (grub is 267 + the default), and `-k edge` tells the script to install the `edge` kernel 268 + instead of the `lts` one. 269 + 270 + ### Chroot into the filesystem 271 + 272 + ```sh 273 + chroot /mnt 274 + mount -t proc proc /proc 275 + mount -t devtmpfs dev /dev 276 + ``` 277 + 278 + Switch to the edge branch, and enable the community and testing repositories. 279 + This is done by editing `/etc/apk/repositories` and replacing its contents with: 280 + 281 + ``` 282 + http://dl-cdn.alpinelinux.org/alpine/edge/main 283 + http://dl-cdn.alpinelinux.org/alpine/edge/community 284 + http://dl-cdn.alpinelinux.org/alpine/edge/testing 285 + ``` 286 + 287 + > You can choose a [mirror] closer to you if you want 288 + 289 + #### Setup a local apk cache 290 + 291 + I like having my downloaded apks available locally. In case I want to reinstall 292 + them or something... You can skip this step if you want a really minimal design. 293 + When prompted, say `/var/cache/apk` for the cache directory. 294 + 295 + ```sh 296 + setup-apkcache 297 + ``` 298 + 299 + ### (No) bootloader 300 + 301 + The next step would be to install a bootloader like GRUB or 302 + systemd-boot/gummiboot. However, we don't need one of them since we instead 303 + create a [Unified Kernel Image][uki] (UKI) which can be directly booted by the 304 + UEFI firmware, hence removing the need for a traditional bootloader. A UKI 305 + contains the following, and some more: 306 + 307 + - The kernel itself 308 + - The kernel’s command line parameters 309 + - A small stub that execute the kernels with that command line 310 + - The [initramfs] (or initrd): a small read-only filesystem with the necessary 311 + userspace utilities to boot into the main system. 312 + 313 + The stub itself is provided by the `gummiboot-efistub` package. It is considered 314 + deprecated, but no solid alternative is available. The bundle itself is built by 315 + `efi-mkuki`, and `secureboot-hook` will rebuild the bundle after each kernel 316 + upgrade. 317 + 318 + ```sh 319 + apk add secureboot-hook gummiboot-efistub 320 + ``` 321 + 322 + Install `blkid` which will be used in a moment. This tool prints the UUID (and a 323 + few other details) for a specified partition. This is the recommended way to 324 + address a partition ambiguously: 325 + 326 + ```sh 327 + apk add blkid 328 + ``` 329 + 330 + #### secureboot-hook 331 + 332 + Edit `/etc/kernel-hooks.d/secureboot.conf` with the following contents. 333 + 334 + ``` 335 + cmdline=/etc/kernel/cmdline 336 + signing_disabled=yes 337 + output_dir="/boot/EFI/Linux" 338 + output_name="alpine-linux-{flavor}.efi" 339 + ``` 340 + 341 + `/<efi>/EFI/Linux` is a more or less standard directory, and will be discovered 342 + by `systemd-boot` if you have that installed. 343 + 344 + Signing is disabled only temporarily until I install the proper keys. 345 + 346 + #### /etc/kernel/cmdline 347 + 348 + Also create a file `/etc/kernel/cmdline` that will contain arguments passed to 349 + the kernel from the bootloader (UKI, in this case). 350 + 351 + ``` 352 + root=UUID=5021db58-cc3a-4829-a630-2d468f8d1761 353 + rootflags=subvol=@ 354 + rootfstype=btrfs 355 + cryptroot=UUID=0db973a0-1b95-4a23-a63f-cb6248fe2bf7 356 + cryptdm=lvmcrypt 357 + cryptkey 358 + modules=sd-mod,btrfs,nvme 359 + quiet 360 + ro 361 + ``` 362 + 363 + The `root` UUID can be determined with: 364 + 365 + ```sh 366 + blkid /dev/vg0/alpine >> /etc/kernel/cmdline 367 + ``` 368 + 369 + The `cryptroot` UUID: 370 + 371 + ```sh 372 + blkid /dev/nvme0n1p2 >> /etc/kernel/cmdline 373 + ``` 374 + 375 + #### initrams 376 + 377 + Edit `/etc/mkinitfs/mkinitfs.conf` to add `features` which are needed for our 378 + encrypted root setup to work. While editing this line, it is also safe to delete 379 + `virtio`, which is used only in virtual machines. Also add `kms` to enable 380 + kernel mode setting. These features will be included in the generated 381 + [initramfs]. 382 + 383 + ```sh 384 + features="ata base ide scsi usb btrfs ext4 lvm kms keymap nvme cryptsetup cryptkey resume" 385 + disable_trigger=yes 386 + ``` 387 + 388 + #### Boot Entry 389 + 390 + We use `efibootmgr` to create a boot entry that (hopefully) shows in the EFI 391 + firmware, although you can always use the UFI's boot from file function or 392 + execute the UKI from a UEFI shell. I heard reports that the boot entries may 393 + disappear after firmware upgrades or other vendor EFI quirks, so bear that in 394 + mind. 395 + 396 + First exit the chroot using `exit`. We can't use `efibootmgr` inside the chroot 397 + because it won't be able to read and set the EFI variables, mounted at 398 + `/sys/firmware/efi/efivars`. Then install efibootmgr: 399 + 400 + ```sh 401 + apk add efibootmgr 402 + ``` 403 + 404 + Then create a boot entry named "Alpine Linux". 405 + 406 + ```sh 407 + efibootmgr --disk /dev/nvme0n1 --part 1 --create --label 'Alpine Linux' --load /EFI/Linux/alpine-linux-edge.efi --verbose 408 + ``` 409 + 410 + > Note: This procedure only needs to be done once; after that the Unified Kernel 411 + > Image will be generated automatically every time the kernel is upgraded. 412 + 413 + Finally, trigger the newly created kernel hook so that all the right files are 414 + copied into `/boot` 415 + 416 + ``` 417 + apk fix kernel-hooks 418 + ``` 419 + 420 + ### Hibernation 421 + 422 + To setup hibernation, we'll first need to find the UUID of our swap partition: 423 + 424 + ```sh 425 + lsblk -f 426 + ``` 427 + 428 + Edit the `/etc/kernel/cmdline` to let the system know where you will be resuming 429 + from. 430 + 431 + ```prop 432 + resume=UUID=<UUID of /dev/vg0/swap> 433 + ``` 434 + 435 + Enable the swap service during boot: 436 + 437 + ```sh 438 + rc-update add swap default 439 + ``` 440 + 441 + Install `zzz` and test hibernation 442 + 443 + ```sh 444 + apk add zzz 445 + zzz -Z # or ZZZ 446 + ``` 447 + 448 + You can now reboot and test your system 449 + 450 + ## Desktop 451 + 452 + ### Fonts 453 + 454 + Installing the Noto fonts make almost every characters rendered (CJK and emoji): 455 + 456 + ```sh 457 + apk add font-noto font-noto-cjk font-noto-extra font-noto-emoji 458 + ``` 459 + 460 + Install my preferred fonts 461 + 462 + ```sh 463 + apk add font-jetbrains-mono font-liberation-serif 464 + ``` 465 + 466 + And configure fontconfig to use them at `/etc/fonts/local.conf` 467 + 468 + ```xml 469 + <?xml version="1.0"?> 470 + <!DOCTYPE fontconfig SYSTEM "fonts.dtd"> 471 + <fontconfig> 472 + <alias> 473 + <family>sans-serif</family> 474 + <prefer> 475 + <family>Cantarell</family> 476 + </prefer> 477 + </alias> 478 + <alias> 479 + <family>monospace</family> 480 + <prefer> 481 + <family>JetBrains Mono</family> 482 + </prefer> 483 + </alias> 484 + <alias> 485 + <family>serif</family> 486 + <prefer> 487 + <family>Liberation Serif</family> 488 + </prefer> 489 + </alias> 490 + </fontconfig> 491 + ``` 492 + 493 + ### Snapper 494 + 495 + [Snapper] is a tool to automatically or manually take snapshots of btrfs 496 + systems. I use it because I sometimes mess up my root system. Let's install it 497 + and setup up our first snapshot. 498 + 499 + > Remember: Snapshots are **NOT** backups. 500 + 501 + ```sh 502 + apk add snapper 503 + 504 + snapper -c root create-config / 505 + ``` 506 + 507 + This will create a new subvolume at `/.snapshots`. Each snapshot will be stored 508 + at `/.snapshot/<snapshot-number>/snapshot`. It will also add a new line to 509 + `/etc/conf.d/snapper`. 510 + 511 + Create a first snapshot: 512 + 513 + ```sh 514 + snapper -c config create --description 'Base Installation' 515 + ``` 516 + 517 + You can also use LVM snaphots, but that is an alternative I have not explored 518 + yet. It like a more interesting option tbf. 519 + 520 + ### Enable & Start function 521 + 522 + I like to have this function handy. It is synonymous to 523 + `systemctl enable --now`. You can put it in /etc/profile or somewhere 524 + 525 + ```sh 526 + rc-init() { 527 + if [ $# -lt 1 ] || [ $# -gt 2 ] 528 + then 529 + >&2 echo "Invalid number of arguments provided (1-2 acceptable)" 530 + return 1 531 + fi 532 + 533 + RUNLEVEL="${2:-default}"; 534 + rc-service $1 start 535 + rc-update add $1 $RUNLEVEL; 536 + } 537 + 538 + rc-deinit() { 539 + if [ $# -lt 1 ] || [ $# -gt 2 ] 540 + then 541 + >&2 echo "Invalid number of arguments provided (1-2 acceptable)" 542 + return 1 543 + fi 544 + 545 + RUNLEVEL="${2:-default}"; 546 + rc-service $1 stop 547 + rc-update del $1 $RUNLEVEL; 548 + } 549 + ``` 550 + 551 + ### GNOME 552 + 553 + Setup the GNOME Desktop Environment (what I use, No I don't use sway or hyprland 554 + yet:\)) 555 + 556 + ```sh 557 + setup-desktop gnome 558 + ``` 559 + 560 + Allow updates to be carried out in GNOME Software. 561 + 562 + ```sh 563 + rc-init apk-polkit-server 564 + ``` 565 + 566 + Allow switching the power profiles in the quick settings 567 + 568 + ```sh 569 + apk add power-profiles-daemon 570 + ``` 571 + 572 + If you have a convertible, allow turning your laptop to flip it 573 + 574 + ```sh 575 + apk add iio-sensor-proxy 576 + ``` 577 + 578 + Hardware acceleration 579 + 580 + ```sh 581 + apk add intel-media-driver 582 + ``` 583 + 584 + ### NetworkManager 585 + 586 + Setup NetworkManager to manage your... network. Also setup WiFi and a TUI 587 + (`nmtui`). I also prefer using `iwd` instead of `wpa_supplicant` as the actual 588 + WiFi backend, since it's what I'm familiar with. 589 + 590 + ```sh 591 + apk add networkmanager networkmanager-wifi networkmanager-wifi networkmanager-dnsmasq 592 + ``` 593 + 594 + I like to use this configuration: 595 + 596 + ```toml 597 + [main] 598 + dhcp=internal 599 + plugins=keyfile 600 + dns=dnsmasq 601 + 602 + [device] 603 + wifi.scan-rand-mac-address=yes 604 + wifi.backend=wpa_supplicant 605 + 606 + [connectivity] 607 + uri=http://nmcheck.gnome.org/check_network_status.txt 608 + ``` 609 + 610 + There are probably other configuration options to set. 611 + 612 + Enable and activate the service: 613 + 614 + ```sh 615 + rc-init networkmanager 616 + ``` 617 + 618 + Since we are now using NetworkManager to manage our connections, we can disable 619 + the default networking service. 620 + 621 + ```sh 622 + rc-update del networking 623 + ``` 624 + 625 + You might also see that `chronyd` takes a while to sync on boot. We can tell it 626 + to do that in the background on boot instead by editing `/etc/conf.d/chronyd` 627 + and setting 628 + 629 + ``` 630 + FAST_STARTUP=yes 631 + ``` 632 + 633 + ### Bluetooth 634 + 635 + ```sh 636 + apk add bluez bluez-openrc 637 + ``` 638 + 639 + Enable the Bluetooth experimental features to view the battery charge of your 640 + bluetooth earphones at `/etc/bluetooth/main.conf` 641 + 642 + ```toml 643 + Experimental=True 644 + ``` 645 + 646 + Reboot or load the kernel module 647 + 648 + ```sh 649 + modprobe btusb 650 + ``` 651 + 652 + Start & enable the bluetooth service 653 + 654 + ```sh 655 + rc-service bluetooth start 656 + rc-update add bluetooth default 657 + ``` 658 + 659 + ### Sound 660 + 661 + By default, checking `dmesg` seems to indicate missing firmware: 662 + 663 + ``` 664 + > dmesg | grep firmw 665 + [ 1.096997] i915 0000:00:02.0: [drm] Finished loading DMC firmware i915/tgl_dmc_ver2_12.bin (v2.12) 666 + [ 36.239398] iwlwifi 0000:00:14.3: loaded firmware version 77.2df8986f.0 QuZ-a0-hr-b0-77.ucode op_mode iwlmvm 667 + [ 36.363387] Bluetooth: hci0: Minimum firmware build 1 week 10 2014 668 + [ 36.365863] Bluetooth: hci0: Found device firmware: intel/ibt-19-0-4.sfi 669 + [ 36.447917] sof-audio-pci-intel-tgl 0000:00:1f.3: Direct firmware load for intel/sof/sof-tgl.ri failed with error -2 670 + [ 36.447919] sof-audio-pci-intel-tgl 0000:00:1f.3: error: sof firmware file is missing, you might need to 671 + [ 36.447921] sof-audio-pci-intel-tgl 0000:00:1f.3: error: failed to load DSP firmware -2 672 + [ 36.714932] psmouse serio1: trackpoint: Elan TrackPoint firmware: 0xa1, buttons: 3/3 673 + [ 38.519487] Bluetooth: hci0: Waiting for firmware download to complete 674 + ``` 675 + 676 + ```sh 677 + apk add sof-firmware 678 + ``` 679 + 680 + Reboot or try to load the kernel module using the relevant soundcard name found 681 + using `find /lib/modules/* -type f -name '*.zst' -name '*sof*' -name '*tgl'`: 682 + 683 + ```sh 684 + modprobe sof-audio-pci-intel-tgl 685 + ``` 686 + 687 + Install PipeWire packages and friends. 688 + 689 + ```sh 690 + apk pipewire wireplumber pipewire-pulse pipewire-alsa pipewire-spa-bluez gst-plugin-pipewire 691 + ``` 692 + 693 + ## Future considerations 694 + 695 + - Use Unl0kr 696 + - Use Clevis 697 + 698 + References: 699 + 700 + - ["Setting up an Alpine Linux workstation" by Hugo Osvaldo Barrera][hugo-guide] 701 + - ["In Praise of Alpine and APK" by Hugo Osvaldo Barrera](https://whynothugo.nl/journal/2023/02/18/in-praise-of-alpine-and-apk/) 702 + - ["In Praise of Alpine" by Drew Devault](https://drewdevault.com/2021/05/06/Praise-for-Alpine-Linux.html) 703 + - [Full disk encryption secure boot - Alpine Wiki](https://wiki.alpinelinux.org/wiki/Full_disk_encryption_secure_boot) 704 + - [UEFI Secure Boot - Alpine Wiki](https://wiki.alpinelinux.org/wiki/UEFI_Secure_Boot) 705 + 706 + [hugo-guide]: https://whynothugo.nl/journal/2023/11/19/setting-up-an-alpine-linux-workstation/ 707 + [alpine-downloads]: https://alpinelinux.org/downloads/ 708 + [luks-options]: https://wiki.archlinux.org/title/Dm-crypt/Device_encryption#Encryption_options_for_LUKS_mode 709 + [snapper-layout]: https://wiki.archlinux.org/title/Snapper#Suggested_filesystem_layout 710 + [mirror]: https://mirrors.alpinelinux.org/ 711 + [uki]: https://wiki.archlinux.org/title/Unified_kernel_image 712 + [initramfs]: https://wiki.archlinux.org/title/Arch_boot_process#initramfs 713 + [clevis]: https://wiki.archlinux.org/title/Clevis 714 + [systemd-homed]: https://systemd.io/HOME_DIRECTORY/ 715 + [pam_mount]: https://wiki.archlinux.org/title/pam_mount 716 + [lvm]: https://wiki.archlinux.org/title/LVM 717 + [snapper]: https://wiki.archlinux.org/title/Snapper
+238
src/content/blog/deno-blog.md
··· 1 + --- 2 + title: Hello Deno Blog! 3 + description: "Converting my blog to Deno. Spoiler Alert: it's easy and fast!" 4 + publish_date: 2022-08-20 5 + hero_image: /images/posts/deno-blog/banner.webp 6 + tags: [code] 7 + --- 8 + 9 + Hello! I'm back to blogging! 10 + 11 + Today, I bring very exciting news!! I migrated my blog to [Deno] fully. And now 12 + it runs on the EDGE. 13 + 14 + ## Preface 15 + 16 + Deno is really a solid choice: 17 + 18 + - Deno is much faster than Node in practice (think time to build) 19 + - Deno is no longer a project in it's infancy 20 + - Amazing community, packages & docs (seriously, everything is documented) 21 + - [Deno Deploy][deploy] 22 + 23 + I recommend you watch [Ryan Dahl's talk about his dream stack][dream-stack], 24 + which inspired me to migrate my blog to Deno. Ryan is the creator of both Deno 25 + and NodeJS. He created Deno to try and fix the mistakes and regrets that have 26 + been made in NodeJS. Watch [10 Things I Regret About Node.js][mistakes] by Ryan 27 + Dahl, which is his legendary talk about him explaining the mistakes he made with 28 + NodeJS. 29 + 30 + ## Plot 31 + 32 + I decided to use the [`deno_blog`][deno_blog] package as it seems too easy to 33 + work with. It's also made by the Deno core team. For example, here is the entire 34 + configuration of Ryan Dahl's blog. It's just a single Javascript file and the 35 + other files are markdown and a few other static files such as images that all 36 + live in `/posts`. 37 + 38 + ```js 39 + import blog, { ga, redirects } from "https://deno.land/x/blog@0.3.3/blog.tsx"; 40 + 41 + blog({ 42 + title: "Ryan Dahl", 43 + author: "Ryan Dahl", 44 + avatar: "./ry.jpg", 45 + avatarClass: "full", 46 + links: [ 47 + { title: "Email", url: "mailto:ry@tinyclouds.org" }, 48 + { title: "GitHub", url: "https://github.com/ry" }, 49 + ], 50 + background: "#fff", 51 + middlewares: [ 52 + ga("UA-91675022-1"), 53 + redirects({ 54 + "iocp-links.html": "iocp_links", 55 + "rant.html": "rant", 56 + }), 57 + ], 58 + }); 59 + ``` 60 + 61 + In the beginning, I built the very same app using a similar configuration but I 62 + soon realised I'm too stubborn and need something more custom. Mainly because I 63 + had integrated too much with `marked` (which is a library that converts markdown 64 + to HTML) and Deno Blog uses `gfm` (Github Flavored Markdown). I know it's bad 65 + (fragmentation instead of contributing new features) but I forked `deno_blog` 66 + and made downstream changes because I thought this might be overkill comparing 67 + to the KISS nature of everything in Deno. I hence created [my fork][fork] so 68 + that it can render my site exactly as it was before all while being open to 69 + being extended. Success! 70 + 71 + Here is my current script (Notice the different import URL): 72 + 73 + ```tsx 74 + /** @jsx h */ 75 + 76 + import blog, { 77 + h, 78 + highlight, 79 + imageContainer, 80 + } from "https://deno.land/x/vixalien_deno_blog@0.4.9/blog.tsx"; 81 + 82 + blog({ 83 + author: "Angelo Verlain", 84 + title: "vixalien's blog", 85 + avatar: "favicon/maskable.webp", 86 + avatarClass: "border-none rounded-full", 87 + port: 3001, 88 + dateStyle: "medium", 89 + links: [ 90 + { title: "Email", url: "mailto:hey@vixalien.com" }, 91 + { title: "GitHub", url: "https://github.com/vixalien" }, 92 + { title: "Twitter", url: "https://twitter.com/vixalientweets" }, 93 + { title: "Resume.pdf", url: "/Resume.pdf", icon: <IconFile /> }, 94 + ], 95 + canonicalUrl: Deno.env.get("URL"), 96 + description: 97 + `Hello! I'm Angelo Verlain, but you can call me vixalien. I am a web \ 98 + developer. This is my website, a collection of projects and writings.`, 99 + middlewares: [await highlight(), await imageContainer()], 100 + headLinks: [ 101 + { 102 + rel: "apple-touch-icon", 103 + sizes: "192x192", 104 + href: "/favicon/android-chrome-192x192.webp", 105 + }, 106 + { 107 + rel: "icon", 108 + type: "image/webp", 109 + sizes: "192x192", 110 + href: "/favicon/android-chrome-192x192.webp", 111 + }, 112 + { 113 + rel: "icon", 114 + type: "image/webp", 115 + sizes: "512x512", 116 + href: "/favicon/android-chrome-512x512.webp", 117 + }, 118 + { rel: "manifest", href: "/manifest.json" }, 119 + { rel: "shortcut icon", href: "/favicon.ico" }, 120 + { href: "/css/app.css", rel: "stylesheet" }, 121 + ], 122 + lang: "en-US", 123 + }); 124 + 125 + function IconFile() { 126 + return ( 127 + <svg 128 + width="1em" 129 + height="1em" 130 + stroke-width={2} 131 + stroke="currentColor" 132 + fill="none" 133 + stroke-linecap="round" 134 + stroke-linejoin="round" 135 + viewBox="0 0 24 24" 136 + > 137 + <path d="M0 0h24v24H0z" stroke="none" /> 138 + <path d="M14 3v4a1 1 0 001 1h4" /> 139 + <path d="M17 21H7a2 2 0 01-2-2V5a2 2 0 012-2h7l5 5v11a2 2 0 01-2 2zM9 9h1M9 13h6M9 17h6" /> 140 + </svg> 141 + ); 142 + } 143 + ``` 144 + 145 + And all I need to do is run a single commad, then the app is live (INSTANT: NO 146 + COMPILATION). 147 + 148 + ```bash 149 + deno run -A mod.tx 150 + ``` 151 + 152 + Then to push it to production, I just push to Github, at which point 153 + [Deno Deploy][deploy] pushes my script to no less than 30 locations worldwide 154 + (the real edge, 👀 Vercel) for free. 155 + 156 + The source of my [new website is hosted on Github][source] on the deno branch. 157 + 158 + ## Aftermath 159 + 160 + Pictures are work 100 words right? 161 + 162 + ### Layout 163 + 164 + #### Before 165 + 166 + ![Old layout on Node](/images/posts/deno-blog/layout-before.webp) 167 + 168 + #### After 169 + 170 + ![New layout on Deno](/images/posts/deno-blog/layout-after.webp) 171 + 172 + > Comment: No visible changes, but under the hood, everything changed. 173 + 174 + ### Total Files 175 + 176 + #### Before 177 + 178 + ![Total files in filesystem before](/images/posts/deno-blog/files-before.webp) 179 + 180 + #### After 181 + 182 + ![Total files in filesystem using Deno](/images/posts/deno-blog/files-after.webp) 183 + 184 + > Command: `tree .` 185 + > 186 + > Comment: Deno uses significantly less files per project as dependencies are 187 + > stored in central cache (`~/.cache/deno`). Node's `node_modules` caused it's 188 + > demise. 189 + 190 + ### Disk Usage 191 + 192 + #### Before 193 + 194 + ![Disk usage on Node (~11 dependencies)](/images/posts/deno-blog/du-before.webp) 195 + 196 + #### After 197 + 198 + ![Disk usage on Deno (source code, =1 dependency)](/images/posts/deno-blog/du-after.webp) 199 + 200 + ![Disk usage on Deno (in Deno cache)](/images/posts/deno-blog/du-after-cache.webp) 201 + 202 + > Command: `du -h .` 203 + > 204 + > Comment: Deno caches every single URL (compressed). If you run an app once, 205 + > it's dependencies will be cached for the next time. Cache is stored in a 206 + > central location so that multiple projects can reuse the cache which is not 207 + > the case for Node 208 + 209 + ### Performance 210 + 211 + #### Before 212 + 213 + ![Performance on Node (Deployed to Vercel)](/images/posts/deno-blog/perf-before.webp) 214 + 215 + #### After 216 + 217 + ![New layout on Deno (Deployed to Deno Deploy)](/images/posts/deno-blog/perf-after.webp) 218 + 219 + > Measured using [Lighthouse](https://web.dev/measure). 220 + > 221 + > Comment: Deno is almost always 2 times faster because there is good management 222 + > of code on Deno(on my part) which causes only 2 requests to be served, but 223 + > nevertheless it's quite amazing to see Deno win because the app deployed to 224 + > Vercel is a static app whereas on Deno some actual JS code runs everytime a 225 + > user hits a route. This is because Deno Deploy pushes to more than 30 226 + > locations for free so when you actually request a resource, it's fetched from 227 + > the nearest data center unlike Vercel which routes all requests to Washington 228 + > DC (in the free plan atleast). 229 + 230 + Thanks for reading! Have a nice day! 231 + 232 + [deno]: https://deno.land 233 + [deploy]: https://deno.com/deploy 234 + [dream-stack]: https://www.youtube.com/watch?v=3NR9Spj0DmQ 235 + [deno_blog]: https://github.com/denoland/deno_blog 236 + [mistakes]: https://www.youtube.com/watch?v=M3BM9TB-8yA 237 + [fork]: https:/github.com/vixalien/vixalien_deno_blog/ 238 + [source]: https://github.com/vixalien/dotio2/tree/deno
+440
src/content/blog/explosiv-blog.md
··· 1 + --- 2 + title: Building a blog with Explosiv 3 + description: "Building a static lightweight and fast blog with Explosiv." 4 + publish_date: 2021-12-19 5 + tags: [code, tutorial] 6 + --- 7 + 8 + Earlier this year, I created [Explosiv ↗][explosiv], a lightweight & fast static site generator that allows pages to be built with JSX. This is a tutorial on how to build a functional blog with Explosiv. 9 + 10 + # Terminology 11 + 12 + We'll first talk about how the blog will be built. You can directly [jump to the programming part](#code) or directly view [the source code of the final blog on Github][explosiv-blog-source]. 13 + 14 + ## What's in *this blog*? 15 + 16 + The blog will be a simple one with room for improvement (I invite you to be creative.) It will simply render a homepage, an about page and a group of posts. That's it. We'll not be using any heavy styling or custom components library. Of course we'll use **Explosiv** to build the blog and we'll write the blog posts themselves in **Markdown**. 17 + 18 + ## Explosiv? 19 + 20 + [Explosiv ↗][explosiv] is a NodeJS framework that uses JSX to render pages. It transforms `.jsx` files into `.html` files. That is: you write code that uses components, run js etc and Explosiv convert them into native HTML ready to be displayed to your favorite web browser. 21 + 22 + ## JSX? 23 + 24 + [JSX ↗][jsx] stands for **XHTML in JSX** and it allows you to write HTML inside JS files simplifying data binding. JSX was created by the React team and is famously used within React so if you come from React, Explosiv will be easy for you to understand because it uses that same loved JSX syntax. Here is an example of JSX syntax. 25 + 26 + ```jsx 27 + // JSX syntax is coool! 28 + let Site = (data) => { 29 + return <div>Hello {data.name}!</div> 30 + } 31 + ``` 32 + 33 + <details><summary>Why not use React instead? or NextJS? or Gatsby? - Rant</summary> 34 + 35 + &nbsp; 36 + 37 + ## Why not use React instead? or NextJS? or Gatsby? 38 + 39 + React is only a library. React is in the core of NextJS or Gatsby and they all use it to create their own opinionated way of rendering React pages into HTML pages. 40 + 41 + [NextJS ↗][nextjs] is a framework created by Vercel and it provides many features to build very complex web apps: API Routes, Internationalisation, Analytics, Typescript, Image Optimization. It's many features means you can use it to create any type of website, from TikTok to Twitch to [others ↗][nextjs-showcase]. However this means that it's also pretty bloated for simple websites like blogs where you end up not using much of the features. And the site ends up containing many and many JS files you'll not use and takes some time to load. 42 + 43 + ![NextJS waterfall][nextjs-waterfall] 44 + 45 + As you can see in the above screenshot from https://www.joshwcomeau.com/blog/how-i-built-my-blog. NextJS served more than 120 requests weighing 6 MBs in 13 seconds. Hmm?? 46 + 47 + [Gatsby ↗][gatsby] touts itself as a fast static site generator that also uses React. [It is NOT fast ↗][gatsby-hn]. It takes about 30 seconds to make a production build. Imagine what would happen if you customize your site. Plus there are also some features that I think are overkill like GraphQL integrations. I mean I get it, but I would like to install GraphQL as a plugin, not baked into my static site that won't use it 48 + 49 + <hr></details><br><br> 50 + 51 + 52 + ## Markdown? 53 + 54 + [Markdown][markdown] is a lightweight language that will convert plain text to formatted text. It's the language we'll use to write our own blog posts. It is used by bloggers, software developers and documentation writers. All those `README.md` files on GitHub are Markdown!. You can view the simple syntax of Markdown here: 55 + 56 + ```md 57 + # This is a heading 58 + 59 + This is a paragraph wil _emphasized_ and **strongly emphasized** text. And this is [a link to Google](https://google.com) 60 + 61 + 1. This is an ordered list 62 + 2. Another list item 63 + 3. - A nested unordered list 64 + - Another list item. 65 + ``` 66 + 67 + This blog post you are reading is written in markdown too! You can [view the source code here ↗][post-source]. 68 + 69 + # Code 70 + 71 + Explosiv is a NodeJS framework. That means you'll need to have NodeJS installed first. NodeJS comes with a package manager called `npm` and we'll use it to install Explosiv. 72 + 73 + ## 1. Install Explosiv 74 + 75 + The first step is creating a folder for your blog. I used `explosiv-blog` for mine. Open the folder in your favorite shell (or command prompt or command line interface). You'll first need to initialize the folder as a NodeJS project. 76 + 77 + ```bash 78 + npm init -y 79 + ``` 80 + 81 + NPM will generate a `package.json` that will be used to identify your app and manage your dependencies. The next step is to install Explosiv. 82 + 83 + ```bash 84 + npm install explosiv 85 + ``` 86 + 87 + You're now ready to start building with Explosiv. 88 + 89 + ## 2. Create homepage and about page. 90 + 91 + Now go ahead and create a folder called `pages` at the root of your project. This folder will hold all Explosiv pages. 92 + 93 + ### Homepage 94 + 95 + Create a file called `index.js` for our homepage. `index` is a special name as it denotes that this file will be the first one that the user sees when they visit our site for the first time. Add some simple JSX to our index page to show a warm welcome message to visitors of the blog. 96 + 97 + ```jsx 98 + // index.js 99 + let Homepage = () => { 100 + return <> 101 + <Head> 102 + <title>my blog</title> 103 + <meta name="description" content="This is my own blog"/> 104 + </Head> 105 + <main> 106 + <h1>Welcome to my blog</h1> 107 + <p>This is my cool new blog built with Explosiv. <a href="/about">About Me</a></p> 108 + </main> 109 + </> 110 + }; 111 + 112 + export default Homepage; 113 + ``` 114 + 115 + We can now see how our site will look in the browser. Switch to your shell and run the following command. 116 + 117 + ### Explosiv Development mode 118 + 119 + ```bash 120 + npx explosiv dev 121 + ``` 122 + 123 + This will start Explosiv in Development Mode, build the app then serve it locally at http://localhost:3000. Visit the URL to view the homepage. 124 + 125 + ![Blog homepage][blog-homepage] 126 + 127 + ### About Page 128 + 129 + Create a file called `about.js`. This will be our about page and it will be accessible at `/about` on our website. Add some JSX for the about page as well. 130 + 131 + ```jsx 132 + // about.js 133 + let About = () => { 134 + return <> 135 + <Head> 136 + <title>about my blog</title> 137 + <meta name="description" content="About my blog"/> 138 + </Head> 139 + <main> 140 + <h1>About my blog</h1> 141 + <p>Hey there! Welcome to my new blog built with Explosiv. Here you can find all my blog posts. <a href="/">Go back to homepage</a></p> 142 + </main> 143 + </> 144 + }; 145 + 146 + export default About; 147 + ``` 148 + 149 + Now go to http://localhost:3000/about to view the about page. Note that Explosiv automatically rebuilt the app because we started explosiv in development mode. 150 + 151 + > **ProTip:** Creating a page at `pages/about.js` is equivalent to creating one at `pages/about/index.js`. 152 + 153 + ## 3. Styling 154 + 155 + Now the page looks a little bit ugly doesn't it? We can add CSS styles to make our site look nicer. We'll create a folder called `public/` and create a stylesheet at `public/app.css`. Files in the `public/` folder will be publicly accessible so you can visit http://localhost:3000/app.css to view the stylesheet. 156 + 157 + ```css 158 + /* public/app.css */ 159 + body { 160 + max-width: 600px; 161 + padding: 0 20px; 162 + margin: 0 auto; 163 + font-family: system-ui, -apple-system, "Segoe UI", "Roboto", "Ubuntu", "Cantarell", "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" 164 + } 165 + ``` 166 + 167 + Now to allow Explosiv to include the above CSS, create a document file at `pages/_document.js` to customize the overall behavior of the blog. 168 + 169 + ```jsx 170 + // pages/_document.js 171 + let Document = () => { 172 + return (<html lang="en"> 173 + <head> 174 + <meta charset="utf-8"/> 175 + <meta name="viewport" content="width=device-width,initial-scale=1"/> 176 + <link rel="stylesheet" href="/app.css"/> 177 + </head> 178 + <body> 179 + <div class="root"></div> 180 + </body> 181 + </html>) 182 + } 183 + 184 + export default Document; 185 + ``` 186 + 187 + The `_document.js` file is a special one because it provides a wrapper to the whole site, hence it can be used to customize the site. Let's explain the components of this `_document.js`: 188 + 189 + - `<html lang="en">` specify the language of our site. 190 + - `<meta charset="utf-8"/>` to specify the character set of our site to prevent incorrect renderings of our site's text. 191 + - `<meta name="viewport">` to correctly scale the site for mobile users. 192 + - `<link rel="stylesheet">` to allow web browsers to fetch our stylesheet. 193 + - `<div class="root"></div>` where the main page's content will be rendered. 194 + 195 + You can now refresh http://localhost:3000 in your browser to see the updated page. 196 + 197 + ![Looks better already][styled-blog-homepage] 198 + 199 + Now you know how to add custom styles, the limit is the sky. You can start to style your app this way. You can even use PostCSS or Stylus to build stylesheets faster. 200 + 201 + ## 4. Blog Posts 202 + 203 + ### Writing the first blog post 204 + 205 + Off to writing the real posts now. Create a blog post at `blog/first-post.md`: 206 + 207 + ```markdown 208 + --- 209 + title: My First Blog Post 210 + description: "The first blog post to be created on this site." 211 + created: 1639915508100 212 + --- 213 + 214 + Hello people, a warm welcome to you. This is the first blog post on this site. 215 + 216 + ``` 217 + 218 + > **Note:** Notice the block at the beginning of the file that starts and ends with `---`. This is called Front Matter and is used to describe the blog posts like who wrote it, the title, when was it written etc. Also, be sure to update the `created` field to show the real time you created it. [View time in terms of milliseconds after the UNIX epoch][vixalien-date]. 219 + 220 + ### Showing the blog posts on the homepage 221 + 222 + Now comes the part that requires us to be a little bit ingenious. We are going to show all the blog posts on the homepage and provide links to them. 223 + 224 + First of all, we'll be installing 2 other dependencies to allow us deal with Markdown files. 225 + 226 + ```bash 227 + npm install front-matter marked 228 + ``` 229 + 230 + - `front- matter`: Allows use to parse page's front matter. 231 + - `marked`: Allows use to parse Markdown files into HTML. 232 + 233 + We are going to write a script at `src/posts.js` that loads all blog posts then give us info about them. 234 + 235 + ```js 236 + // src/posts.js 237 + 238 + // Import dependencies 239 + let path = require("path"); 240 + let fs = require("fs"); 241 + let fm = require("front-matter"); 242 + 243 + // This function resolves where files or folders are relative to the `cwd` or current working directory. 244 + let resolve = (...link) => path.resolve(process.cwd(), ...link); 245 + 246 + // Where all our blog posts are stored 247 + let blogsPath = resolve("blog"); 248 + 249 + let blogs = fs 250 + // Get all blog posts in the `blogsPath` folder. 251 + .readdirSync(blogsPath) 252 + .map((blog) => { 253 + // Get the slug. i.e `first-post` from `first-post.md` 254 + let slug = blog.replace(/\.md$/, ""); 255 + // And return an array of posts and their front matter 256 + // Example: [ "first-post", { title: "My First Blog Post", created: 1639915508100, description: "..." } ] 257 + return [ 258 + slug, 259 + { slug, ...fm(fs.readFileSync(resolve(blogsPath, blog), "utf8")).attributes }, 260 + ] 261 + }) 262 + // Sort the blog posts by date created 263 + .sort(([_, a], [$, b]) => b.created - a.created); 264 + 265 + // Export the posts as an object 266 + module.exports = Object.fromEntries(blogs); 267 + 268 + ``` 269 + 270 + We are then going to display all blog posts on the homepage. To do this, we'll create a component at `components/posts.js` that uses the post data to display a list of info about posts. 271 + 272 + ```jsx 273 + // components/posts.js 274 + // Load the posts as an object. 275 + import postsJSON from "../src/posts"; 276 + 277 + let PostsCard = ({ ...props }) => { 278 + // Convert the posts object into an array. 279 + let posts = Object.entries(postsJSON) 280 + 281 + return ( 282 + <p> 283 + <h2>Posts</h2> 284 + <div className="posts"> 285 + {/* Display the posts one by one */} 286 + {/* Display each post's title, date of creation and description with a link to read the post */} 287 + {posts.map(([slug, { title, description, created }]) => ( 288 + <p> 289 + <a href={"/post/" + slug}>{title} &rarr;</a><br/> 290 + <small>{new Date(created).toDateString()}</small><br/> 291 + <span>{description}</span> 292 + </p> 293 + ))} 294 + </div> 295 + </p> 296 + ); 297 + }; 298 + 299 + export default PostsCard; 300 + 301 + ``` 302 + 303 + We'll then modify `pages/index.js` to show blog posts using the newly created component on the homepage. 304 + 305 + ```jsx 306 + // index.js 307 + import PostsCard from "../components/posts.js"; 308 + 309 + let Homepage = () => { 310 + return <> 311 + <Head> 312 + <title>my blog</title> 313 + <meta name="description" content="This is my own blog"/> 314 + </Head> 315 + <main> 316 + <h1>Welcome to my blog</h1> 317 + <p>This is my cool new blog built with Explosiv. <a href="/about">About Me</a></p> 318 + <PostsCard/> 319 + </main> 320 + </> 321 + }; 322 + 323 + export default Homepage; 324 + ``` 325 + 326 + At this point you can visit http://localhost:3000 to view the site in a web browser. Notice the list of posts 327 + 328 + ![Blog Homepage with Posts][blog-homepage-with-posts] 329 + 330 + ### Showing the blog posts on their URLs 331 + 332 + Yay!! Our blog can now show posts. But if you click on the link to read the blog post, you'll reach a 404 page. We are going to create a page that will render each blog post to allow readers to read it. 333 + 334 + #### Meet dynamic pages 335 + 336 + We would need to write each blog's page like `/pages/post/first-blog.js` and `/pages/post/second-blog.js` etc. However, there is a feature called **Dynamic Pages** that simplify the development of related pages. We will be creating one single dynamic page at `/pages/post/[slug].js` that will render each post according to the `[slug]` provided. For example, visiting `/post/first-blog` will render `/pages/post/[slug].js` with a `slug` that is equal to `first-blog`. 337 + 338 + > **Note:** You can change `[slug]` to any other name you like. For example `[id]` or `[post]`. However, it is important to enclose the slug in brackets (`[]`). 339 + 340 + ```jsx 341 + // pages/post/[slug].js 342 + 343 + // Import dependencies, will be used later 344 + import { promises as fs } from 'fs' 345 + import path from 'path' 346 + import matter from 'front-matter' 347 + import { marked } from 'marked' 348 + 349 + // The Post component will be used to render each post 350 + const Post = ({ post }) => ( 351 + <> 352 + {/* Add a HEAD that shows the title of the page and expose the description of the post */} 353 + <Head> 354 + <title>{post.attributes.title} - vixalien</title> 355 + <meta name="description" content={post.attributes.description} /> 356 + </Head> 357 + <main> 358 + {/* Show a link to the homepage */} 359 + <div style={{marginTop:"20px"}}><a href="/">Homepage</a><br/><br/></div> 360 + <small>{new Date(post.attributes.created).toDateString()}</small> 361 + <h1>{post.attributes.title}</h1> 362 + <p>{post.attributes.description}</p> 363 + <div>===</div> 364 + <br/> 365 + {/* Render the post's content as HTML in an `article` tag */} 366 + <article html={post.content}/> 367 + </main> 368 + </> 369 + ) 370 + 371 + export default Post; 372 + ``` 373 + 374 + #### `getPaths` and `getProps` 375 + 376 + However, the above content is not enough for a dynamic page to work. For it to work correctly, we need to export 2 other functions beside the default export which is the main page JSX. 377 + 378 + The first needed export is `getPaths` and it is used to determine the number of all acceptable paths (or slugs). For example, it can be used to allow `/post/first-blog` to be rendered and `/post/unknown-post` to return a 404 page (Not Found). In our case, it's pretty straightforward to know the range of acceptable slugs. We just read the `blog` folder and see which blog posts are there: 379 + 380 + 381 + ```jsx 382 + // Append to the end of `pages/post/[slug].js` 383 + export const getPaths = async () => { 384 + // Read all files in the `blog` folder. 385 + const files = await fs.readdir(path.resolve('blog')) 386 + // Remove the training extensions like `.md` (remove the 3 last characters of filename) 387 + return files.map((filename) => filename.slice(0, filename.length - 3)) 388 + } 389 + ``` 390 + 391 + Now that we know what posts are there, we'll use `getProps` to read info about the post themselves given the slug. The `getProps` function is provided with a `slug` and use it to get information that will be passed to default export of the function (as props.) 392 + 393 + 394 + ```jsx 395 + // Append to the end of `pages/post/[slug].js` 396 + export const getProps = async (slug) => { 397 + // Read the file named `slug`+.md in the `blog` directory with the utf-8 format. 398 + let post = await fs.readFile(path.join('blog', `${slug}.md`), 'utf-8') 399 + // uses the `front-matter` package to get the post's attributes. 400 + post = matter(post) 401 + 402 + // parse the post's body to get the raw HTML content. 403 + post.content = marked(post.body) 404 + // Return an object that will be passed onto the default page export. 405 + return { post } 406 + } 407 + 408 + ``` 409 + 410 + Now visit http://localhost:3000/post/first-blog to read `first-blog`. 411 + 412 + ![First Blog][blog-post] 413 + 414 + ## Final Steps 415 + 416 + Now that you are done, here are a list of things you should do next. 417 + 418 + - Visit [Explosiv][explosiv] on Github for docs, stars etc. 419 + - Host your site on [Vercel][vercel] 420 + - Provide feedback in Github Issues 421 + - View the [source of this site][dotio2], which is written with Explosiv as well. 422 + 423 + [explosiv]: https://github.com/vixalien/explosiv 424 + [jsx]: https://reactjs.org/docs/introducting-jsx.html 425 + [nextjs]: https://nextjs.org 426 + [nextjs-showcase]: https://nextjs.org/showcase 427 + [gatsby]: https://www.gatsbyjs.com/ 428 + [gatsby-hn]: https://news.ycombinator.com/item?id=24670252 429 + [markdown]: https://www.markdownguide.org/getting-started/ 430 + [post-source]: https://github.com/vixalien/dotio2/blob/main/blog/explosiv-blog.md 431 + [vixalien-date]: https://vixalien.com/date 432 + [vercel]: https://vercel.com 433 + [dotio2]: https://github.com/vixalien/dotio2 434 + [explosiv-blog-source]: https://github.com/vixalien/explosiv-blog 435 + 436 + [nextjs-waterfall]: /images/posts/explosiv-blog/nextjs-waterfall.webp 437 + [blog-homepage]: /images/posts/explosiv-blog/blog-homepage.webp 438 + [styled-blog-homepage]: /images/posts/explosiv-blog/styled-blog-homepage.webp 439 + [blog-homepage-with-posts]: /images/posts/explosiv-blog/blog-homepage-with-posts.webp 440 + [blog-post]: /images/posts/explosiv-blog/blog-post.webp
+161
src/content/blog/explosiv.md
··· 1 + --- 2 + title: How Explosiv Works 3 + description: "The most lightweight, yet fully featured static-site generator you'll see." 4 + publish_date: 2021-01-28 5 + tags: [code, projects, explained] 6 + --- 7 + 8 + Explosiv [npm ↗][npm] [Github ↗][github] is a static site generator for JSX content. 9 + 10 + > This article is about how Explosiv works, if you want to want to learn how to use Explosiv go to [Explosiv's Github Page][github] instead. 11 + 12 + ## Why Explosiv was made. 13 + 14 + While I was creating this blog, I thought. 15 + 16 + About all the front-end options I had. Because, I was not going to write static HTML for a fully featured site! While I had already met [stylus] for all my CSS needs, I was still looking for an option to write my markup seamlessly. 17 + 18 + ## React 19 + 20 + TBH, I love [React]. It's syntax, it's community, it's everything really. Yet, React also put so much overhead on your site, like Build times, Babel, Webpack, hydrating or rendering etc. After some digging, I found out the foundation behind React was [JSX][jsx-intro], dubbed as XHTML within Javascript. 21 + 22 + ```js 23 + // JSX syntax is coool! 24 + let Site = (data) => { 25 + return <div>Hello {data.name}!</div> 26 + } 27 + ``` 28 + 29 + Well because JSX is tightly coupled with React, I kinda thought it would not work on it's own, but yet [kartiknair] made [Dhow], which proved me otherwise. 30 + 31 + ## Dhow 32 + 33 + [Dhow], is a static site generator, that uses JSX to render static HTML at build time, ready to be served as is. It is quick, _very fast_ and still uses JSX, so migrating my app from React was _a breeeeze_. Until I encountered the severe limitations of Dhow. 34 + 35 + Dhow was very young. That means It just implemented JSX, nothing else. Many features were lacking. While creating this site, I cloned Dhow. I found myself adding many features as I wanted. I saw it was incredible. I decided to push it to My Github as [Explosiv] 36 + 37 + ## Explosiv 38 + 39 + [Explosiv], being a clone of [Dhow], inherits all it's current features. Here is a simple example. 40 + 41 + First, add `explosiv` to your site's dependencies. 42 + 43 + ```bash 44 + npm i explosiv 45 + ``` 46 + 47 + And install explosiv globally, so that you can use the CLI wherever you are... 48 + 49 + ```bash 50 + npm i explosiv -g 51 + ``` 52 + 53 + Or, to keep up with modern standards, although I personally like the first syntax more, use `npx` to always use the latest version of explosiv 54 + 55 + ```bash 56 + npx explosiv 57 + ``` 58 + 59 + To make an Explosiv site, just create a folder and generate a `pages/` directory. Add a simple `index.js` file to get started. 60 + 61 + ```js 62 + // pages/index.js 63 + import Explosiv from 'explosiv' 64 + 65 + export default () => ( 66 + <main> 67 + <h1>Hello there!</h1> 68 + <p> 69 + This is a super simple example of generating static files using Explosiv. 70 + You can learn more at{' '} 71 + <a href="https://github.com/vixalien/explosiv">here</a> 72 + </p> 73 + </main> 74 + ) 75 + ``` 76 + 77 + To build, and serve, the site use: 78 + 79 + ```bash 80 + explosiv build 81 + explosiv serve 82 + ``` 83 + 84 + Et voìla! A static site was generated in your `/out` directory. Magic right! 85 + 86 + ### How it works 87 + 88 + You can learn how JSX works by [this article][jsx-intro] from the React team. 89 + 90 + You can read a very nice article by kartiknair, the creator of Dhow [about converting JSX into HTML][jsx-post] **without React**. 91 + 92 + TL;DR: We use a _pragma_ function that generate real DOM elements using a minimal DOM implementation, [min-document]. 93 + 94 + ```js 95 + // A general overview of how it works. 96 + // !! Not real code 97 + const document = require('min-document'); 98 + 99 + const createElement = (tag, props, ...children) => { 100 + const element = document.createElement(tag) 101 + 102 + children.forEach((child) => { 103 + element.appendChild(child) 104 + }) 105 + 106 + return element 107 + } 108 + ``` 109 + 110 + We transpile Javascript using [ESBuild], a verrry fast, yet fully featured transpiler. We transpile the code in the pages directory from JSX into pure, native Javascript, while replacing all instances of JSX with our _pragma_ function. 111 + 112 + The transpiled file will look like this 113 + 114 + ```js 115 + // transpiled/index.js 116 + let { createElement } = require('explosiv') 117 + 118 + export default () => ( 119 + createElement('main', null, 120 + createElement('h1', null, 'Hello there!'), 121 + createElement('p', null, 122 + 'This is a super simple example of generating static files using Explosiv.', 123 + 'You can learn more', ' ', 124 + createElement('a', { 125 + href: "https://github.com/vixalien/explosiv" 126 + }, 'here' 127 + ), 128 + ) 129 + ) 130 + ``` 131 + 132 + At the end we render our DOM into static HTML by using `document.toString()` and piping the output into the relevant output directory. 133 + 134 + ### Impovements over Dhow 135 + 136 + Explosiv, is a personal project. It is not a competitor, or even an alternative to Dhow, yet all current improvements are listed for anyone interested. Many of these can also be implemented in Dhow if worth it. 137 + 138 + - Provide an `explosiv serve` command that serve a static directory on a specified port (defaults to 3000). 139 + - `Head` elements are added on top of `document.head` instead of the bottom (allowing overriding existing tags) 140 + - Rewritten for `build` code to be independent and ease debugging 141 + - Does not use `polka` but the more minimal `connect`. 142 + - Use middleware deemed as useful like `morgan` which log all requests and `compression` which compress resources on HTTP. 143 + - Fixed bugs on edge cases like rendering `<>` (aka Fragment tags) as root elements and rendering empty children. 144 + - Added support for `className` HTML attribute. 145 + - Fixed bug where self-closing (like `<img src="/path/to.img">`) elements doesn't render correctly. 146 + - Use tabs instead of 4 spaces lol! 147 + - And other many but subtle changes. 148 + 149 + 150 + [npm]: https://npmjs.com/package/explosiv 151 + [github]: https://github.com/vixalien/explosiv 152 + 153 + [react]: https://reactjs.org 154 + [stylus]: https://google.com?q=stylus+css 155 + [kartiknair]: https://www.github.com/kartiknair 156 + [dhow]: https://www.github.com/kartiknair 157 + [explosiv]: https://www.github.com/vixalien/explosiv 158 + [jsx-post]: https://kartikn.me/writing/jsx-without-react 159 + [min-document]: https://npmjs.com/package/min-document 160 + [esbuild]: https://esbuild.github.io 161 + [jsx-intro]: https://reactjs.org/docs/introducting-jsx.html
-16
src/content/blog/first-post.md
··· 1 - --- 2 - title: 'First post' 3 - description: 'Lorem ipsum dolor sit amet' 4 - pubDate: 'Jul 08 2022' 5 - heroImage: '/blog-placeholder-3.jpg' 6 - --- 7 - 8 - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae ultricies leo integer malesuada nunc vel risus commodo viverra. Adipiscing enim eu turpis egestas pretium. Euismod elementum nisi quis eleifend quam adipiscing. In hac habitasse platea dictumst vestibulum. Sagittis purus sit amet volutpat. Netus et malesuada fames ac turpis egestas. Eget magna fermentum iaculis eu non diam phasellus vestibulum lorem. Varius sit amet mattis vulputate enim. Habitasse platea dictumst quisque sagittis. Integer quis auctor elit sed vulputate mi. Dictumst quisque sagittis purus sit amet. 9 - 10 - Morbi tristique senectus et netus. Id semper risus in hendrerit gravida rutrum quisque non tellus. Habitasse platea dictumst quisque sagittis purus sit amet. Tellus molestie nunc non blandit massa. Cursus vitae congue mauris rhoncus. Accumsan tortor posuere ac ut. Fringilla urna porttitor rhoncus dolor. Elit ullamcorper dignissim cras tincidunt lobortis. In cursus turpis massa tincidunt dui ut ornare lectus. Integer feugiat scelerisque varius morbi enim nunc. Bibendum neque egestas congue quisque egestas diam. Cras ornare arcu dui vivamus arcu felis bibendum. Dignissim suspendisse in est ante in nibh mauris. Sed tempus urna et pharetra pharetra massa massa ultricies mi. 11 - 12 - Mollis nunc sed id semper risus in. Convallis a cras semper auctor neque. Diam sit amet nisl suscipit. Lacus viverra vitae congue eu consequat ac felis donec. Egestas integer eget aliquet nibh praesent tristique magna sit amet. Eget magna fermentum iaculis eu non diam. In vitae turpis massa sed elementum. Tristique et egestas quis ipsum suspendisse ultrices. Eget lorem dolor sed viverra ipsum. Vel turpis nunc eget lorem dolor sed viverra. Posuere ac ut consequat semper viverra nam. Laoreet suspendisse interdum consectetur libero id faucibus. Diam phasellus vestibulum lorem sed risus ultricies tristique. Rhoncus dolor purus non enim praesent elementum facilisis. Ultrices tincidunt arcu non sodales neque. Tempus egestas sed sed risus pretium quam vulputate. Viverra suspendisse potenti nullam ac tortor vitae purus faucibus ornare. Fringilla urna porttitor rhoncus dolor purus non. Amet dictum sit amet justo donec enim. 13 - 14 - Mattis ullamcorper velit sed ullamcorper morbi tincidunt. Tortor posuere ac ut consequat semper viverra. Tellus mauris a diam maecenas sed enim ut sem viverra. Venenatis urna cursus eget nunc scelerisque viverra mauris in. Arcu ac tortor dignissim convallis aenean et tortor at. Curabitur gravida arcu ac tortor dignissim convallis aenean et tortor. Egestas tellus rutrum tellus pellentesque eu. Fusce ut placerat orci nulla pellentesque dignissim enim sit amet. Ut enim blandit volutpat maecenas volutpat blandit aliquam etiam. Id donec ultrices tincidunt arcu. Id cursus metus aliquam eleifend mi. 15 - 16 - Tempus quam pellentesque nec nam aliquam sem. Risus at ultrices mi tempus imperdiet. Id porta nibh venenatis cras sed felis eget velit. Ipsum a arcu cursus vitae. Facilisis magna etiam tempor orci eu lobortis elementum. Tincidunt dui ut ornare lectus sit. Quisque non tellus orci ac. Blandit libero volutpat sed cras. Nec tincidunt praesent semper feugiat nibh sed pulvinar proin gravida. Egestas integer eget aliquet nibh praesent tristique magna.
+350
src/content/blog/git.md
··· 1 + --- 2 + title: Learn Git! 3 + description: Learn to use Git, a popular Distributed Control System to effectively collaborate and manage your software projects. 4 + publish_date: 2021-08-01 5 + tags: [tutorial, code] 6 + --- 7 + 8 + So now you understand a certain programming language and coffee is your new best friend. But everywhere there is code, you see **Git**. They say a true developer must know Git and here you are, knowing nothing about Git, but longing to getting started. 9 + 10 + I have to admit, Git is as complex as it is popular. However, you don't need to know all the 160+ commands of Git to do daily operations. Here is a short but comprehensive tutorial that will teach you the most used features of this world-famous version control system. 11 + 12 + ## What is a Version Control System? 13 + 14 + A Version Control System (VCS) is a system that track and manages changes to files. A project with a VCS is usually called a __repository__ or __repo__ for short. 15 + 16 + A VCS allows you to have multiple versions of a project. To visualize the need for a VCS, imagine you are working on a new feature in a project, and a bug that needs to be fixed immediately is reported. With a VCS, you can easily create a new version of your repository, fix the bug, then return to the previous version when you are done fixing the bug. You can also see the different changes you've made in your repository over time and can easily rollback when a change is undesired or introduced a bug. If you also modify your files or lose them, you can restore your repository to the last saved snapshot. 17 + 18 + ## What is Git? 19 + 20 + **Git** is a version control system that manages a collection of files in a certain directory. Git is a _Distributed Version Control System._ This means Git does not rely on a central server to store all the versions of a project's files. Instead, every person "clones" a copy of a repository and get all the history and branches (more on that later) of the project. Although the original source code of a repository may be stored on a third-party hosting service like Github, any person can have their own copy of the project. 21 + 22 + Git was created by Linus Torvalds in 2005 for development of the Linux kernel, with other kernel developers contributing to its initial development. 23 + 24 + ## Installation 25 + 26 + > **Note: You'll need to be able to work with the command line fluently before learning Git.** Even though there are Git GUIs, Git itself is a command line application. If you don't understand the arts of the command line yet, you can check [Tania's command line tutorial](https://www.taniarascia.com/how-to-use-the-command-line-for-apple-macos-and-linux/). 27 + 28 + You can download Git for macOS, Windows, Linux or build it from source from the [Official Git website](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git). 29 + 30 + When you are finished installing, you can check if git is installed by running the following code in your Terminal (or Command Prompt for windows). 31 + 32 + ```bash 33 + git --version 34 + ``` 35 + 36 + If you see a result like the following, you are good to go. 37 + 38 + ``` 39 + git version 2.30.2.windows.1 40 + ``` 41 + 42 + ## Configuring Git 43 + 44 + After you install Git, you will have to set some configurations. These include your name and email and are used to mark changes that you introduce in a repository. That way, people can see changes you made and contact you easi;y. 45 + 46 + ```bash 47 + git config --global user.name "Firstname Lastname" 48 + git config --global user.email username@email.com 49 + ``` 50 + 51 + The above commands tell Git your names and email. Remember to change `Firstname Lastname` and `username@email.com` to your own names and email respectively. 52 + 53 + You can type the following command to check the saved config: 54 + 55 + ```bash 56 + git config --list 57 + ``` 58 + 59 + And you may get results similar to this: 60 + 61 + ``` 62 + color.ui=auto 63 + core.editor='C:\Program Files\Sublime Text 3\subl.exe' -w 64 + core.symlinks=true 65 + core.eol=lf 66 + user.email=username@email.com 67 + user.name=Firstname Lastname 68 + ``` 69 + 70 + ## Git workflow 71 + 72 + Any git repository consists of 3 "trees" maintained by Git: 73 + 74 + - **Working Directory:** This is your folder with the actual files, the one you can see in File Explorer. 75 + - **Index:** This is a staging area where Git put files that you are going to commit soon. (i.e. changes that are going to be marked as a new version.) This is because there are certain files that you may want to mark as finished and won't change, while there are others you are still working on and don't want to be released in this version. 76 + - **HEAD:** This is a reference that points to the last commit you've made. 77 + 78 + So you _add_ files from the Working directory to the Index. As you work further, you can add more files to the Index or even remove (_restore_) files from the Index. When you are ready, you **commit** your changes. This will next generate a commit and a new HEAD that points to your last commit. 79 + 80 + ## Working with repositories 81 + 82 + In this tutorial, we will need a new blank directory to learn Git and follow along. You can create a new folder anywhere to start experimenting with Git. I created mine at `D:\project`. 83 + 84 + ### Initializing a Git repository 85 + 86 + By Initializing a Git repository, you convert an unversioned project to Git or as in our case, create a new empty repository. (Yep, you can now call your project a repository!) You will need to run the rest of the commands in the root of the project. You can type `cd` to see where you are now and check if it is indeed where you are planning to create a new repository. 87 + 88 + ```bash 89 + git init 90 + ``` 91 + 92 + After initializing your git repository, you should see a message like: "Initialized empty Git repository in C:/project/.git/" to confirm that a new repo has been created successfully. Note that Git create a hidden folder called `.git` to store version and history data. 93 + 94 + ### Tracking files 95 + 96 + You will now need to create two files at the root of your project folder: `index.html` and `style.css`. You can use your favorite text editor (I ❤ [Sublime Text](https://www.sublimetext.com/)) to save them to the root of your repository. 97 + 98 + #### Checking the status of a repository 99 + 100 + You can check the status of your local repository by using the `git status` command. You will use this command a lot while working with Git repositories. 101 + 102 + ```bash 103 + git status 104 + ``` 105 + 106 + Output: 107 + 108 + ``` 109 + On branch main 110 + 111 + No commits yet 112 + 113 + Untracked files: 114 + (use "git add <file>..." to include in what will be committed) 115 + index.html 116 + style.css 117 + 118 + nothing added to commit but untracked files present (use "git add" to track) 119 + ``` 120 + 121 + > **Note:** On some older versions of Git, the `main` branch may be called `master` by default, this is normal. To change the name of the master branch to main, run: `git checkout master` then `git branch -M main` 122 + 123 + #### Staging files 124 + 125 + The output above tells us that Git knows there are new files in the Working Directory but they are not tracked (They are not part of our Git repo; Git is not tracking changes to them, yet). We have to **stage** the files using the git `add` command. 126 + 127 + ```bash 128 + git add . 129 + ``` 130 + 131 + Adding/Staging the files put them to Git's Index. 132 + 133 + > The `.` (or `*`) tells git to add EVERYTHING to the repo. 134 + > 135 + > You can also add a single file to the index at a time by using the `git add <filename>` syntax. `git add index.html` would add index.html only. 136 + > 137 + > You could also add a range of files using the `*` (wildcard character). `git add hello/*` would add all files in the hello folder. 138 + 139 + #### Committing changes 140 + 141 + Let's check the status again with `git status`. 142 + 143 + ``` 144 + On branch main 145 + 146 + No commits yet 147 + 148 + Changes to be committed: 149 + (use "git rm --cached <file>..." to unstage) 150 + new file: index.html 151 + new file: style.css 152 + 153 + ``` 154 + 155 + We are now ready to commit the files (i.e. mark the changes we made a version). 156 + 157 + 158 + ```bash 159 + git commit -m "Initial Commit" 160 + ``` 161 + 162 + Output: 163 + 164 + ``` 165 + [main (root-commit) 154dcd7] Initial Commit 166 + 2 files changed, 2 insertions(+) 167 + create mode 100644 index.html 168 + create mode 100644 style.css 169 + ``` 170 + 171 + While committing, the option `-m` can be used to provide a commit message, in this case "Initial Commit". You are encouraged to always provide a descriptive commit message that show a gist of the changes you've made. 172 + 173 + > If you don't provide a commit message, Git will use the default editor, set in the installation process on Windows or otherwise Vim, which could be weird for users who don't know to use Vim because it shows a strange screen where you can no longer enter any commands. To quit Vim, press <kbd>ESC</kbd> and type `:q!` followed by <kbd>ENTER</kbd>. You can [learn how to configure Git to use your favorite text editor](https://docs.github.com/en/get-started/getting-started-with-git/associating-text-editors-with-git). 174 + 175 + ### Branches 176 + 177 + Branches are used to develop features isolated from each other. The `main` branch (or `master`, depending on the version of Git) is the "default" branch when you create a repository. Use other branches for development and merge them back to the main branch upon completion. 178 + 179 + #### Creating a new branch 180 + 181 + In this project, we will be creating a new branch to add javascript. 182 + You use the `git chekout -b <branch-name>` syntax to add a new branch. 183 + 184 + ```bash 185 + git checkout -b javascript 186 + ``` 187 + 188 + ``` 189 + Switched to a new branch 'javascript' 190 + ``` 191 + 192 + #### Listing all branches 193 + 194 + You can use the `git branch` command to list all branches in the current repository. 195 + 196 + ```bash 197 + git branch 198 + ``` 199 + 200 + ``` 201 + * javascript 202 + main 203 + 204 + ``` 205 + 206 + The current branch is highlighted in green and an `asterisk` is shown before it's name. 207 + 208 + We can starting working on code in our new branch. Create a file called `script.js`. We can the use `git status` to view the state of our repo. 209 + 210 + ```bash 211 + git status 212 + ``` 213 + 214 + ``` 215 + On branch javascript 216 + Untracked files: 217 + (use "git add <file>..." to include in what will be committed) 218 + script.js 219 + 220 + nothing added to commit but untracked files present (use "git add" to track) 221 + 222 + ``` 223 + 224 + Notice that the output shows that we're on branch `javascript` and that `script.js` is untracked. We will need to add it to the 225 + Index. 226 + 227 + ```bash 228 + git add . 229 + ``` 230 + 231 + Then commit. 232 + 233 + ```bash 234 + git commit -m "Add script" 235 + ``` 236 + 237 + ``` 238 + [javascript 355fad9] Add script 239 + 1 file changed, 1 insertion(+) 240 + create mode 100644 script.js 241 + 242 + ``` 243 + 244 + ### Reviewing and Merging 245 + 246 + When you are done implementing a feature in a branch, the only thing left is to **merge** it to the main branch. Merging is Git's way of taking a forked history (an independent line of that divergd from the current branch) and incorporating it the current branch. Merging allows you to "combine" different versions of code that diverged from a shared branch. We are going to merge code from the `javascript` branch back into the `main` branch. 247 + 248 + We'll need to go back to the main branch first. 249 + 250 + ```bash 251 + git checkout main 252 + ``` 253 + 254 + #### git diff 255 + 256 + It is good practise to first review changes before merging or committing. You can use the `git diff` command (short for difference) to show changes between different revisions or paths. (You can use it to compare branches, commits and whatnots). 257 + 258 + If you provide only one argument, Git shows the changes in your working tree relative to the named reference. You can use HEAD to compare it to the latest commit, or a branch name to compare it to the tip of a different branch, which is what we'll do here. 259 + 260 + ```bash 261 + git diff javascript 262 + ``` 263 + 264 + ``` 265 + diff --git a/script.js b/script.js 266 + new file mode 100644 267 + index 0000000..d7ac302 268 + --- /dev/null 269 + +++ b/script.js 270 + @@ -0,0 +1 @@ 271 + 272 + ``` 273 + 274 + #### git merge 275 + 276 + After now reviewing the changes we are about to merge, it's time we merge the actual changes back into the main branch. 277 + 278 + ```bash 279 + git merge javascript 280 + ``` 281 + 282 + ``` 283 + Updating 154dcd7..355fad9 284 + Fast-forward 285 + script.js | 1 + 286 + 1 file changed, 1 insertion(+) 287 + create mode 100644 script.js 288 + 289 + ``` 290 + 291 + After the branch is merged successfully, we can now delete the `javascript` branch because it is no longer needed. 292 + 293 + ```bash 294 + git branch -d javascript 295 + ``` 296 + 297 + > Git tries to auto-merge changes. Unfortunately, this is not always possible and results in conflicts. You are responsible to merge those conflicts manually by editing the files shown by git. After changing, you need to mark them as merged with `git add <filename>` before merging changes. 298 + 299 + ### Logging 300 + 301 + You can type `git log` see the repository's history and confirm that the branch has been merged. 302 + 303 + ``` 304 + commit 355fad97c2d442bb4a307385bfd6bac2198e825d (HEAD -> main) 305 + Author: Firstname Lastname <username@email.com> 306 + Date: Sun Aug 1 15:35:31 2021 +0200 307 + 308 + Add script 309 + 310 + commit 154dcd7ce38589eb903346f6996e810401fa4910 311 + Author: Firstname Lastname <username@email.com> 312 + Date: Sun Aug 1 15:07:37 2021 +0200 313 + 314 + Initial Commit 315 + 316 + ``` 317 + 318 + Because git feeds git log results to a pager, you may need to press `q` when the log is too long. 319 + 320 + You can add a lot of parameters to make the log look like what you want. Here are a few examples: 321 + 322 + - To see only the commits of a certain author: 323 + `git log --author=alice` 324 + - To see a very compressed log where each commit is one line: 325 + `git log --pretty=oneline` 326 + - Or maybe you want to see an ASCII art tree of all the branches, decorated with the names of tags and branches: 327 + `git log --graph --oneline --decorate --all` 328 + - See only which files have changed: 329 + `git log --name-status` 330 + 331 + These are just a few of the possible parameters you can use. For more, see `git log --help` 332 + 333 + ### Tags 334 + 335 + When you are done and ready to mark a version of your code, you can add a git tag. 336 + 337 + ```bash 338 + git tag v1.0.0 339 + ``` 340 + 341 + ### The End 342 + 343 + You are now mostly ready to contribute to projects while using Git to track your projects. Although you must note that this is only a basic tutorial of Git and you still have more to learn namely working with remotes. Git is a very complex software and has more than 160 commands but it's amazing how you don't have to know even a half to contribute to open source projects and track your project's progress. You'll know more git commands naturally as you become more experienced with Git and when the need for them comes. 344 + 345 + ### Resources 346 + 347 + - [Official Git website](https://git-scm.com/) 348 + - [Git Downloads from the official site](https://git-scm.com/downloads) 349 + - [No nonsense git cheatsheet](https://rogerdudler.github.io/git-guide/img/trees.png) 350 + - [Extensive Git guide by Atlassian](https://www.atlassian.com/git)
+189
src/content/blog/let-it-snow.md
··· 1 + --- 2 + title: Let it snow! 3 + description: Building an optimized snowing weather with the Web Animations API and Promises. 4 + publish_date: 2021-01-31 5 + tags: [projects, code] 6 + --- 7 + 8 + > Easter egg: Run this page with `#snow` at the end 9 + 10 + 🌨⛄ Do you like snow? Does it snow in your region? Are we in December yet? 11 + 12 + We are going to create virtual snow using the chilly _[Web Animations API][animation-mdn]_. 13 + 14 + ## A snowflake! 15 + 16 + First and foremost, let's create a snowflake! Our snowflake will be loaded as an `.svg` file provided by the beautiful [Ionicons]. 17 + 18 + ### Loading the snowflake 19 + 20 + You can store it as a local file then load it as SVG, or use it from Ionicon's library, but we will be storing it as a string. 21 + 22 + ```js 23 + let svg_str = `<!-- snowflake svg text here -->`; 24 + ``` 25 + 26 + ### Parsing the string into a DOM element 27 + 28 + Then we'll use `DOMParser` to parse the string into an actual DOM element. 29 + 30 + ```js 31 + let snow = new DOMParser().parseFromString(svg_str, "text/xml").children[0]; 32 + ``` 33 + 34 + > **Note:** Because `parseFromString` returns a `#document`, we used `.children[0]` to get the `<svg>` element instead. (`<svg>` is equivalent to `<html>`.) 35 + 36 + ### Setting the snowflake to float 37 + 38 + Our snowflake is fixed (it doesn't scroll like other elements) and initially, it is placed just above the screen. 39 + 40 + ```js 41 + snow.style.position = "fixed"; 42 + snow.style.top = "-24px"; 43 + ``` 44 + 45 + ## Creating a new snowflake 46 + 47 + Because our page will have many snowflakes, we'll clone the snowflake we just created. 48 + 49 + ```js 50 + let newSnow = () => { 51 + let clonedSnow = snow.cloneNode(true); 52 + // we pass true to clone the node deeply (that is, with all it's children). 53 + }; 54 + ``` 55 + 56 + > **Note:** from now on, our code will be in the `newSnow` function. 57 + 58 + Next, we'll generate a random left position for that snowflake 59 + 60 + ```js 61 + let left = Math.floor(document.body.offsetWidth * Math.random()); 62 + // we use Math.floor to ensure left is an integer 63 + clonedSnow.style.left = left + "px"; 64 + ``` 65 + 66 + Then we'll just add it to the DOM 67 + 68 + ```js 69 + document.body.append(clonedSnow); 70 + ``` 71 + 72 + ### Animating the snowflake 73 + 74 + Here we'll just use _[Web Animations API][animation-mdn]_ to animate an element. To use the API, we run `element.animate(keyframes, options)`. You can read more in the [MDN Page][animation-mdn]. 75 + 76 + To make real snow effect, we will also generate a random speed (think the animation's duration) 77 + 78 + ```js 79 + let time = Math.max(10 * Math.random(), 5) * 1000; 80 + // Math.max choose the largest argument it was given. By using it here, we restrict time to be larger than 5. 81 + ``` 82 + 83 + We will animate the snow to change it's `top` CSS property gradually. At the end, the element will be placed just below the viewport, where you can't see it. 84 + 85 + ```js 86 + let anim = clonedSnow.animate( 87 + { 88 + top: window.innerHeight + 24 + "px", 89 + }, 90 + { duration: time, fill: "forwards" } 91 + ); 92 + ``` 93 + 94 + One last thing, we'll do Garbage Collection. When the animation ends, delete that snowflake as it is no longer useful. 95 + 96 + ```js 97 + // garbage collection 98 + anim.onfinish = el => el.target.effect.target.remove() 99 + ``` 100 + 101 + Now go ahead, in your console, run `newSnow()`. You'll see a snowflake falling slowly. 102 + 103 + ## Snowing!!! 104 + 105 + So far, we can only create snowflakes on demand by running `newSnow()` everytime we need it. What about we create a loop that create as many snowflakes as possible? 106 + 107 + ### The problem with native JS loops 108 + 109 + If you use `for` loops or `while` or whatever, it won't work. Why? It will create many snowflakes at a time. Your browser will be filled with snowflakes and unless you are on a supercomputer, your browser will crash, badly. This creates a need for a custom loop! 110 + 111 + ### Looping asynchronously 112 + 113 + #### Async Iterate 114 + 115 + Here's an implementation of an async loop. 116 + 117 + ```js 118 + let asyncIterate = async (start, iterations, fn) => { 119 + // initialize the iterator 120 + let i = start; 121 + let call = res => fn(res) 122 + // waits for the function to resolves before calling the next iteration 123 + .then(async result => { 124 + if (i >= iterations) return result; 125 + i++ 126 + return await call(i) 127 + }); 128 + return await call(i); 129 + } 130 + ``` 131 + 132 + It accepts 3 parameters. `start` is what the iterator is initialized as. `iterations` is pretty self-explanatory. it is the number of times the function will run. then `fn` is the function to execute. 133 + 134 + It is important to remember that this is an async loop. That means, it will run the function, _then waits that it resolves_. then execute the next iteration. 135 + 136 + #### wait 137 + 138 + Next is the `wait` function. This is a wrapper around `setTimeout`. It waits some time (in milliseconds), then execute a function. (It is available on the npm registry as [async-wait-then]). 139 + 140 + ```js 141 + wait = time => new Promise(res => setTimeout(res, time)) 142 + ``` 143 + 144 + Here is a simple example using `wait`. 145 + 146 + ```js 147 + wait(1000) 148 + .then(() => console.log('This will be logged after one second!')); 149 + ``` 150 + 151 + #### Using `wait` and `asyncIterate` to snow 152 + 153 + By combining `wait` and `asyncIterate`, we get a powerful function set that uses the Promises API. 154 + 155 + So, to create realistic snow (and prevent browser crashes) we'll have to wait before we create a snow element 156 + 157 + ```js 158 + asyncIterate(0, 10, async () => { 159 + await wait(1000) 160 + newSnow() 161 + }) 162 + ``` 163 + 164 + This will make it rain 10 snowflakes, but with an interval of 1 seconds between each snowflake 165 + 166 + To make it look more realistic (and add some suspense), we will wait for a random amount of time instead of the static 1 second. 167 + 168 + ```js 169 + asyncIterate(0, 10, async () => { 170 + await wait(Math.max(3 * Math.random(), 1) * 300) 171 + newSnow() 172 + }) 173 + ``` 174 + 175 + But then, this will only create 10 snowflakes. Let's make it rain forever. 176 + 177 + ```js 178 + asyncIterate(0, Infinity, async () => { 179 + await wait(Math.max(3 * Math.random(), 1) * 300) 180 + newSnow() 181 + }) 182 + ``` 183 + 184 + The full code, complete with some optimizations is posted as [Github Gist][gist] 185 + 186 + [gist]: https://gist.github.com/vixalien/4a9fb790036d01399186e7c3050c2560 187 + [animation-mdn]: https://developer.mozilla.org/en-US/docs.Web/API/Web_Animations_API 188 + [ionicons]: https://ionicons.com 189 + [async-wait-then]: https://npmjs.com/package/async-wait-then
-214
src/content/blog/markdown-style-guide.md
··· 1 - --- 2 - title: 'Markdown Style Guide' 3 - description: 'Here is a sample of some basic Markdown syntax that can be used when writing Markdown content in Astro.' 4 - pubDate: 'Jun 19 2024' 5 - heroImage: '/blog-placeholder-1.jpg' 6 - --- 7 - 8 - Here is a sample of some basic Markdown syntax that can be used when writing Markdown content in Astro. 9 - 10 - ## Headings 11 - 12 - The following HTML `<h1>`—`<h6>` elements represent six levels of section headings. `<h1>` is the highest section level while `<h6>` is the lowest. 13 - 14 - # H1 15 - 16 - ## H2 17 - 18 - ### H3 19 - 20 - #### H4 21 - 22 - ##### H5 23 - 24 - ###### H6 25 - 26 - ## Paragraph 27 - 28 - Xerum, quo qui aut unt expliquam qui dolut labo. Aque venitatiusda cum, voluptionse latur sitiae dolessi aut parist aut dollo enim qui voluptate ma dolestendit peritin re plis aut quas inctum laceat est volestemque commosa as cus endigna tectur, offic to cor sequas etum rerum idem sintibus eiur? Quianimin porecus evelectur, cum que nis nust voloribus ratem aut omnimi, sitatur? Quiatem. Nam, omnis sum am facea corem alique molestrunt et eos evelece arcillit ut aut eos eos nus, sin conecerem erum fuga. Ri oditatquam, ad quibus unda veliamenimin cusam et facea ipsamus es exerum sitate dolores editium rerore eost, temped molorro ratiae volorro te reribus dolorer sperchicium faceata tiustia prat. 29 - 30 - Itatur? Quiatae cullecum rem ent aut odis in re eossequodi nonsequ idebis ne sapicia is sinveli squiatum, core et que aut hariosam ex eat. 31 - 32 - ## Images 33 - 34 - ### Syntax 35 - 36 - ```markdown 37 - ![Alt text](./full/or/relative/path/of/image) 38 - ``` 39 - 40 - ### Output 41 - 42 - ![blog placeholder](/blog-placeholder-about.jpg) 43 - 44 - ## Blockquotes 45 - 46 - The blockquote element represents content that is quoted from another source, optionally with a citation which must be within a `footer` or `cite` element, and optionally with in-line changes such as annotations and abbreviations. 47 - 48 - ### Blockquote without attribution 49 - 50 - #### Syntax 51 - 52 - ```markdown 53 - > Tiam, ad mint andaepu dandae nostion secatur sequo quae. 54 - > **Note** that you can use _Markdown syntax_ within a blockquote. 55 - ``` 56 - 57 - #### Output 58 - 59 - > Tiam, ad mint andaepu dandae nostion secatur sequo quae. 60 - > **Note** that you can use _Markdown syntax_ within a blockquote. 61 - 62 - ### Blockquote with attribution 63 - 64 - #### Syntax 65 - 66 - ```markdown 67 - > Don't communicate by sharing memory, share memory by communicating.<br> 68 - > — <cite>Rob Pike[^1]</cite> 69 - ``` 70 - 71 - #### Output 72 - 73 - > Don't communicate by sharing memory, share memory by communicating.<br> 74 - > — <cite>Rob Pike[^1]</cite> 75 - 76 - [^1]: The above quote is excerpted from Rob Pike's [talk](https://www.youtube.com/watch?v=PAAkCSZUG1c) during Gopherfest, November 18, 2015. 77 - 78 - ## Tables 79 - 80 - ### Syntax 81 - 82 - ```markdown 83 - | Italics | Bold | Code | 84 - | --------- | -------- | ------ | 85 - | _italics_ | **bold** | `code` | 86 - ``` 87 - 88 - ### Output 89 - 90 - | Italics | Bold | Code | 91 - | --------- | -------- | ------ | 92 - | _italics_ | **bold** | `code` | 93 - 94 - ## Code Blocks 95 - 96 - ### Syntax 97 - 98 - we can use 3 backticks ``` in new line and write snippet and close with 3 backticks on new line and to highlight language specific syntax, write one word of language name after first 3 backticks, for eg. html, javascript, css, markdown, typescript, txt, bash 99 - 100 - ````markdown 101 - ```html 102 - <!doctype html> 103 - <html lang="en"> 104 - <head> 105 - <meta charset="utf-8" /> 106 - <title>Example HTML5 Document</title> 107 - </head> 108 - <body> 109 - <p>Test</p> 110 - </body> 111 - </html> 112 - ``` 113 - ```` 114 - 115 - ### Output 116 - 117 - ```html 118 - <!doctype html> 119 - <html lang="en"> 120 - <head> 121 - <meta charset="utf-8" /> 122 - <title>Example HTML5 Document</title> 123 - </head> 124 - <body> 125 - <p>Test</p> 126 - </body> 127 - </html> 128 - ``` 129 - 130 - ## List Types 131 - 132 - ### Ordered List 133 - 134 - #### Syntax 135 - 136 - ```markdown 137 - 1. First item 138 - 2. Second item 139 - 3. Third item 140 - ``` 141 - 142 - #### Output 143 - 144 - 1. First item 145 - 2. Second item 146 - 3. Third item 147 - 148 - ### Unordered List 149 - 150 - #### Syntax 151 - 152 - ```markdown 153 - - List item 154 - - Another item 155 - - And another item 156 - ``` 157 - 158 - #### Output 159 - 160 - - List item 161 - - Another item 162 - - And another item 163 - 164 - ### Nested list 165 - 166 - #### Syntax 167 - 168 - ```markdown 169 - - Fruit 170 - - Apple 171 - - Orange 172 - - Banana 173 - - Dairy 174 - - Milk 175 - - Cheese 176 - ``` 177 - 178 - #### Output 179 - 180 - - Fruit 181 - - Apple 182 - - Orange 183 - - Banana 184 - - Dairy 185 - - Milk 186 - - Cheese 187 - 188 - ## Other Elements — abbr, sub, sup, kbd, mark 189 - 190 - ### Syntax 191 - 192 - ```markdown 193 - <abbr title="Graphics Interchange Format">GIF</abbr> is a bitmap image format. 194 - 195 - H<sub>2</sub>O 196 - 197 - X<sup>n</sup> + Y<sup>n</sup> = Z<sup>n</sup> 198 - 199 - Press <kbd>CTRL</kbd> + <kbd>ALT</kbd> + <kbd>Delete</kbd> to end the session. 200 - 201 - Most <mark>salamanders</mark> are nocturnal, and hunt for insects, worms, and other small creatures. 202 - ``` 203 - 204 - ### Output 205 - 206 - <abbr title="Graphics Interchange Format">GIF</abbr> is a bitmap image format. 207 - 208 - H<sub>2</sub>O 209 - 210 - X<sup>n</sup> + Y<sup>n</sup> = Z<sup>n</sup> 211 - 212 - Press <kbd>CTRL</kbd> + <kbd>ALT</kbd> + <kbd>Delete</kbd> to end the session. 213 - 214 - Most <mark>salamanders</mark> are nocturnal, and hunt for insects, worms, and other small creatures.
+199
src/content/blog/rabbet.md
··· 1 + --- 2 + title: How Rabbet Works 3 + description: "A lightweight site that allow you to build pages from links." 4 + publish_date: 2021-12-17 5 + tags: [code, projects, explained] 6 + --- 7 + 8 + **Rabbet** is a very small and minimal app that allows users to create pages on the Internet with a set of URLs. 9 + 10 + I will try my best to share how Rabbet works in this short write-up. You can try to browse the source to follow along the explanations. 11 + 12 + Useful links: 13 + 14 + - [Rabbet dashboard ↗][rabbet] 15 + - [Github ↗][github] 16 + - [Test Page ↗][test] 17 + 18 + # Rabbet is a monorepo 19 + 20 + Rabbet is built as a monorepo. That is, it is one repository (folder) but it contain other packages inside of it. The monorepo doesn't use any repo manager like Lerna or Yarn workspaces as the packages are not tightly coupled. The different packages are for different uses: 21 + 22 + - **render:** a simple library that consume pages as JSON and return rendered HTML ready to be used in `pages` or `dash` to preview pages. 23 + - **db:** a library that allows other packages to access the database where the users and pages are stored. 24 + - **pages:** the express app that serves the rabbet pages at [username].rabbet.me/[page-slug]. 25 + - **dash:** a NextJS app that renders the [Rabbet dashboard ↗][rabbet] that serves as a front end that allows users to sign in and create pages. 26 + 27 + ## Render 28 + 29 + `render` was one of the hardest packages to create. The hardest part is that it has different templates so that a user can choose a template they choose. 30 + 31 + ### Templates 32 + 33 + TODO: Allow user to select template in Dashboard UI 34 + 35 + Templates reside in the `/templates` directory. The default template is called **Lnks.** Templates favorably use **React** and **Stylus** to render HTML and CSS respectively. Because the `render` package is meant to be run in the browser, Templates can't access the filesystem. So the template is split into 2 files: 36 + 37 + #### `metadata.js` 38 + 39 + This file is run at build time and is used to get all files necessary using `require` statements. For example, **Lnks** use `metadata.js` to load it's JSX file (the one that will be used to render the page into HTML.) And the CSS file that will be used to style the rendered page. 40 + 41 + ```js 42 + const jsFn = require("./page.jsx").default; 43 + const cssString = require('./style.css'); 44 + 45 + module.exports = async () => { 46 + return { cssString, jsFn }; 47 + } 48 + ``` 49 + 50 + #### `page.js` 51 + 52 + This file is used to provide info about the template itself. It exports a JSON object with the following properties and a render function. 53 + 54 + ```js 55 + { 56 + "label": "lnks", 57 + "settings": [ 58 + { 59 + "key": "show_rabbet", 60 + "label": "Show Powered by Rabbet", 61 + "description": "Show the text 'Powered by Rabbet' at the end of the page.", 62 + "type": "boolean", // can be boolean, number, string 63 + "default": true, 64 + } 65 + ], 66 + render, 67 + }; 68 + ``` 69 + 70 + The settings property affect Settings related to the template itself. 71 + 72 + The render function is a function that is fed the Page JSON and a meta attribute (which is the data returned from `metadata.json`.) The `render` function returns an object with properties that will be converted into HTML. 73 + 74 + ```js 75 + let render = (page, meta) => { 76 + return { 77 + title: "Sample page", 78 + about: "A sample page", 79 + scripts: [ 80 + { src: "https://uri", type: "module" }, 81 + { html: "console.log(\"Hello\")" }, 82 + "https://uri", 83 + "console.log(\"Hello\")" 84 + ], 85 + links: [ 86 + { href: "https://uri", rel: "stylesheet" }, 87 + "https://uri" 88 + ], 89 + styles: [ 90 + { html: 'body { color: "red" }' }, 91 + 'body { color: "red" } ' 92 + ], 93 + html: "HTML" 94 + } 95 + } 96 + ``` 97 + 98 + The `html` attribute is what will be in the body of the rendered page. The reason for `styles`, `links` and `scripts` is that the template may or may not need some scripts. For example, **Lnks** only use Lite Youtube scripts and external CSSs when the page's hero is a YouTube Embed. 99 + 100 + ## DB 101 + 102 + > **Note:** All DB functions are expected to be promises. 103 + 104 + This is the most trivial package of all. It provides an `/init` script that is called before any database operation (may be called multiple times.) It's main export is a file that provide a set of trivial operations such as: 105 + 106 + ### DB operations. 107 + 108 + - **get(COLLECTION, id):** Get an item with given `id` from `collection`. 109 + - **set(COLLECTION, id, data, merge = false):** Update `data` for an item with given `id` from `collection` and whether to `merge` the new data with already exisiting data. 110 + - **add(COLLECTION, data):** Add an item with given `data` to `collection`. The `id` is inferred automatically. 111 + - **query(COLLECTION, ...queries):** Perform a set of `queries` on given `collection`. 112 + - **deleteAll(COLLECTION, ...queries):** Delete all records that match a set of `queries` on given `collection`. 113 + 114 + A query is built with the exported `where` property. For example, to get all users who have the given username use: 115 + 116 + ```js 117 + import db from "@rabbet/db"; 118 + 119 + db.get("users", db.where("username", "==", "exampleusername")); 120 + ``` 121 + 122 + ### Account operations 123 + 124 + > In the future, all account operations will be moved to `/account` instead of the current default export. 125 + 126 + - **getCurrentUser():** Get the logged in user or null. 127 + - **getRealUser({ uid }):** Get the real user's info (from the database) based on the `uid` returned from login. 128 + - **onCurrentUserChange(callback):** Calls the given `callback` when the current user changes (logged out, logged in, loaded, logged out remotely) 129 + 130 + > **Note:** Firebase take a while to initialize so when yu call `getCurrentUser` for the first time it will always return null. Rabbet doesn't use `getCurrentUser` but instead listen to the `onCurrentUserChange` to get a real result when the user has changed. 131 + 132 + Example: 133 + 134 + ```js 135 + import db from "@rabbet/db"; 136 + 137 + let user = await dbgetCurrentUser(); 138 + ``` 139 + 140 + ### Optimized Account operations 141 + 142 + Optimized account operations are located at `/account`. 143 + 144 + - **login {}:** Currently an object with `{ withGoogle }` that launchs a login dialog. 145 + - **logout():** Logout from the current device. 146 + 147 + Example: 148 + 149 + ```js 150 + import account from "@rabbet/db/account"; 151 + 152 + loginWithGoogleButton.addEventListener("click", account.login.withGoogle); 153 + logoutButton.addEventListener("click", logout); 154 + ``` 155 + 156 + ## Pages 157 + 158 + `pages` is a small Express app that resides at `https://rabbet.me`. It renders a page from the given url. You can always visit a [test rabbet page ↗][test]. 159 + 160 + Assuming the root URL is `rabbet.me`, the pages app routes using the following rules: 161 + 162 + - **[username].rabbet.me/[slug]:** Return the page with given `slug` created by user with given `username`. 163 + - **rabbet.me/:** Redirects to Rabbet dashboard. 164 + - A 404 page with a link to the Rabbet dashboard when a resource isn't found. 165 + 166 + > **Note:** Pages doesn't currently use the `db` package but use a custom implementation that access the firebase API using URLs instead of a native driver (library) because it was thought to be faster. TODO: fix this. 167 + 168 + ## Dash 169 + 170 + This is the dashboard that appears to all visitors of the service. It allows users to log in and create accounts, create, modify and delete pages. It is a NextJS app. 171 + 172 + The package has the following directory structure: 173 + 174 + <pre style="white-space: break-spaces; font-family: monospace;"> 175 + 176 + **dash** 177 + ├─ **components**: Components used throughout the app. 178 + ├─ **lib**: Useful libraries and tools. 179 + │ ├─ **constants**: Holds app constants. 180 + ├─ **pages**: Contains JSX files for the project. 181 + │ ├─ **account**: Pages related to user accounts' 182 + │ ├─ **pages**: Pages related to linkpages created by users 183 + │ ├─ **home**: Homepage: the site which the users see when not logged in. 184 + ├─ **public**: Static files 185 + ├─ **partials**: Reused high-level components 186 + ├─ **schemas**: Validation schemas for different objects 187 + ├─ **stores**: Zustand stores for different objects like user, pages etc. 188 + ├─ **stylus**: Stylus for the site that will be transcribed into CSS 189 + 190 + </pre> 191 + 192 + --- 193 + <br/> 194 + 195 + If you've read this much, you might as well create a PR for a template. Inspirations: [Orcd ↗](https://skepta.orcd.co/all-in) [Linkfire ↗](https://ada.lnk.to/Intro) [Dev.page ↗](https://dev.page/vixalien) 196 + 197 + [rabbet]: https://dash.rabbet.me/account 198 + [github]: https://github.com/vixalien/rabbet3 199 + [test]: https://vixalien.rabbet.me/test
+309
src/content/blog/remix-in-the-cosmos.md
··· 1 + --- 2 + title: Setting up React Cosmos in Remix 3 + description: Build a sandbox for developing and testing UI components in Remix. 4 + publish_date: 2022-08-22 5 + tags: [code, tutorial] 6 + --- 7 + 8 + Hello friends! 9 + 10 + As many of you might already know [Remix] is a framework to build websites. Much 11 + like Next.js, it's production-grade, but unlike Next, it is extremely robust, 12 + easy and considerate. If you didn't check it out already, now might be the time. 13 + 14 + ## Remix 💔 Storybook 15 + 16 + ![Storybook](/images/posts/remix-in-the-cosmos/storybook.webp) 17 + 18 + I was building an app using Remix, then as usual, I started writing some 19 + components like Buttons, Select, Forms etc. I wanted to prototype those 20 + components and view them in the browser as a way to speed up the development 21 + process using Storybook. But sadly, 22 + [Storybook does not work on Remix][no-heart]. It's fine as long as you don't use 23 + some of Remix's provided components. This is because of the way Remix works, and 24 + that if you use Remix's features such as `Link`, `NavLink` or `useTransition`, 25 + you need to wrap your app in a certain `Remix` component. That `Remix` component 26 + doesn't in fact exist, and is just a combination of a React Context, Router 27 + (provided by `react-router` and config.) I'm sure if we try hard, we may be able 28 + to make it work on Storybook, but right now, I don't know of anyone who has 29 + figured it out yet. 30 + 31 + ## Remix 💝 Cosmos 32 + 33 + ![Cosmos](/images/posts/remix-in-the-cosmos/cosmos.webp) 34 + 35 + [React Cosmos][cosmos] is yet another tool that aims to achieve exactly the same as 36 + Storybook. Given, it doesn't have the docs, community or polish of Storybook, 37 + but I had used it before and it worked exceptionally well. So I thought I might 38 + give it another try. I found [Rasmus] who already made Remix work with Cosmos. 39 + you can read his blog post as it explains what he did. I extended on his work to 40 + make it work better in my use case, and I hope it can be helpful for you too. 41 + 42 + ## My approach 43 + 44 + I based my work on Rasmus' and made some other modifications. Here is the full 45 + list of file changes. 46 + 47 + Structure of changes: 48 + 49 + ``` 50 + . 51 + ├── .gitignore 52 + ├── cosmos.config.json 53 + ├── package.json 54 + ├── prod.cosmos.config.json 55 + ├── app 56 + │ ├── components 57 + │ │ └── cosmos.decorator.tsx 58 + │ └── routes 59 + │ └── cosmos.tsx 60 + └── scripts 61 + └── generate-cosmos-userdeps 62 + ``` 63 + 64 + ### cosmos.config.json 65 + 66 + ```json 67 + { 68 + "staticPath": "public", 69 + "watchDirs": ["app"], 70 + "userDepsFilePath": "app/cosmos.userdeps.js", 71 + "experimentalRendererUrl": "http://localhost:3000/cosmos" 72 + } 73 + ``` 74 + 75 + This file is pretty much self explanatory. It is Cosmos's config. I don't think 76 + the `staticPath` is necessary, because Cosmos doesn't inherently need it, but i 77 + haven't tested it yet, so there it is. The `experimentalRendererUrl` must match 78 + the URL you use for Remix in development, just add the `/cosmos` path, for which 79 + we'll create a page route soon. The `userDepsFilePath` is a file that is 80 + generated by Cosmos, and it outlines all the files that it uses (decorators, 81 + fixtures, config). 82 + 83 + ### prod.cosmos.config.json 84 + 85 + ```json 86 + { 87 + "staticPath": "public", 88 + "watchDirs": ["app"], 89 + "userDepsFilePath": "app/cosmos.userdeps.js", 90 + "experimentalRendererUrl": "https://whereveryourappishosted:3000/cosmos" 91 + } 92 + ``` 93 + 94 + This is almost a perfect clone of the `cosmos.config.json`, but notice the 95 + changed `experimentalRendererUrl` that points to wherever you app is hosted for 96 + production, this is important. 97 + 98 + ### app/routes/cosmos.json 99 + 100 + ```tsx 101 + import { useCallback, useState } from "react"; 102 + import { useEffect } from "react"; 103 + import type { HeadersFunction } from "@remix-run/node"; 104 + import type { LinksFunction } from "@remix-run/node"; 105 + 106 + /// @ts-ignore - is generated everytime by cosmos 107 + import { decorators, fixtures, rendererConfig } from "~/cosmos.userdeps.js"; 108 + 109 + // only load cosmos in the browser 110 + const shouldLoadCosmos = typeof window !== "undefined"; 111 + 112 + // CORS: allow sites hosted on other URLs to access this one. 113 + export const headers: HeadersFunction = () => { 114 + return { "Access-Control-Allow-Origin": "*" }; 115 + }; 116 + 117 + // mount the DOM renderer, notice it hydrates into `body` 118 + function Cosmos() { 119 + const [cosmosLoaded, setCosmosLoaded] = useState(false); 120 + const loadRenderer = useCallback(async () => { 121 + /// @ts-ignore - works 122 + const { mountDomRenderer } = (await import("react-cosmos/dom")).default; 123 + mountDomRenderer({ 124 + decorators, 125 + fixtures, 126 + rendererConfig: { 127 + ...rendererConfig, 128 + containerQuerySelector: "body", 129 + }, 130 + } as any); 131 + }, []); 132 + 133 + useEffect(() => { 134 + if (shouldLoadCosmos && !cosmosLoaded) { 135 + loadRenderer(); 136 + setCosmosLoaded(true); 137 + } 138 + }, [loadRenderer, cosmosLoaded]); 139 + 140 + return <div className="cosmos-container" />; 141 + } 142 + 143 + export default Cosmos; 144 + ``` 145 + 146 + This is almost the same as Rasmus's version. 147 + 148 + ### app/components/cosmos.decorator.tsx 149 + 150 + ```tsx 151 + import { CustomApp } from "~/root"; 152 + import { MemoryRouter } from "react-router-dom"; 153 + import { useEffect, useState } from "react"; 154 + import { createTransitionManager } from "@remix-run/react/dist/transition"; 155 + import { LiveReload, Scripts, ScrollRestoration } from "@remix-run/react"; 156 + 157 + const clientRoutes = [ 158 + { 159 + id: "idk", 160 + path: "idk", 161 + hasLoader: true, 162 + element: "", 163 + module: "", 164 + action: () => null, 165 + }, 166 + ]; 167 + 168 + let context = { 169 + routeModules: { idk: { default: () => null } }, 170 + manifest: { 171 + routes: { 172 + idk: { 173 + hasLoader: true, 174 + hasAction: false, 175 + hasCatchBoundary: false, 176 + hasErrorBoundary: false, 177 + id: "idk", 178 + module: "idk", 179 + }, 180 + }, 181 + entry: { imports: [], module: "" }, 182 + url: "", 183 + version: "", 184 + }, 185 + matches: [], 186 + clientRoutes, 187 + routeData: {}, 188 + appState: {} as any, 189 + transitionManager: createTransitionManager({ 190 + routes: clientRoutes, 191 + location: { 192 + key: "default", 193 + hash: "#hello", 194 + pathname: "/", 195 + search: "?a=b", 196 + state: {}, 197 + }, 198 + loaderData: {}, 199 + onRedirect(to, state?) { 200 + console.log("redirected"); 201 + }, 202 + }), 203 + }; 204 + 205 + const Decorator = ({ children }: { children: any }) => { 206 + const [result, setResult] = useState<any | null>(null); 207 + 208 + useEffect(() => { 209 + /// @ts-expect-error i swear to God importing is allowed 210 + import( 211 + /// @ts-expect-error Node expects CommonJS, but we're giving him ESM 212 + "@remix-run/react/dist/esm/components" 213 + ).then( 214 + ( 215 + { RemixEntryContext }: 216 + typeof import("@remix-run/react/dist/components"), 217 + ) => { 218 + setResult( 219 + <RemixEntryContext.Provider value={context}> 220 + <MemoryRouter> 221 + {children} 222 + <ScrollRestoration /> 223 + <Scripts /> 224 + <LiveReload /> 225 + </MemoryRouter> 226 + </RemixEntryContext.Provider>, 227 + ); 228 + }, 229 + ); 230 + }, []); 231 + 232 + if (!result) return <>Loading...</>; 233 + 234 + if (result) return result; 235 + }; 236 + export default Decorator; 237 + ``` 238 + 239 + This is the file that took me most of the time to implement. One drawback is 240 + that it only renders on the client, so Cosmos won't have SSR. It basically wraps 241 + around all other components in a directory deeper than it is `app/components/**` 242 + in this case. It wraps the provided children around inside a `MemoryRouter` 243 + which allows `react-router` specific things to work (such as `Link` or 244 + `NavLink`) It then wraps (decorates) that around in a `RemixEntryContext` 245 + Provider together with a very fake context (it was partially stolen from Remix 246 + test data). 247 + 248 + ### scripts/generate-cosmos-userdeps.js 249 + 250 + ```js 251 + const { generateUserDepsModule } = require( 252 + "react-cosmos/dist/userDeps/generateUserDepsModule.js", 253 + ); 254 + const { getCosmosConfigAtPath } = require( 255 + "react-cosmos/dist/config/getCosmosConfigAtPath", 256 + ); 257 + const { join, relative, dirname } = require("path"); 258 + const { writeFileSync } = require("fs"); 259 + 260 + const config = getCosmosConfigAtPath( 261 + join(process.cwd(), "prod.cosmos.config.json"), 262 + ); 263 + 264 + const userdeps = generateUserDepsModule({ 265 + cosmosConfig: config, 266 + rendererConfig: {}, 267 + relativeToDir: relative(process.cwd(), dirname(config.userDepsFilePath)), 268 + }); 269 + 270 + writeFileSync(config.userDepsFilePath, userdeps); 271 + ``` 272 + 273 + This file generates the `userDeps` that I talked about earlier. There is no 274 + direct script to generate it on the CLI, but this works. 275 + 276 + ### package.json 277 + 278 + Here are some convinience scripts to build the mix. 279 + 280 + ```json 281 + { 282 + "private": true, 283 + "sideEffects": false, 284 + "scripts": { 285 + "build:remix": "remix build", 286 + "build:userdeps": "node scripts/generate-cosmos-userdeps.js", 287 + "build": "yarn build:userdeps && yarn build:remix", 288 + "dev": "remix dev", 289 + "start": "remix-serve build", 290 + "cosmos": "cosmos", 291 + "build:cosmos": "cosmos-export --config prod.cosmos.config.json", 292 + "start:cosmos": "serve cosmos-export" 293 + } 294 + } 295 + ``` 296 + 297 + ### .gitignore 298 + 299 + ``` 300 + /app/cosmos.userdeps.js 301 + /cosmos-export 302 + ``` 303 + 304 + Thanks for being with me today. One love! 305 + 306 + [remix]: https://remix.run 307 + [rasmus]: https://dev.to/rzmz/react-cosmos-with-remix-7go 308 + [no-heart]: https://github.com/remix-run/remix/issues/214 309 + [cosmos]: https://reactcosmos.org/
+421
src/content/blog/river.md
··· 1 + --- 2 + title: How River Works 3 + description: A simple photostream webapp that (currently) gets images from VSCO. 4 + publish_date: 2024-01-18 5 + tags: [code, projects, explained] 6 + hero_image: /images/posts/river/banner.png 7 + --- 8 + 9 + The other day, I saw a [mastodon post][post] by [Jakub Steiner (jimmac)][jakub], 10 + a seasoned artist whom I well respect, about a site he's made to house his 11 + collection of arts. The website is hosted at https://art.jimmac.eu/ and I 12 + encourage you to visit the website to see the art for yourself. 13 + 14 + ![art.jimmac.eu by jimmac](/images/posts/river/art.jimmac.eu.png) 15 + 16 + While I'm fascinated by the art, I was even more fascinated by the website 17 + itself. It's a very minimalistic single-page app that shows a stream (grid) of 18 + images and allows you to click on each image to see it in full size. It's 19 + beautiful, and I've always been looking for a way to make something similar for 20 + myself, basically a way to show off a few of my photos. 21 + 22 + ## source code 23 + 24 + I learnt that Jakub has another similar site for his photos, 25 + https://photo.jimmac.eu/, and decided to check out 26 + [the source code][photos-source]. 27 + 28 + While checking out the implementation, I saw that the website(s) themselves are 29 + forked versions of [photo-stream], a "home for your photos" initially created by 30 + maxvoltar. 31 + 32 + ## photo-stream 33 + 34 + [photo-stream] is a git repository that contains a jekyll site that shows the 35 + photos in a predefined location _inside the git repo_. 36 + 37 + I was not the best fan of this approach, as I don't really like using git for 38 + non-code stuff (in this case images), as git repositories can get quite large if 39 + you put too many binaries in them. It was hence not a good fit for me, as where 40 + I live bandwidth is not cheap nor fast. 41 + 42 + Hence, I decided a more sane and ingenious solution would be to utilise images 43 + stored somewhere else, and just show them in a nice format like Jakub's site. 44 + 45 + ## vsco 46 + 47 + I already have a [VSCO] [account][vsco-profile], and I've been using it as a 48 + more sane alternative to my Instagram, which is private. Basically I put images 49 + on VSCO that I want to be public, like landscapes or just random photos I take. 50 + 51 + ![my vsco](/images/posts/river/my-vsco.png) 52 + 53 + So I thought it would be a nice idea to use VSCO as a source of images for my 54 + photostream, instead of relying on a git-based workflow. This means that I can 55 + simply upload an image to my VSCO using the mobile app, and it will be instantly 56 + visible on my photostream. 57 + 58 + It's important to note that in this case, VSCO is just an implementation detail, 59 + as I initially wanted to a less proprietary solution, like [pixelfed], but I 60 + didn't have the time to explore the ecosystem and set it up, so I just went with 61 + VSCO (at the moment). 62 + 63 + However, VSCO doesn't have an API, but since the website is a webapp and makes 64 + requests client-side, I can just inspect the network requests and see how the 65 + website gets the images. 66 + 67 + ## reverse engineering 68 + 69 + So now let's invoke the developer tools and look at how VSCO gets that crispy 70 + data about the images. 71 + 72 + The website is a SSR webapp and that means it initally loads the pages (for 73 + example, the [user profile][vsco-profile]) are initially generated by the server 74 + and returned as HTML. This means getting data from VSCO would require scraping 75 + the HTML and parsing it, which is not a very good idea. 76 + 77 + However, we are lucky. If you then navigate to another page (say you click on an 78 + image), the website doesn't reload the page, but it makes a _data_ client side 79 + request to an [internal API][vsco-api] which returns the data in JSON format. 80 + This means that we can get data for a user's profile by first making a request 81 + to a user's photo page (which is returned as HTML), and then clicking on the 82 + user's username to get the JSON data. 83 + 84 + Using this method, it was easy to know some of the endpoints that the VSCO API 85 + provides. 86 + 87 + ## vsco's API 88 + 89 + By clicking around VSCO's website, you will see it making a view requests to 90 + it's API, and that's how I discovered a few of the endpoints needed to get the 91 + data I wanted. 92 + 93 + ![a few VSCO API endpoints](/images/posts/river/vsco-requests.png) 94 + 95 + Here are the details I found about VSCO's API: 96 + 97 + ### base URL 98 + 99 + The base URL for the API is `https://vsco.co/api`. 100 + 101 + ### versions 102 + 103 + When you inspect the requests, you will see that the API is versioned. There are 104 + 2 versions in use **concurrently** at the time of writing this article: `v2` and 105 + `v3`. The 2 VSCO APIs seem to coexist and are both utilised by VSCO, buy they 106 + are incompatible. This means that some resources are only visible in `v2` and 107 + some are only visible in `v3`. 108 + 109 + It's very weird that VSCO uses 2 versions of the API at the same time, but it's 110 + what they do, and we have to deal with it. 111 + 112 + ### authentication 113 + 114 + If you try to make a request to the API normally, the API will deny you access 115 + and return a response with a `401` status code. This means that we need to 116 + authenticate (or identify) ourselves to the API. 117 + 118 + Luckily, VSCO uses a simple [token authentication][token-auth] scheme (also 119 + known as a bearer authentication). All the (web)apps need to do to gain access 120 + to the API is to include a special "token" that identifies them to the server. 121 + 122 + Normally, other APIs use something more sophisticated, like OAuth to make sure 123 + each user has their own "token" or tokens, but VSCO doesn't do that and keeps 124 + the granularity of the tokens to the app level. 125 + 126 + To provide the token, the apps provide a standard 127 + [`Authorization` header][auth-header] in the request body. The header the 128 + `auth-scheme` of `Bearer`. This means that the full header looks like the 129 + following: 130 + 131 + ``` 132 + Authorization: Bearer <token> 133 + ``` 134 + 135 + Note that the `<token>` is a placeholder for the actual token, and for the 136 + purposes of this article, I will not be showing the actual token used, although 137 + you can easily get it by inspecting the requests made by the 138 + [VSCO website][vsco] or the [VSCO Studio][vsco-studio]. 139 + 140 + Some good news is that `v2` and `v3` both use the same token, so we don't have 141 + manage multiple tokens. 142 + 143 + ### endpoints 144 + 145 + After identifying how to connect to the API, I started looking at the endpoints 146 + and the kind of data they return. 147 + 148 + #### `/2.0/sites?subdomain=<username>` 149 + 150 + This endpoint returns the data about a user's profile. The `subdomain` query is 151 + used to identify the user. For example, if you want to get the data for the user 152 + `vsco`, you would make a request to `/2.0/sites?subdomain=vsco`. 153 + 154 + The data returned by this endpoint is a bit too verbose, but there is really one 155 + field you need, which is the `id` field. This field is used to identify the user 156 + in other endpoints. 157 + 158 + ```json 159 + { 160 + "sites": [ 161 + { 162 + // other fields hidden for brevity 163 + "description": "", 164 + "domain": "vsco.co/vsco", 165 + "externalLink": "", 166 + "name": "VSCO", 167 + "subdomain": "vsco", 168 + "id": 113950 // <-- this is the site id 169 + } 170 + ] 171 + } 172 + ``` 173 + 174 + #### `/3.0/medias/profile?site_id=283527326&limit=14&cursor=` 175 + 176 + This endpoint returns the data about a user's photos. This endpoint has a few 177 + query parameters: 178 + 179 + - `site_id` is the **required** user id of the user you want to get the photos 180 + of. This is the `id` field from the previous endpoint. 181 + - `limit` is the **optional** number of photos to return. The default is `10`, 182 + and the maximum is `30`. 183 + - `cursor` is the **optional** cursor to use to get the next page of photos. 184 + This is used for pagination, and is not needed for the first page. 185 + 186 + The data returned by this endpoint is a bit too verbose, and I invite you to try 187 + it for yourself to get a complete response but here is a sample: 188 + 189 + ```json 190 + { 191 + "media": [ 192 + { 193 + "type": "image", 194 + "image": { 195 + // some fields are hidden for brevity 196 + "_id": "IDOFIMG", 197 + "grid_name": "vsco", 198 + "adaptive_base": "/i/IDOFIMG", 199 + "site_id": 283527326, 200 + "site_profile_image_url": "i.vsco.co/IDOFIMG", 201 + "description": "", 202 + "description_anchored": "", 203 + "capture_date": 1705541060652, 204 + "capture_date_ms": 1705541060652, 205 + "upload_date": 1705541060652, 206 + "last_updated": 1705541060652, 207 + "permalink": "http://vsco.co/vsco/media/id", 208 + "share_link": "http://vsco.co/vsco/media/id?share=tracking", 209 + "responsive_url": "im.vsco.co/aws-us-west-2/blurb/etc/id/not-real.jpg", 210 + "image_meta": { 211 + "aperture": 0, 212 + "copyright": "Copyright 2024. All rights reserved.", 213 + "flash_mode": "Off, Did not fire", 214 + "iso": 0, 215 + "make": "Sony", 216 + "model": "ZV E-1", 217 + "shutter_speed": "128/100000", 218 + "white_balance": "Auto", 219 + "orientation": 1, 220 + "capture_date": 1705541060652, 221 + "flash_value": 10, 222 + "file_size": 1010000, 223 + "file_hash": "hash" 224 + }, 225 + "height": 1000, 226 + "width": 100 227 + } 228 + }, 229 + { 230 + // ... another image 231 + }, 232 + { 233 + // ... yet another image 234 + } 235 + ], 236 + "next_cursor": "use-this-to-get-the-next-page-of-results" 237 + } 238 + ``` 239 + 240 + I know that's a lot of data, but we only need a few fields from each `media` 241 + object: 242 + 243 + - `image._id` is the id of the image. This is used to identify each image 244 + uniquely. 245 + - `image.responsive_url` is the URL of the image. This is the URL we will use to 246 + get and display the image. 247 + - `image.description` is the user-provided caption of the image. May be used to 248 + show more context about the image. 249 + - `image.capture_date` is the timestamp of when the image was captured. This is 250 + used to sort the images by date. However, this field is not always present, so 251 + we will use the `image.upload_date` field if that's the case. 252 + - `image.image_meta` is the metadata of the image. This is used to show more 253 + context about the image. 254 + 255 + That's actually all the endpoints we need to get the data we want. With this 256 + information in mind, we can now start building the webapp that shows the images. 257 + 258 + I then build a [minimal VSCO API client][vsco-api-client] that can be used to 259 + get the data from VSCO. 260 + 261 + ## webapp 262 + 263 + The original [photo-stream] is a jekyll app. And by looking at the source code, 264 + [jekyll] looks like a very nice tool, but there are caveats that rendered the 265 + original app an unsuitable choice for me: 266 + 267 + - The git based workflow, as explained before, is not for me. You need to 268 + basically fork the repo, add your images, and then push the repo to GitHub and 269 + then GitHub pages will build the site for you. This means that you will need 270 + to maintain a fork of the repo, and I'd rather not. 271 + - I'm not even sure if GitHub pages will be able to build the site, and hosting 272 + providers that provide virtual machines are not my thing. I'd rather host my 273 + website on a serverless platform using JAMStack preferrably. 274 + 275 + It was clear that I needed to build my own webapp, and in the ever-growing 276 + landscape of JavaScript frameworks, it was not easy to pick a choice. However, I 277 + ended up settling on [Fresh], a preact framework optimised for [Deno]. This 278 + means that I can host the app on [Deno Deploy][deno-deploy] and it will just 279 + work. 280 + 281 + The webapp itself just fetches data from the API, and presents the images in a 282 + nice UI ~~stolen from~~ inspired by [photo-stream]. It's basically a 283 + re-implementation of [photo-stream] in TypeScript. 284 + 285 + Of course, I have made some minor modifications to the UI, but mostly they are 286 + the same concept. 287 + 288 + You can find the website's [source code on GitHub][river]. 289 + 290 + ## flow 291 + 292 + Now that the webapp is built, let's look at how exactly it gets the images. 293 + 294 + Now, we don't have to add images to a git repo, so you might be wondering how 295 + exactly we get images for the photostream. 296 + 297 + ### 1. setup VSCO 298 + 299 + You will need to create a VSCO account if you don't have one already. An account 300 + is free, although you can pay for a subscription to get more features. 301 + 302 + ### 2. clone river 303 + 304 + You now need to make a clone of [river], then clone the forked repo to your 305 + local machine. 306 + 307 + ```sh 308 + git clone https://github.com/vixalien/river 309 + ``` 310 + 311 + ### 2. get your site id 312 + 313 + Remember that we use the site id (in contrast to username) to identify the user 314 + in the API. To get the site id, you need can run the following command in the 315 + root of the cloned app to know your site id. This is so we don't have to always 316 + fetch the site id from the API. 317 + 318 + ```sh 319 + deno task get-id <username> 320 + ``` 321 + 322 + Remember to replace `<username>` with your username. 323 + 324 + ### 3. configure river 325 + 326 + Now let's configure the app. You need to create a `.env` file in the root of the 327 + cloned app, and modify the configuration to your liking. 328 + 329 + There is a [`.env.defaults`][env-defaults] file that contains all the possible 330 + configuration values. You can refer to the file to add configuration files as 331 + needed. 332 + 333 + For example, here is the configuration of my site at 334 + https://memories.vixalien.com/: 335 + 336 + ```sh .env 337 + # .env 338 + TITLE=vixalienʼs memories 339 + AUTHOR_NAME=vixalien 340 + AUTHOR_WEBSITE=https://vixalien.com 341 + DESCRIPTION=A few snapshots from a life that I probably lived. 342 + URL=https://memories.vixalien.com 343 + SHOW_RSS_FEED=0 344 + 345 + ALLOW_ORDER_SORT_CHANGE=0 346 + 347 + INSTAGRAM_USERNAME=username 348 + CUSTOM_LINK_NAME=© 2024 vixalien 349 + CUSTOM_LINK_URL=https://vixalien.com 350 + 351 + # this is the only required configuration 352 + USER_ID=281297126 353 + ``` 354 + 355 + Note that you should probably change the settings to reflect your own site. And 356 + the `.env` file is ignored by git, so you don't have to worry about accidentally 357 + committing it. 358 + 359 + ### 5. run 360 + 361 + Now that you have configured the app, it's time to run it. Run the following 362 + command in the root of the cloned app: 363 + 364 + ```sh 365 + deno task start 366 + ``` 367 + 368 + You should now have a local server running at http://localhost:8000/ that shows 369 + your photostream, similar to the following 370 + 371 + ![a river photostream](/images/posts/river/river.png) 372 + 373 + ### 4. deploy 374 + 375 + Now that you have configured the app, you can deploy it to Deno Deploy or any 376 + other provider of your choice. I'll focus on Deno Deploy as it's the one I used, 377 + and is free for my use case. 378 + 379 + 1. Create an account at https://deno.com/deploy 380 + 2. Clone [river] to your GitHub account 381 + 3. Create a new project on Deno Deploy at https://dash.deno.com/new 382 + 4. Connect your forked repo to the project 383 + 5. Add configuration variables by going to Project > Settings > Environment 384 + Variables. 385 + 6. Visit your project's URL!! 386 + 387 + ![example river deno deploy configuration](/images/posts/river/deno-config.png) 388 + 389 + ## conclusion 390 + 391 + I hope you enjoyed this article. I know it's a bit long, but I wanted to explain 392 + this (useless) little project of mine deeply. The next step is to fix some items 393 + on [the todo list][todo] add more features to the app. 394 + 395 + On the wishlist is supporting other sources of images, like [pixelfed] and maybe 396 + even Instagram. I also want to add support for videos, but I don't have a use 397 + case for that yet. 398 + 399 + If you already forgot, the source code of river [is available on GitHub][river]. 400 + 401 + The source of the banner image is https://memories.vixalien.com 402 + 403 + [jakub]: https://jimmac.eu/ 404 + [post]: https://mastodon.social/@jimmac/111708348377993550 405 + [photos-source]: https://github.com/jimmac/photo.jimmac.eu 406 + [photo-stream]: https://github.com/waschinski/photo-stream 407 + [vsco]: https://vsco.co/ 408 + [vsco-api]: https://vsco.co/api/ 409 + [pixelfed]: https://pixelfed.org/ 410 + [vsco-profile]: https://vsco.co/angeloverlain 411 + [vsco-studio]: https://studio.vsco.co/ 412 + [token-auth]: https://swagger.io/docs/specification/authentication/bearer-authentication/ 413 + [auth-header]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization 414 + [jekyll]: https://jekyllrb.com/ 415 + [fresh]: https://fresh.deno.dev/ 416 + [deno]: https://deno.land/ 417 + [deno-deploy]: https://deno.com/deploy 418 + [river]: https://github.com/vixalien/river 419 + [vsco-api-client]: https://github.com/vixalien/river/tree/main/api 420 + [env-defaults]: https://github.com/vixalien/river/blob/main/.env.defaults 421 + [todo]: https://github.com/vixalien/river/blob/main/TODO.md
-16
src/content/blog/second-post.md
··· 1 - --- 2 - title: 'Second post' 3 - description: 'Lorem ipsum dolor sit amet' 4 - pubDate: 'Jul 15 2022' 5 - heroImage: '/blog-placeholder-4.jpg' 6 - --- 7 - 8 - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae ultricies leo integer malesuada nunc vel risus commodo viverra. Adipiscing enim eu turpis egestas pretium. Euismod elementum nisi quis eleifend quam adipiscing. In hac habitasse platea dictumst vestibulum. Sagittis purus sit amet volutpat. Netus et malesuada fames ac turpis egestas. Eget magna fermentum iaculis eu non diam phasellus vestibulum lorem. Varius sit amet mattis vulputate enim. Habitasse platea dictumst quisque sagittis. Integer quis auctor elit sed vulputate mi. Dictumst quisque sagittis purus sit amet. 9 - 10 - Morbi tristique senectus et netus. Id semper risus in hendrerit gravida rutrum quisque non tellus. Habitasse platea dictumst quisque sagittis purus sit amet. Tellus molestie nunc non blandit massa. Cursus vitae congue mauris rhoncus. Accumsan tortor posuere ac ut. Fringilla urna porttitor rhoncus dolor. Elit ullamcorper dignissim cras tincidunt lobortis. In cursus turpis massa tincidunt dui ut ornare lectus. Integer feugiat scelerisque varius morbi enim nunc. Bibendum neque egestas congue quisque egestas diam. Cras ornare arcu dui vivamus arcu felis bibendum. Dignissim suspendisse in est ante in nibh mauris. Sed tempus urna et pharetra pharetra massa massa ultricies mi. 11 - 12 - Mollis nunc sed id semper risus in. Convallis a cras semper auctor neque. Diam sit amet nisl suscipit. Lacus viverra vitae congue eu consequat ac felis donec. Egestas integer eget aliquet nibh praesent tristique magna sit amet. Eget magna fermentum iaculis eu non diam. In vitae turpis massa sed elementum. Tristique et egestas quis ipsum suspendisse ultrices. Eget lorem dolor sed viverra ipsum. Vel turpis nunc eget lorem dolor sed viverra. Posuere ac ut consequat semper viverra nam. Laoreet suspendisse interdum consectetur libero id faucibus. Diam phasellus vestibulum lorem sed risus ultricies tristique. Rhoncus dolor purus non enim praesent elementum facilisis. Ultrices tincidunt arcu non sodales neque. Tempus egestas sed sed risus pretium quam vulputate. Viverra suspendisse potenti nullam ac tortor vitae purus faucibus ornare. Fringilla urna porttitor rhoncus dolor purus non. Amet dictum sit amet justo donec enim. 13 - 14 - Mattis ullamcorper velit sed ullamcorper morbi tincidunt. Tortor posuere ac ut consequat semper viverra. Tellus mauris a diam maecenas sed enim ut sem viverra. Venenatis urna cursus eget nunc scelerisque viverra mauris in. Arcu ac tortor dignissim convallis aenean et tortor at. Curabitur gravida arcu ac tortor dignissim convallis aenean et tortor. Egestas tellus rutrum tellus pellentesque eu. Fusce ut placerat orci nulla pellentesque dignissim enim sit amet. Ut enim blandit volutpat maecenas volutpat blandit aliquam etiam. Id donec ultrices tincidunt arcu. Id cursus metus aliquam eleifend mi. 15 - 16 - Tempus quam pellentesque nec nam aliquam sem. Risus at ultrices mi tempus imperdiet. Id porta nibh venenatis cras sed felis eget velit. Ipsum a arcu cursus vitae. Facilisis magna etiam tempor orci eu lobortis elementum. Tincidunt dui ut ornare lectus sit. Quisque non tellus orci ac. Blandit libero volutpat sed cras. Nec tincidunt praesent semper feugiat nibh sed pulvinar proin gravida. Egestas integer eget aliquet nibh praesent tristique magna.
+260
src/content/blog/setup-git.md
··· 1 + --- 2 + title: Setup Git 3 + description: How I set up Git on my systems with using opinionated defaults. 4 + publish_date: 2022-04-01 5 + hero_image: /images/posts/setup-git/banner.webp 6 + tags: [code, tutorial] 7 + --- 8 + 9 + After you've [learnt the basics of Git](/blog/git), it's time to make Git your own by creating a workflow that suits you. 10 + 11 + ## How to setup Git 12 + 13 + This is my default setup 14 + 15 + ### 1. Install Git 16 + 17 + Git has a high chance of being already installed on some systems. First run `git version` to see if it is installed and up-to-date. 18 + 19 + ```bash 20 + brew install git # on macos 21 + 22 + yay -S git # arch (or use pacman) 23 + sudo apt install git # ubuntu 24 + sudo dnf install git # fedora 25 + 26 + # this guide is not really meant for windows but everything should work, go to https://git-scm.com/download/win to download git for windows 27 + ``` 28 + 29 + ### 2. Update Git settings 30 + 31 + Create a file called `.gitconfig` in your home directory. On Windows, it is `C:\Users\username\.gitconfig`. 32 + 33 + 34 + ```yaml 35 + # ~/.gitconfig 36 + # Tell Git who you are 37 + [user] 38 + email = email@domain.tld 39 + name = First Last Name 40 + [push] 41 + default = current 42 + [credential] 43 + helper = store 44 + [alias] 45 + silly = commit --amend -a --no-edit 46 + tree = log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset%n' --abbrev-commit --date=relative --branches 47 + # logs 48 + lg1 = log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold green)(%ar)%C(reset) %C(white)%s%C(reset) %C(dim white)- %an%C(reset)%C(bold yellow)%d%C(reset)' --all 49 + lg2 = log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold cyan)%aD%C(reset) %C(bold green)(%ar)%C(reset)%C(bold yellow)%d%C(reset)%n'' %C(white)%s%C(reset) %C(dim white)- %an%C(reset)' --all 50 + lg = !"git lg1" 51 + adog = log --all --decorate --oneline --graph 52 + [filter "lfs"] 53 + required = true 54 + clean = git-lfs clean -- %f 55 + smudge = git-lfs smudge -- %f 56 + process = git-lfs filter-process 57 + # Set the default branch to main 58 + [init] 59 + defaultBranch = main 60 + [commit] 61 + verbose = true 62 + [rebase] 63 + autoStash = true 64 + stat = true 65 + [tag] 66 + forceSignAnnotated = true 67 + [fetch] 68 + prune = true 69 + [color] 70 + ui = true 71 + [core] 72 + editor = flatpak run com.visualstudio.code -w 73 + 74 + ``` 75 + 76 + ## Github/GitLab/... 77 + 78 + Git is decentralised, however, you usually need to host your files on a server somewhere to make them available to other people. **Git Forges** make it easy, and there are many options available, such as GitHub, GitLab, Forgejo, Bitbucket, and more. Here's how you can set up your local Git to work with your favorite forge correctly. 79 + 80 + ### 1. Using SSH 81 + 82 + Your hosted git server will need to authenticate you. We use [SSH], or Secure SHell to generate a pair of keys that will help establish the link to the server. One is a public key which means we can share it with anyone, while the other is a private key which means it needs to stay on the coumputer 83 + 84 + #### 1. Generate SSH keys 85 + 86 + ```sh 87 + ssh-keygen -t ed25519 -f ~/.ssh/[filename] -C "[comment]" 88 + ``` 89 + 90 + Replace `[filename]` with the name of your forge, and add a comment to help you remember what this key is for. For example, here is how I'd generate a key for github: 91 + 92 + ```sh 93 + ssh-keygen -t ed25519 -f ~/.ssh/github -C "email@domain.tld" 94 + ``` 95 + 96 + #### 2. Copy the contents of the `[filename].pub` 97 + 98 + For a reminder, it's located at `~/.ssh/[filename].pub`. 99 + 100 + #### 3. Add your SSH to your forge 101 + 102 + For convenience, here's how to add an SSH key to Github. 103 + 104 + 1. Go to your Github's [Account Settings][github-settings] 105 + 2. On the sidebar, Click on "[SSH and GPG keys][github-sag]". 106 + 3. Scroll and click on "New SSH Key". 107 + 4. Add a descriptive label and paste the text you had copied from the previous command and save. 108 + 109 + #### 4. Create an SSH config 110 + 111 + Now, we need to create a config file that tells SSH which key to use for which domain at `~/.ssh/config` 112 + 113 + ``` 114 + Host github.com 115 + HostName github.com 116 + IdentityFile ~/.ssh/github 117 + ``` 118 + 119 + Feel free to add even more domains for each forge. 120 + 121 + #### 5. Test SSH 122 + 123 + To test SSH, run the following command: 124 + 125 + ```bash 126 + ssh -T git@github.com 127 + ``` 128 + 129 + If you see something like the following, it worked: 130 + 131 + ``` 132 + Hi username! You've successfully authenticated, but Github does 133 + not provide shell access. 134 + ``` 135 + 136 + Also, don't mind it if you see a message that says the authenticity of 'github.com' can't be established. It's normal, and once you trust the domain, SSH will remember that. 137 + 138 + Now clone a repo using SSH by running your clone commands in the form: 139 + 140 + ```bash 141 + git clone git@github.com:user/repo.git 142 + ``` 143 + 144 + ### 2. Adding a GPG Key to your account. 145 + 146 + You can show a `Verified` Badge or a blue tick next to your commits by setting up a [GPG] Key and using it to sign your commits. Commits made through the Github web interface are automatically signed. 147 + 148 + #### 1. Install gpg 149 + 150 + ```bash 151 + brew install gpg # on macos 152 + # it's usually preinstalled on linux 153 + # on windows go to https://www.gnupg.org/download/ 154 + ``` 155 + 156 + #### 2. Generate a new GPG Key 157 + 158 + To create a GPG key quickly: 159 + 160 + ```bash 161 + gpg --quick-generate-key [email@domain.tld] 162 + ``` 163 + 164 + <pre class="not-code"> 165 + We need to generate a lot of random bytes. It is a good idea to perform 166 + some other action (type on the keyboard, move the mouse, utilize the 167 + disks) during the prime generation; this gives the random number 168 + generator a better chance to gain enough entropy. 169 + We need to generate a lot of random bytes. It is a good idea to perform 170 + some other action (type on the keyboard, move the mouse, utilize the 171 + disks) during the prime generation; this gives the random number 172 + generator a better chance to gain enough entropy. 173 + gpg: key <i><b><code>YOUR KEY WILL BE HERE</code></b></i> marked as ultimately trusted 174 + dgpg: revocation certificate stored as '/home/<i>USERNAME</i><wbr>/.gnupg/openpgp-revocs.d/RANDOM40CHARACTERSIDKWHYUREADINGTHISLMAO.rev' 175 + public and secret key created and signed. 176 + 177 + pub rsa4096 2022-03-31 [SC] [expires: 2022-06-29] 178 + RANDOM40CHARACTERSIDKWHYUREADINGTHISLMAO 179 + uid <i>"Beautiful name (The comment you provided) &lt;good@email.dne&gt;</i> 180 + sub rsa4096 2022-03-31 [E] [expires: 2022-06-29] 181 + </pre> 182 + 183 + At this point you will be demanded to enter a password twice. Please remember it or save it somewhere (in a password manager, does paper still exist in the future?) 184 + 185 + #### 3. Export the key and add it to Github 186 + 187 + Run the following command using the `[KEY_ID]` from the previous command to export your newly-created GPG key. 188 + 189 + ```bash 190 + gpg --armor --export [KEY_ID] 191 + ``` 192 + 193 + This will generate a large block of text. In the following format. 194 + 195 + ``` 196 + -----BEGIN PGP PUBLIC KEY BLOCK----- 197 + [SCRAMBLE] 198 + -----END PGP PUBLIC KEY BLOCK----- 199 + ``` 200 + 201 + Copy the whole text including the comments. 202 + 203 + 204 + As an example, here's how to add the key to Github. 205 + 206 + 1. Open [Github] 207 + 2. Go your [settings][github-settings] 208 + 3. On the sidebar, Click on "[SSH and GPG keys][github-sag]". 209 + 4. Scroll and click on "New GPG Key" in the "GPG" keys section. (Below the SSH section). 210 + 5. Paste the text you had copied from the previous command and save. 211 + 212 + #### 4. Configure Git to always sign commits 213 + 214 + Now that you have GPG set up with Git, run the following commands to tell git to always sign your commits with your GPG key. 215 + 216 + ```bash 217 + git config --global user.signingkey [KEY_ID] 218 + git config --global commit.gpgsign true 219 + ``` 220 + 221 + If you do not enable `commit.gpgsign` you can always sign each commit individually by running `git commit -S`. 222 + 223 + Now try and commit to one of your projects. And it should show a verified commit. 224 + 225 + #### Troubleshooting 226 + 227 + If you run into issues while on the last part and the response says the commit can't be verified, try running the following command. 228 + 229 + ```bash 230 + echo "test" | gpg --clearsign 231 + ``` 232 + 233 + If it fails, set the GPG_TTY variable. 234 + 235 + ```bash 236 + export GPG_TTY=$(tty) 237 + ``` 238 + 239 + Then try re-running the command and it should be successful. 240 + 241 + It is also a good idea to kill the GPG client so that it asks for the password the first time. 242 + 243 + ```bash 244 + gpgconf --kill all 245 + gpg-agent --daemon 246 + ``` 247 + 248 + ### Sources 249 + 250 + - [Verifying GPG keys on Daily Dev Tips](https://daily-dev-tips.com/posts/how-to-verify-your-commits-on-github/). 251 + - [Your First time on Git by Karl Broman](https://kbroman.org/github_tutorial/pages/first_time.html) 252 + 253 + 254 + [github]: https://github.com 255 + [github-settings]: https://github.com/settings/profile 256 + [github-developer-settings]: https://github.com/settings/apps 257 + [github-pat]: https://github.com/settings/tokens 258 + [github-sag]: https://github.com/settings/keys 259 + [ssh]: https://en.wikipedia.org/wiki/Secure_Shell 260 + [gpg]: https://en.wikipedia.org/wiki/GNU_Privacy_Guard
-16
src/content/blog/third-post.md
··· 1 - --- 2 - title: 'Third post' 3 - description: 'Lorem ipsum dolor sit amet' 4 - pubDate: 'Jul 22 2022' 5 - heroImage: '/blog-placeholder-2.jpg' 6 - --- 7 - 8 - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae ultricies leo integer malesuada nunc vel risus commodo viverra. Adipiscing enim eu turpis egestas pretium. Euismod elementum nisi quis eleifend quam adipiscing. In hac habitasse platea dictumst vestibulum. Sagittis purus sit amet volutpat. Netus et malesuada fames ac turpis egestas. Eget magna fermentum iaculis eu non diam phasellus vestibulum lorem. Varius sit amet mattis vulputate enim. Habitasse platea dictumst quisque sagittis. Integer quis auctor elit sed vulputate mi. Dictumst quisque sagittis purus sit amet. 9 - 10 - Morbi tristique senectus et netus. Id semper risus in hendrerit gravida rutrum quisque non tellus. Habitasse platea dictumst quisque sagittis purus sit amet. Tellus molestie nunc non blandit massa. Cursus vitae congue mauris rhoncus. Accumsan tortor posuere ac ut. Fringilla urna porttitor rhoncus dolor. Elit ullamcorper dignissim cras tincidunt lobortis. In cursus turpis massa tincidunt dui ut ornare lectus. Integer feugiat scelerisque varius morbi enim nunc. Bibendum neque egestas congue quisque egestas diam. Cras ornare arcu dui vivamus arcu felis bibendum. Dignissim suspendisse in est ante in nibh mauris. Sed tempus urna et pharetra pharetra massa massa ultricies mi. 11 - 12 - Mollis nunc sed id semper risus in. Convallis a cras semper auctor neque. Diam sit amet nisl suscipit. Lacus viverra vitae congue eu consequat ac felis donec. Egestas integer eget aliquet nibh praesent tristique magna sit amet. Eget magna fermentum iaculis eu non diam. In vitae turpis massa sed elementum. Tristique et egestas quis ipsum suspendisse ultrices. Eget lorem dolor sed viverra ipsum. Vel turpis nunc eget lorem dolor sed viverra. Posuere ac ut consequat semper viverra nam. Laoreet suspendisse interdum consectetur libero id faucibus. Diam phasellus vestibulum lorem sed risus ultricies tristique. Rhoncus dolor purus non enim praesent elementum facilisis. Ultrices tincidunt arcu non sodales neque. Tempus egestas sed sed risus pretium quam vulputate. Viverra suspendisse potenti nullam ac tortor vitae purus faucibus ornare. Fringilla urna porttitor rhoncus dolor purus non. Amet dictum sit amet justo donec enim. 13 - 14 - Mattis ullamcorper velit sed ullamcorper morbi tincidunt. Tortor posuere ac ut consequat semper viverra. Tellus mauris a diam maecenas sed enim ut sem viverra. Venenatis urna cursus eget nunc scelerisque viverra mauris in. Arcu ac tortor dignissim convallis aenean et tortor at. Curabitur gravida arcu ac tortor dignissim convallis aenean et tortor. Egestas tellus rutrum tellus pellentesque eu. Fusce ut placerat orci nulla pellentesque dignissim enim sit amet. Ut enim blandit volutpat maecenas volutpat blandit aliquam etiam. Id donec ultrices tincidunt arcu. Id cursus metus aliquam eleifend mi. 15 - 16 - Tempus quam pellentesque nec nam aliquam sem. Risus at ultrices mi tempus imperdiet. Id porta nibh venenatis cras sed felis eget velit. Ipsum a arcu cursus vitae. Facilisis magna etiam tempor orci eu lobortis elementum. Tincidunt dui ut ornare lectus sit. Quisque non tellus orci ac. Blandit libero volutpat sed cras. Nec tincidunt praesent semper feugiat nibh sed pulvinar proin gravida. Egestas integer eget aliquet nibh praesent tristique magna.
-25
src/content/blog/using-mdx.mdx
··· 1 - --- 2 - title: 'Using MDX' 3 - description: 'Lorem ipsum dolor sit amet' 4 - pubDate: 'Jun 01 2024' 5 - heroImage: '/blog-placeholder-5.jpg' 6 - --- 7 - 8 - This theme comes with the [@astrojs/mdx](https://docs.astro.build/en/guides/integrations-guide/mdx/) integration installed and configured in your `astro.config.mjs` config file. If you prefer not to use MDX, you can disable support by removing the integration from your config file. 9 - 10 - ## Why MDX? 11 - 12 - MDX is a special flavor of Markdown that supports embedded JavaScript & JSX syntax. This unlocks the ability to [mix JavaScript and UI Components into your Markdown content](https://docs.astro.build/en/guides/markdown-content/#mdx-features) for things like interactive charts or alerts. 13 - 14 - If you have existing content authored in MDX, this integration will hopefully make migrating to Astro a breeze. 15 - 16 - ## Example 17 - 18 - Here is how you import and use a UI component inside of MDX. 19 - When you open this page in the browser, you should see the clickable button below. 20 - 21 - ## More Links 22 - 23 - - [MDX Syntax Documentation](https://mdxjs.com/docs/what-is-mdx) 24 - - [Astro Usage Documentation](https://docs.astro.build/en/guides/markdown-content/#markdown-and-mdx-pages) 25 - - **Note:** [Client Directives](https://docs.astro.build/en/reference/directives-reference/#client-directives) are still required to create interactive components. Otherwise, all components in your MDX will render as static HTML (no JavaScript) by default.
+316
src/content/blog/workbench-typescript.md
··· 1 + --- 2 + title: Workbench + TypeScript 3 + description: Quickly prototyping and iterating on typesafe GNOME apps. 4 + publish_date: 2024-08-26 5 + tags: [linux] 6 + --- 7 + 8 + ## Prologue 9 + 10 + When I started writing [GTK] apps (GTK is a cross-platform toolkit for creating 11 + graphical user interfaces. Particularly common with Linux app developers), I 12 + noticed that writing apps was noticeably more difficult. 13 + 14 + From the perspective of a web developer: Writing desktop apps is hard. Not 15 + entirely because the languages used in writing desktop apps are harder, but I'd 16 + argue that it's because of the accomodations and tools web developers have at 17 + their disposal. 18 + 19 + ### TypeScript 20 + 21 + One of such tools that help massively with developer experience in the web 22 + development world is [TypeScript]. It's a programming language introduced by 23 + Microsoft in 2012. It's a superset of JavaScript, but adds static types with 24 + optional type annotations. 25 + 26 + This means at a basic level it's just JavaScript, with the added possibility of 27 + adding types to your variables and parameters. So if you had code like this 28 + 29 + ```js 30 + function sayHello(name) { 31 + console.log(`Hello ${name}. Your name is ${name.length} characters long.`); 32 + } 33 + ``` 34 + 35 + It would instead become: 36 + 37 + ```ts 38 + // Notice the `: string` 39 + function sayHello(name: string) { 40 + console.log(`Hello ${name}. Your name is ${name.length} characters long.`); 41 + } 42 + ``` 43 + 44 + For platform developers, this looks seemingly normal, as most of the languages 45 + they use are probably already typed (C, C++, Rust, and more). But for JavaScript 46 + developers, this was a very novel idea and as you can probably guess, it reduces 47 + the bugs you write in the first place by a huge margin. 48 + 49 + In the example above, in the JavaScript code you could pass a number instead of 50 + a string to the `sayHello` function. JavaScript will happily execute that, but 51 + then you will run into bugs sooner or later. 52 + 53 + ```js 54 + sayHello(12); 55 + // Hello 12. Your name is undefined characters long. 56 + ``` 57 + 58 + TypeScript will easily catch these errors, though: 59 + 60 + ```ts 61 + sayHello(12); 62 + // ts: Argument of type 'number' is not assignable to parameter of type 'string'. 63 + ``` 64 + 65 + This is just the surface of what's available with TypeScript, but at this point 66 + I think you and me both can see how it massively improves the developer's 67 + experience. 68 + 69 + ### Developing Desktop apps is hard 70 + 71 + Now writing desktop apps is hard. Usually, developers use languages like C to 72 + write apps. One of the first shortcomings I found is that when you are writing 73 + apps this way, you first need to write the desired changes, compile your app and 74 + then run the app to see your changes. This is in sharp contrast with the way web 75 + development works. There is a neat feature called 76 + [Hot Module Replacement][hmr] where the compiler will replace the changed modules in your 77 + code while the application is running, without reloading anything. We don't 78 + (AFAIK) have this feature in GTK app development yet (this was actually supposed 79 + to be my other proposal) but we can achieve something similar with a neat little 80 + app called [Workbench]. 81 + 82 + ### Workbench 83 + 84 + ![Workbench's main window](/images/posts/workbench-typescript/workbench-main-window.png) 85 + 86 + [Workbench] is an app that simply allows you to write the UI interface of your 87 + app, write some accompanying behavior code and with a simple click of the "Run" 88 + button, the UI you are iterating on becomes readily available. When you are done 89 + working on this particular piece of UI, you are supposed to copy the code you've 90 + written into your application, knowing the code will work and hence skipping the 91 + complex iteration loops. 92 + 93 + I've used Workbench quite consistently while working on my GTK applications. GTK 94 + (and the underlying libraries) are quite awesome because they allow you to write 95 + apps in a number of different languages (called language bindings). And you can write GTK 96 + apps using your favourite language, whether it's JavaScript, Rust, C, C++, 97 + Python and more. Since I was coming from web development, I chose to use 98 + JavaScript for writing GTK apps as it was quite familiar to me already. 99 + 100 + ### TypeScript with GTK 101 + 102 + But in the GTK app development ecosystem, 103 + [there was a growing interest to write 104 + apps in TypeScript instead of JavaScript][chris-ts-blog] where possible, and I 105 + was intrigued as well. I started using TypeScript instead of JavaScript for my 106 + apps, and loved it. There was one piece missing however, Workbench, the code 107 + playground, still did not support TypeScript. To make my TypeScript code work in 108 + Workbench, I used to compile the TypeScript code into JavaScript, put it in 109 + Workbench, then rewrite the code back into TypeScript for my app. Very 110 + inconvinient. 111 + 112 + At some time, I began talking with [Sonny Piers][sonny], and we began talking 113 + about possibly adding TypeScript support to Workbench, to allow developers to 114 + use the language rapidly gaining foothold in the GNOME/GTK app development 115 + arena. This would also help welcome new developers into GNOME, as more and more 116 + web developers are familiar with TypeScript and would provide a familiar 117 + platform language to them. 118 + 119 + ## GSoC 120 + 121 + With that in mind, I wrote a [Google Summer of Code][gsoc] proposal titled "Add 122 + TypeScript Support to Workbench". As early as hitting the "Send" button, I 123 + started working on the project. 124 + 125 + ### Adding the TypeScript View to Workbench 126 + 127 + The first task I ever did was to 128 + [add an option for "TypeScript" in the Workbench app][pr-1]. This was relatively 129 + simple enough in the sense that all it did was add an option to the dropdown at 130 + the top allowing the user to select "TypeScript" as their preferred language. 131 + 132 + I used the pre-existing [TypeScript SDK Extension][typescript-sdk] written 133 + Christopher Davis. This SDK Extension provided the `tsc` (TypeScript Compiler) 134 + and `typescript-language-server` executables to the Flatpak environment 135 + Workbench works in. 136 + 137 + ### Compiling the TypeScript to JavaScript 138 + 139 + Even though there was a TypeScript View, the TypeScript code you entered was 140 + simply interpreted as Javascript. This means that you were not able to use 141 + TypeScript language features just yet, and hence the next step to implement 142 + would be to 143 + [compile TypeScript into JavaScript and then 144 + execute the JavaScript code](https://github.com/workbenchdev/Workbench/pull/941) 145 + when you clicked the "Run" button. 146 + 147 + This was all trivial thanks to the aforementioned TypeScript SDK Extension. All 148 + we had to do was: 149 + 150 + 1. Write the TypeScript code into a `main.ts` file. 151 + 2. Compile the TypeScript code into a `main.js` file using `tsc`. 152 + 3. Import the `main.js` file and then execute it. 153 + 154 + With this in place, we had real support for running TypeScript in Workbench. 155 + 156 + ![TypeScript code running in Workbench](/images/posts/workbench-typescript/typescript-in-workbench.png "Source --- https://floss.social/@sonny/112436298323911287") 157 + 158 + ### Adding GObject modules types 159 + 160 + With that set in place, there was still an issue, and that, the types of 161 + [GObject] modules. 162 + 163 + [GObject], or the GLib Object System is is a library providing a portable object 164 + system and transparent cross-language interoperability. 165 + [GObject Introspection][gi] (GI or GIR) is a related library providing access to 166 + typelibs and introspection data which describes C APIs. 167 + 168 + Essentialy, this system provides a way to import and use "modules" in many 169 + languages. [Gtk] and many other related libraries like GLib, Pango, Gio are all 170 + GI modules and you import them to use them. For example, here is how you create 171 + a Gtk Label in GJS (GNOME JavaScript): 172 + 173 + ```js 174 + import Gtk from "gi://Gtk?version=4.0"; 175 + 176 + const label = new Gtk.Label({ 177 + label: "Hello, World!", 178 + }); 179 + 180 + // present the label 181 + ``` 182 + 183 + For TypeScript to be useful, we need to provide "TypeScript definition files" 184 + (`.d.ts`) that will essentially tell us the type of the `gi://Gtk?version=4.0` 185 + and other imports. Because these files must be statically available, we need a 186 + way to source them. 187 + 188 + #### `gi.ts` 189 + 190 + Thankfully for us, there was a project named [gi.ts] by Evan Welsh that did 191 + exactly that. It was a binary that you would execute and point it to a GIR file, 192 + say `/usr/share/gir-1.0/Gtk-4.0.gir` and then it would generate the TypeScript 193 + definition files (`gtk4.d.ts`) and all their dependencies as we needed. 194 + 195 + #### `gi-typescript-definitions` 196 + 197 + Christopher Davis then created a repository called 198 + [gi-typescript-definitions](https://gitlab.gnome.org/BrainBlasted/gi-typescript-definitions) 199 + where they would regenerate the typescript definition files regularly, and we 200 + would consume the definition files instead of regenerating them ourselves. 201 + 202 + #### Adding the TypeScript generation files to Workbench 203 + 204 + I hence added `gi-typescript-definitions` as a submodule in Workbench, and 205 + pointed the typescript compiler to these definition files. And with everything 206 + all together, [we now had Typechecking in Workbench][pr-3] and as a side effect, 207 + we had diagnostics and completions too!! 208 + 209 + ![Type diagnostics and completions in Workbench](/images/posts/workbench-typescript/typechecking-in-workbench.png "Source --- https://floss.social/@sonny/112786875907926117") 210 + 211 + ### Typechecking with JavaScript?? 212 + 213 + As it turns out, we could use the new features (diagnostics and completions) 214 + from TypeScript in JavaScript! This is by using a special `jsconfig.json` file 215 + and using the aforementioned `typescript-language-server` for JavaScript files 216 + too, which it supports. 217 + 218 + This means 219 + [we got similar diagnostics and completions even for JavaScript!!][pr-4] 220 + 221 + ![Typechecking Javascript? Heck yeah!!](/images/posts/workbench-typescript/typechecking-javascript.png) 222 + 223 + ### `ts-for-gir` 224 + 225 + Remember the tool named [gi.ts] used to generate typescript definition files I 226 + talked about earlier, turns out there is an exceedingly similar tool named 227 + [ts-for-gir] by Pascal Garber that does the same thing!! 228 + 229 + After the developers of both tools noticed the other one, they started working 230 + on [marrying the two projects together][ts-for-gir-gi.ts-marriage] and called 231 + the resulting project `ts-for-gir` v4, which is still in beta at the time of 232 + writing. 233 + 234 + I knew that I had to start working on a way to use types generated by this new 235 + version of `ts-for-gir` instead of relying on `gi.ts`, which has now been 236 + archived and hence not receiving updates. So I now have a 237 + [draft PR that switches from `gi.ts` to `ts-for-gir`][pr-5], but the question of 238 + [how we generate the typescript definition files is still unsolved][question-1] 239 + because if you remember correctly, we were using pregenerated typescript 240 + definition files from `gi-typescript-definitions` and now we will need to 241 + generate them ourselves in Workbench. Unfortunately, 242 + [there is currently an issue blocking this][issue-1], but hopefully it will be 243 + resolved soon. 244 + 245 + ### Porting the demos 246 + 247 + Workbench has a huge library of demos, which I would also like to recommend the next 248 + time you need to familiarise yourself with how a certain platform feature works. 249 + The [demos library][demos] has different code samples in various languages: 250 + JavaScript, Vala, Rust and Python. 251 + 252 + ![Workbench demos library](/images/posts/workbench-typescript/demos.png) 253 + 254 + My work would hence be incomplete without 255 + [porting all the JavaScript demos to TypeScript][pr-6], a noticeably complex 256 + task that is currently ongoing. 257 + 258 + Because TypeScript compiles into JavaScript, we chose to remove all JavaScript 259 + demos and instead add TypeScript demos, and this way we can compile all the 260 + TypeScript demos into JavaScript at build time, and this way we get both TS and 261 + JS demos for the price of one. Pretty cool, right? 262 + 263 + ## Wrapping up 264 + 265 + I hope you enjoy the TypeScript support in Workbench that will hopefully be 266 + available in the next release, and start writing GNOME apps!! I also hope you liked 267 + this write-up I made about all this and don't hesitate to reach out to me if you 268 + have any questions or feedback. 269 + 270 + ### Acknowledgements 271 + 272 + I would like to thank [Google Summer of Code][gsoc] for providing me with the 273 + opportunity of working on this, and the [GNOME Foundation][gnome] which 274 + administered the programme. 275 + 276 + I would like to thank my mentors, Sonny Piers and Andy Holmes for providing me 277 + with unceasing feedback and guidance, I really appreaciate them!! 278 + 279 + I would also like to thank Christopher Davis, for their early experimentations 280 + on working with TypeScript on GNOME, `gi-typescript-definitions`, the TypeScript 281 + SDK Extension and many many more. You're awesome! 282 + 283 + I would also like to extend my thanks to Pascal Garber, for working on 284 + `ts-for-gir`, their feedback and putting up with me on my wild feature requests 285 + 😅. 286 + 287 + Finally, let me thank all the other people that helped me in the internship, 288 + from Bharat, a fellow GSoCer to the whole GNOME community for helping me out 289 + when stuck. 290 + 291 + Thank you everyone! 292 + 293 + [gtk]: https://www.gtk.org/ 294 + [typescript]: https://www.typescriptlang.org/ 295 + [hmr]: https://webpack.js.org/concepts/hot-module-replacement/ 296 + [workbench]: https://apps.gnome.org/Workbench/ 297 + [chris-ts-blog]: https://blogs.gnome.org/christopherdavis/2022/08/25/trying-typescript-for-gnome-apps/ 298 + [sonny]: https://sonny.re 299 + [gsoc]: https://summerofcode.withgoogle.com/ 300 + [typescript-sdk]: https://github.com/flathub/org.freedesktop.Sdk.Extension.typescript 301 + [gobject]: https://docs.gtk.org/gobject/ 302 + [gi]: https://docs.gtk.org/girepository/ 303 + [gi.ts]: https://github.com/gjsify/gi.ts 304 + [gi-typescript-definitions]: https://gitlab.gnome.org/BrainBlasted/gi-typescript-definitions 305 + [ts-for-gir]: https://github.com/gjsify/ts-for-gir 306 + [ts-for-gir-gi.ts-marriage]: https://github.com/gjsify/ts-for-gir/issues/120 307 + [gnome]: https://gnome.org 308 + [demos]: https://github.com/workbenchdev/demos 309 + [pr-1]: https://github.com/workbenchdev/Workbench/pull/938 310 + [pr-2]: https://github.com/workbenchdev/Workbench/pull/941 311 + [pr-3]: https://github.com/workbenchdev/Workbench/pull/946 312 + [pr-4]: https://github.com/workbenchdev/Workbench/pull/972 313 + [pr-5]: https://github.com/workbenchdev/Workbench/pull/979 314 + [pr-6]: https://github.com/workbenchdev/demos/pull/201 315 + [question-1]: https://github.com/workbenchdev/Workbench/pull/979#issuecomment-2299187978 316 + [issue-1]: https://github.com/workbenchdev/Workbench/issues/980
+11 -8
src/layouts/BlogPost.astro
··· 4 4 import Header from "../components/Header.astro"; 5 5 import ImageContainer from "../components/ImageContainer.astro"; 6 6 import PostInfo from "../components/PostInfo.astro"; 7 - import { PUBLISH_YEAR, SITE_TITLE } from "../consts"; 7 + import { AUTHOR, PUBLISH_YEAR, SITE_TITLE } from "../consts"; 8 8 9 9 type Props = CollectionEntry<"blog">["data"]; 10 10 11 - const { title, description, pubDate, heroImage } = Astro.props; 11 + const { title, description, publish_date, hero_image, tags } = Astro.props; 12 12 --- 13 13 14 14 <html lang="en"> ··· 18 18 19 19 <body class="overflow-yes"> 20 20 { 21 - heroImage && ( 22 - <ImageContainer image={heroImage} invert={false}> 21 + hero_image && ( 22 + <ImageContainer image={hero_image} invert={false}> 23 23 <Fragment slot="top"> 24 24 <Header title={SITE_TITLE} /> 25 25 </Fragment> ··· 27 27 <PostInfo 28 28 title={title} 29 29 description={description} 30 - pubDate={pubDate} 30 + publish_date={publish_date} 31 + tags={tags} 31 32 /> 32 33 </Fragment> 33 34 </ImageContainer> ··· 35 36 } 36 37 <main class="container"> 37 38 { 38 - !heroImage && ( 39 + !hero_image && ( 39 40 <> 40 41 <Header title={SITE_TITLE} /> 41 42 <PostInfo 42 43 title={title} 43 44 description={description} 44 - pubDate={pubDate} 45 + publish_date={publish_date} 46 + tags={tags} 45 47 /> 46 48 <hr /> 49 + <br /> 47 50 </> 48 51 ) 49 52 } ··· 58 61 ( 59 62 <div> 60 63 <br /> 61 - <span>&copy; Angelo Verlain {PUBLISH_YEAR}</span> 64 + <span>&copy; {AUTHOR} {PUBLISH_YEAR}</span> 62 65 </div> 63 66 ) 64 67 }
+2 -2
src/pages/index.astro
··· 1 1 --- 2 2 import BaseHead from "../components/BaseHead.astro"; 3 - import { SITE_TITLE, SITE_DESCRIPTION } from "../consts"; 3 + import { SITE_TITLE, SITE_DESCRIPTION, AUTHOR } from "../consts"; 4 4 import Intro from "../components/Intro.astro"; 5 5 import Welcome from "../components/Welcome.astro"; 6 6 import PostsIndex from "../components/PostsIndex.astro"; ··· 25 25 <h2>Contact & Links</h2> 26 26 <ContactLinks /> 27 27 <footer> 28 - &copy; Angelo Verlain {new Date().getFullYear()} 28 + &copy; {AUTHOR} {new Date().getFullYear()} 29 29 </footer> 30 30 </main> 31 31 </body>
+69 -1
src/styles/global.css
··· 121 121 } 122 122 123 123 .posts > p { 124 - margin-bottom: 4em; 124 + margin-bottom: 2em; 125 125 } 126 126 127 127 .posts > p a.post-title { ··· 369 369 color: inherit; 370 370 } 371 371 } 372 + 373 + code, 374 + kbd, 375 + samp, 376 + pre { 377 + font-family: 378 + /* macOS 10.10+ */ "Menlo", 379 + /* Windows 6+ */ "Consolas", 380 + /* Android 4+ */ "Roboto Mono", 381 + /* Ubuntu 10.10+ */ "Ubuntu Monospace", 382 + /* KDE Plasma 5+ */ "Noto Mono", 383 + /* KDE Plasma 4+ */ "Oxygen Mono", 384 + /* Linux/OpenOffice fallback */ "Liberation Mono", 385 + /* fallback */ monospace, 386 + /* macOS emoji */ "Apple Color Emoji", 387 + /* Windows emoji */ "Segoe UI Emoji", 388 + /* Windows emoji */ "Segoe UI Symbol", 389 + /* Linux emoji */ "Noto Color Emoji"; 390 + } 391 + 392 + blockquote { 393 + margin: 3em 0; 394 + padding: 10px 0; 395 + border-width: 4px 0; 396 + border-style: double; 397 + } 398 + 399 + blockquote p { 400 + margin: 0; 401 + } 402 + 403 + code { 404 + padding: 0 5px; 405 + font-size: 0.9em; 406 + border: 1px solid; 407 + border-radius: 5px; 408 + line-height: 1; 409 + } 410 + 411 + pre:not(.not-code) code { 412 + display: block; 413 + padding: 10px; 414 + color: #b2b6bb; 415 + background-color: #0a1826; 416 + border: 0; 417 + line-height: 1.5; 418 + } 419 + 420 + code, 421 + pre { 422 + text-align: left; 423 + white-space: pre; 424 + word-spacing: normal; 425 + word-break: normal; 426 + word-wrap: normal; 427 + -moz-tab-size: 2; 428 + -o-tab-size: 2; 429 + tab-size: 2; 430 + -webkit-hyphens: none; 431 + -moz-hyphens: none; 432 + -ms-hyphens: none; 433 + hyphens: none; 434 + overflow: auto; 435 + } 436 + 437 + pre { 438 + background-color: transparent !important; 439 + }