this repo has no description
1
fork

Configure Feed

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

✨ Bunch of stuff

+486 -90
+3 -1
components/intro.templ
··· 5 5 font-weight: 800; 6 6 font-size: clamp(3rem, 10vw, 7rem); 7 7 line-height: 0.8; 8 + max-width: 1000px; 9 + margin: 0 auto 1rem; 8 10 } 9 11 10 12 css explain() { 11 13 margin: 0 auto; 12 - margin-top: -2rem; 14 + max-width: 800px; 13 15 text-align: center; 14 16 } 15 17
+19 -4
components/ui.templ
··· 2 2 3 3 import "github.com/ewen-lbh/portfolio/shared" 4 4 5 - css iconedLink() { 6 - display: inline-flex; 5 + css simpleLink() { 7 6 align-items: center; 7 + display: inline-flex; 8 8 text-decoration: none; 9 9 transition: font-weight 0.125s ease-in-out; 10 10 text-underline-offset: .5ch; 11 + } 12 + 13 + css iconedLink() { 11 14 margin-top: 0.5em; 12 15 } 13 16 ··· 26 29 return styles[0] 27 30 } 28 31 32 + templ SimpleLink(href string) { 33 + @shared.OnHover( 34 + simpleLink(), 35 + shared.Declarations{ 36 + "text-decoration": "underline 0.125em", 37 + }, 38 + ) 39 + <a class={ simpleLink(), iconedLink() } href={ templ.SafeURL(href) }> 40 + { children... } 41 + </a> 42 + } 43 + 29 44 templ ArrowLink(href string, styles ...templ.CSSClass) { 30 45 @shared.OnHover( 31 46 iconedLink(), ··· 33 48 "text-decoration": "underline 0.125em", 34 49 }, 35 50 ) 36 - <a class={ iconedLink(), optionalStyles(styles...) } href={ templ.SafeURL(href) }> 51 + <a class={ simpleLink(), iconedLink(), optionalStyles(styles...) } href={ templ.SafeURL(href) }> 37 52 <svg class={ iconedLinkIcon() } width="66" height="33" viewBox="0 0 66 33" fill="none" xmlns="http://www.w3.org/2000/svg"> 38 53 <path d="M17.9625 2L3 16.5M3 16.5L17.9625 31M3 16.5H66" stroke="currentColor" stroke-width="3" transform="rotate(180 33 16.5)"></path> 39 54 </svg> ··· 48 63 "text-decoration": "underline 0.125em", 49 64 }, 50 65 ) 51 - <a class={ iconedLink() } href={ templ.SafeURL(href) }> 66 + <a class={ simpleLink(), iconedLink() } href={ templ.SafeURL(href) }> 52 67 <svg class={ iconedLinkIcon() } width="66" height="33" viewBox="0 0 66 33" fill="none" xmlns="http://www.w3.org/2000/svg"> 53 68 <path d="M17.9625 2L3 16.5M3 16.5L17.9625 31M3 16.5H66" stroke="currentColor" stroke-width="3"></path> 54 69 </svg>
+7 -1
i18n.go
··· 205 205 missingMessages: make([]po.Message, 0), 206 206 language: languageCode, 207 207 } 208 - fmt.Printf("[%s] Loaded %d translations\n", languageCode, len(poFile.Messages)) 208 + filledTranslationsCount := 0 209 + for _, msg := range poFile.Messages { 210 + if msg.MsgId != "" { 211 + filledTranslationsCount++ 212 + } 213 + } 214 + fmt.Printf("[%s] Loaded %d translations\n", languageCode, filledTranslationsCount) 209 215 } 210 216 } 211 217 return translations, nil
+45 -27
i18n/fr.po
··· 15 15 msgid "“Syntactically Awesome Style Sheets”—A mature, stable, and powerful professional grade CSS extension language." 16 16 msgstr "“Syntactically Awesome Style Sheets”—Une extension puissante, stable et professionelle du langage CSS." 17 17 18 - msgid "A <a href='https://svelte.dev'>Svelte</a>-powered framework for building web applications of all sizes, with a beautiful development experience and flexible filesystem-based routing" 19 - msgstr "Un framework alimenté par <a href='https://svelte.dev'>Svelte</a> pour construire des applications web de toutes tailles, avec une belle expérience de développement et un routage flexible basé sur le système de fichiers." 18 + msgid "12 March 2020" 19 + msgstr "" 20 + 21 + msgid "A <a href=\"https://svelte.dev\">Svelte</a>-powered framework for building web applications of all sizes, with a beautiful development experience and flexible filesystem-based routing" 22 + msgstr "" 20 23 21 24 msgid "A bundler for javascript and friends. Packs many modules into a few bundled assets." 22 25 msgstr "Un bundler pour javascript et autres. Regroupe de nombreux modules en quelques fichiers groupés." ··· 40 43 msgstr "Un langage de programmation open source dynamique, axé sur la simplicité et la productivité. Il possède une syntaxe élégante, naturelle et facile à écrire." 41 44 42 45 msgid "A graphism technique that involves the composition of multiple images into a single, cohesive one" 43 - msgstr "Une technique de graphisme qui implique la composition de plusieurs images en un ensemble cohérent" 46 + msgstr "" 44 47 45 48 msgid "A high-level programming language that enables interactive web pages" 46 49 msgstr "Un langage de programmation de haut niveau qui permet l'intéractivité de pages web" ··· 48 51 msgid "A high-level Python Web framework that encourages rapid development and clean, pragmatic design" 49 52 msgstr "Un framework Web Python de haut niveau qui encourage un développement rapide et une conception propre et pragmatique" 50 53 51 - msgid "A high-performance template engine heavily influenced by <a href='https://haml.info/'>Haml</a> and implemented with JavaScript for Node.js and browsers." 52 - msgstr "Un moteur de template haute performance fortement influencé par <a href='https://haml.info/'>Haml</a> et implémenté avec JavaScript pour Node.js et les navigateurs." 54 + msgid "A high-performance template engine heavily influenced by <a href=\"https://haml.info/\">Haml</a> and implemented with JavaScript for Node.js and browsers." 55 + msgstr "" 53 56 54 57 msgid "A high-quality typesetting & document preparation system" 55 58 msgstr "Un système de composition et de préparation des documents de haute qualité" ··· 112 115 msgstr "Une série de nano-ordinateurs" 113 116 114 117 msgid "A set of brand resources like logos and banners that make up a brand's visal identity" 115 - msgstr "Un ensemble de ressources de marque, comme des logos et des bannières, qui constituent l'identité visuelle d'une marque" 118 + msgstr "" 116 119 117 120 msgid "A simple cross-platform library to create graphical user interfaces in [Go](/using/go) that work on Android, iOS, Linux, MacOS and Windows." 118 121 msgstr "Une bibliothèque multiplateforme simple pour créer des interfaces graphiques en [Go](/using/go) qui fonctionnent sur Android, iOS, Linux, MacOS et Windows." ··· 172 175 msgstr "un format d'image vectorielle basé sur XML pour définir des graphiques bidimensionnels, ayant un support pour l'interactivité et l'animation. La spécification SVG est un standard ouvert développé par le World Wide Web Consortium depuis 1999" 173 176 174 177 msgid "Any piece of work related to languages (programming languages or natural languages)" 175 - msgstr "Projets liés au langage (langages de programmation ou langages naturels)" 178 + msgstr "" 176 179 177 180 msgid "Any piece of work that involves programming, such as libraries, programs, scripts, et cætera." 178 - msgstr "Toute œuvre qui implique de la programmation, telles que des bibliothèques, des programmes, des scripts, etc." 181 + msgstr "" 179 182 180 183 msgid "apis" 181 184 msgstr "APIs" ··· 184 187 msgstr "application" 185 188 186 189 msgid "Application Programming Interfaces, mostly _Web_ APIs, meaning machine-redable sites that allow data processing and interaction without going to the client-facing website" 187 - msgstr "Interfaces de programmation d'applications, principalement des API _Web_, c'est-à-dire des sites accessibles aux machines qui permettent le traitement des données et l'interaction sans passer par le site web orienté vers le client." 190 + msgstr "" 188 191 189 192 msgid "apps" 190 193 msgstr "applications" ··· 204 207 msgid "cli" 205 208 msgstr "cli" 206 209 207 - msgid "command line" 208 - msgstr "terminal" 209 - 210 210 msgid "command-line" 211 211 msgstr "terminal" 212 212 ··· 241 241 msgstr "Langage CSS expressif, robuste, riche en fonctionnalités pour nodejs" 242 242 243 243 msgid "Extensions or add-ons that add features to web browsers" 244 - msgstr "Extensions ou add-ons qui ajoutent des fonctionnalités à un navigateurs web" 244 + msgstr "" 245 245 246 246 msgid "flyer" 247 247 msgstr "flyer" ··· 289 289 msgstr "illustrations" 290 290 291 291 msgid "Images that represent a media, such as a video, in a single glance. Might be shown at a small size." 292 - msgstr "Images qui représentent un média, tel qu'une vidéo, d'un seul coup d'œil. Peut être affiché à petite taille." 292 + msgstr "" 293 293 294 294 msgid "intros" 295 295 msgstr "intros" ··· 306 306 msgid "linguistics" 307 307 msgstr "linguistique" 308 308 309 + msgid "made with" 310 + msgstr "réalisé avec" 311 + 309 312 msgid "Made with the intent to discover, not necessarily to be useful or aesthetic." 310 - msgstr "Réalisé dans le but de découvrir sans contrainte d'utilité ou d'esthétique." 313 + msgstr "" 311 314 312 315 msgid "math" 313 316 msgstr "maths" ··· 322 325 msgstr "musique" 323 326 324 327 msgid "Music covers for albums, EPs and singles" 325 - msgstr "Pochettes pour albums, EPs et singles" 328 + msgstr "" 326 329 327 330 msgid "my carreer" 328 331 msgstr "ma carrière" ··· 330 333 msgid "my music" 331 334 msgstr "ma musique" 332 335 336 + msgid "Native" 337 + msgstr "Langue maternelle" 338 + 333 339 msgid "Open Source design and prototyping platform meant for cross-domain teams. Non dependent on operating systems, Penpot is web based and works with open web standards (SVG)." 334 340 msgstr "Plateforme de conception et prototypage open source visant des équipes multi-domaines. Non dépendante des systèmes d'exploitation, Penpot est un logiciel web et fonctionne avec les standards web ouverts (SVG)." 335 341 ··· 355 361 msgstr "programmes" 356 362 357 363 msgid "Programs that run in the terminal, a text-based interface for computers." 358 - msgstr "Programmes qui s'exécutent dans une console, une interface de texte pour les ordinateurs." 364 + msgstr "" 365 + 366 + msgid "project" 367 + msgstr "projet" 368 + 369 + msgid "projects" 370 + msgstr "projets" 359 371 360 372 msgid "Proudly powered by <a href=\"https://github.com/ortfo\">ortfo</a>, my portfolio database management system and <a href=\"https://github.com/a-h/templ\">templ</a>" 361 373 msgstr "Fièrement propulsé par <a href=\"https://github.com/ortfo\">ortfo</a>, mon système de gestion de base de données pour les portfolios et <a href=\"https://github.com/a-h/templ\">templ</a>" ··· 367 379 msgstr "scolaire" 368 380 369 381 msgid "School projects and school-related projects" 370 - msgstr "Projets scolaires et projets liés à l'école" 382 + msgstr "" 371 383 372 384 msgid "science" 373 385 msgstr "science" ··· 379 391 msgstr "Envoyer" 380 392 381 393 msgid "Set of glyphs (letters, punctuation marks and more) that define how text looks." 382 - msgstr "Ensemble de glyphes (lettres, signes de ponctuation et plus encore) qui définissent l'apparence du texte." 394 + msgstr "" 383 395 384 396 msgid "Short introductory video sequences typically played at the beginning of every video" 385 - msgstr "Séquences vidéo introducientrières courtes qui sont typiquement jouées au début de chaque vidéo" 397 + msgstr "" 398 + 399 + msgid "Single-page PDF resume" 400 + msgstr "CV monopage en PDF" 386 401 387 402 msgid "sites" 388 403 msgstr "sites" 404 + 405 + msgid "skills" 406 + msgstr "" 389 407 390 408 msgid "Source code" 391 409 msgstr "Code source" ··· 403 421 msgstr "Le langage de balisage standard conçu pour représenter des pages web" 404 422 405 423 msgid "The study of language" 406 - msgstr "L'étude du langage" 424 + msgstr "" 407 425 408 426 msgid "These are my creations." 409 427 msgstr "Voici mes créations." 428 + 429 + msgid "This is a dynamic resume, pulling from <em><a href=\"https://wakatime.com\">wakatime</a></em> and other sources, including the portfolio's database itself." 430 + msgstr "" 410 431 411 432 msgid "thumbnails" 412 433 msgstr "miniatures" 413 434 414 - msgid "Time spent" 415 - msgstr "Temps passé" 416 - 417 435 msgid "typeface" 418 436 msgstr "police de caractères" 419 437 ··· 427 445 msgstr "Un système de génération de portfolios faits maison." 428 446 429 447 msgid "User interfaces that utilize graphical elements such as icons, and images." 430 - msgstr "Intefaces utilisateur qui utilisent des éléments graphiques tels que des icônes et des images." 448 + msgstr "" 431 449 432 450 msgid "visual identities" 433 451 msgstr "identités visuelles" ··· 448 466 msgstr "extensions web" 449 467 450 468 msgid "Web sites, web apps and web-related libraries" 451 - msgstr "Sites web, applications web et bibliothèques liées au web" 469 + msgstr "" 452 470 453 471 msgid "With a scientific side" 454 - msgstr "Avec un côté scientifique" 472 + msgstr "" 455 473 456 474 msgid "works" 457 475 msgstr "projets"
+28 -2
main.go
··· 71 71 72 72 handlePage("", pages.Index(db, translations.language)) 73 73 for _, work := range db.Works() { 74 - handlePage(work.ID, pages.Work(work, shared.TagsOf(tags, work.Metadata), shared.TechsOf(technologies, work.Metadata), translations.language)) 74 + handlePage(work.ID, pages.Work( 75 + work, 76 + shared.TagsOf(tags, work.Metadata), 77 + shared.TechsOf(technologies, work.Metadata), 78 + collections.ThatIncludeWork(work, shared.Keys(db.Works()), tags, technologies), 79 + translations.language, 80 + )) 75 81 } 76 82 77 83 for id, collection := range collections { ··· 100 106 } 101 107 102 108 handlePage("about", pages.AboutPage(translations.language)) 109 + handlePage("resume", pages.DynamicResume(db, technologies, translations.language)) 103 110 104 111 handlePage("contact", pages.ContactPage(false)) 105 112 handlePage("contact/sent", pages.ContactPage(true)) ··· 187 194 if err != nil { 188 195 panic(err) 189 196 } 197 + // Ugly fix while ortfodb isn't fixed 198 + for i, work := range db { 199 + if work.ID == "#meta" { 200 + continue 201 + } 202 + if work.Metadata.AdditionalMetadata["made_with"] == nil { 203 + continue 204 + } 205 + newWork := work 206 + for _, techName := range work.Metadata.AdditionalMetadata["made_with"].([]interface{}) { 207 + newWork.Metadata.MadeWith = append(newWork.Metadata.MadeWith, techName.(string)) 208 + } 209 + db[i] = newWork 210 + } 190 211 locales := db.Languages() 191 212 sort.Strings(locales) 192 213 fmt.Printf("[ ] Works database has %d works in %d locales (%s)\n", len(db.Works()), len(locales), strings.Join(locales, ", ")) ··· 215 236 216 237 var collections map[string]shared.Collection 217 238 loadDataFileMap("collections.yaml", &collections) 239 + for id, c := range collections { 240 + newCollection := c 241 + newCollection.ID = id 242 + collections[id] = newCollection 243 + } 218 244 219 245 var sites []shared.Site 220 246 loadDataFile("sites.yaml", &sites) ··· 226 252 loadDataFile("tags.yaml", &tags) 227 253 228 254 for i := range technologies { 229 - err := technologies[i].CalculateTimeSpent() 255 + _, err := technologies[i].CalculateTimeSpent(technologies) 230 256 if err != nil { 231 257 color.Yellow("[!!] While calculating time spent on %s: %s", technologies[i].Name, err) 232 258 } else if technologies[i].TimeSpent.Seconds() > 0 {
+8
pages/contact.templ
··· 35 35 margin-bottom: 1rem; 36 36 box-sizing: border-box; 37 37 width: 100%; 38 + transition: all 0.125s ease; 38 39 } 39 40 40 41 css formLabel() { 41 42 font-size: 1.2em; 43 + margin-left: .5rem; 42 44 } 43 45 44 46 css mailSentBanner() { ··· 55 57 "background-color": "var(--secondary, white)", 56 58 "color": "var(--primary, black)", 57 59 }) 60 + @shared.OnHover(formEntry(), shared.Declarations{ 61 + "border-radius": "2rem", 62 + }) 63 + @shared.OnFocus(formEntry(), shared.Declarations{ 64 + "background-color": "lightgray", 65 + }) 58 66 <form class={ contactForm() } method="post" action="/mail"> 59 67 if sent { 60 68 <section class={ mailSentBanner() }>
+6 -2
pages/filtered.templ
··· 6 6 7 7 templ TagPage(tag shared.Tag, db ortfodb.Database, locale string) { 8 8 @components.IntroWith("", tag.Plural, true) { 9 - <i18n>{ tag.Description }</i18n> 9 + <i18n> 10 + @shared.HTML(tag.Description) 11 + </i18n> 10 12 } 11 13 @components.GalleryPage( 12 14 tag.Works(db), ··· 16 18 17 19 templ TechnologyPage(tech shared.Technology, db ortfodb.Database, locale string) { 18 20 @components.IntroWith("", tech.Name, false) { 19 - <i18n>{ tech.Description }</i18n> 21 + <i18n> 22 + @shared.HTML(tech.Description) 23 + </i18n> 20 24 if tech.TimeSpent.Seconds() > 0 { 21 25 <br/> 22 26 <strong i18n>Time spent</strong> { formatDuration(tech.TimeSpent, locale) }
+90
pages/resume.templ
··· 1 + package pages 2 + 3 + import "fmt" 4 + import "github.com/ewen-lbh/portfolio/shared" 5 + import "github.com/ewen-lbh/portfolio/components" 6 + import "github.com/ortfo/db" 7 + 8 + templ DynamicResume(db ortfodb.Database, technologies []shared.Technology, language string) { 9 + @components.IntroWith("Ewen Le Bihan", "Curriculum Vitæ", false) { 10 + <i18n> 11 + This is a dynamic resume, pulling from <em><a href="https://wakatime.com">wakatime</a> </em> and other sources, including the portfolio's database itself. 12 + </i18n> 13 + <br/> 14 + @components.ArrowLink("/resume.pdf") { 15 + <i18n>Single-page PDF resume</i18n> 16 + } 17 + } 18 + <main> 19 + <section> 20 + <h2 i18n>skills</h2> 21 + <h3 i18n>language</h3> 22 + <ul> 23 + <li> 24 + <h4>Français</h4> 25 + <p i18n>Native</p> 26 + </li> 27 + <li> 28 + <h4>日本語</h4> 29 + <p></p> 30 + </li> 31 + <li> 32 + <h4>English</h4> 33 + <dl> 34 + <dt>TOEIC</dt> 35 + <dd> 36 + 950<span>⁄</span>990 37 + <span i18n>12 March 2020</span> 38 + </dd> 39 + <dt>CECRL </dt> 40 + <dd>C1 </dd> 41 + <dt>ILR </dt> 42 + <dd>4 </dd> 43 + <dt>ACTFL </dt> 44 + <dd>S </dd> 45 + </dl> 46 + </li> 47 + </ul> 48 + <h3>programming</h3> 49 + <p> 50 + work time tracked by <a href="https://wakatime.com">WakaTime</a> 51 + </p> 52 + <ul> 53 + for _, tech := range technologies { 54 + if len(tech.Works(db)) > 0 || tech.TimeSpent > 0 { 55 + <li> 56 + <img src={ string(shared.Asset(fmt.Sprintf("logos/%s.svg", tech.Slug))) }/> 57 + <h4> 58 + if tech.By != "" { 59 + <span>{ tech.By }&nbsp;</span> 60 + } 61 + { tech.Name } 62 + </h4> 63 + if len(tech.Works(db)) > 1 { 64 + <a href={ templ.URL(fmt.Sprintf("/using/%s", tech.Slug)) }> 65 + { fmt.Sprint(len(tech.Works(db))) } <i18n>projects</i18n> 66 + </a> 67 + } else if len(tech.Works(db)) == 1 { 68 + <a href={ templ.URL(fmt.Sprintf("/%s", tech.Works(db)[0].ID)) }> 69 + 1 <i18n>project</i18n> 70 + </a> 71 + } 72 + if tech.TimeSpent > 0 { 73 + <span> 74 + { fmt.Sprint(tech.TimeSpent.Hours()) } 75 + <i18n> 76 + if tech.TimeSpent.Hours() > 1 { 77 + hours 78 + } else { 79 + hour 80 + } 81 + </i18n> 82 + </span> 83 + } 84 + </li> 85 + } 86 + } 87 + </ul> 88 + </section> 89 + </main> 90 + }
+71 -23
pages/work.templ
··· 172 172 } 173 173 174 174 css blockStyles() { 175 - background-color: var(--primary, black); 176 - color: var(--secondary, white); 175 + background-color: var(--secondary, black); 176 + color: var(--primary, white); 177 177 display: flex; 178 178 justify-content: center; 179 179 border-radius: 0.5rem; ··· 219 219 width: 100%; 220 220 } 221 221 222 + css madeWithTechnologiesList() { 223 + display: flex; 224 + flex-wrap: wrap; 225 + column-gap: 2rem; 226 + } 227 + 228 + css madeWithTechnology() { 229 + display: flex; 230 + flex-direction: column; 231 + align-items: center; 232 + border-radius: 2rem; 233 + } 234 + 235 + css madeWithTechnologyLogo() { 236 + height: 5rem; 237 + } 238 + 239 + css madeWithTechnologyDescription() { 240 + font-size: 0.8em; 241 + opacity: 0.75; 242 + display: block; 243 + max-width: 10rem; 244 + max-height: 5rem; 245 + overflow: hidden; 246 + text-overflow: ellipsis; 247 + } 248 + 249 + css standardWorkSection() { 250 + display: flex; 251 + flex-direction: column; 252 + align-items: center; 253 + } 254 + 222 255 // formatDuration formats a duration in a human-readable way using plain words, depending on the language given (fr or en) 223 256 // it handles plurals and singulars, and does not using any words higher than days 224 257 func formatDuration(duration time.Duration, lang string) string { ··· 293 326 return strings.Join(parts, " ") 294 327 } 295 328 296 - templ Work(work ortfodb.AnalyzedWork, tags []shared.Tag, techs []shared.Technology, lang string) { 329 + templ Work(work ortfodb.AnalyzedWork, tags []shared.Tag, techs []shared.Technology, collections []shared.Collection, lang string) { 297 330 @components.IntroWith( 298 331 shared.FormatDate(work.Metadata.CreatedAt(), "January 2006", lang), 299 332 work.Content[lang].Title.String(), ··· 304 337 }) 305 338 <section class={ tagLinks() }> 306 339 for _, tag := range tags { 307 - <a class={ tagLink() } href={ templ.URL("/" + tag.URLName()) }> 308 - <span class={ hashtag() }>#</span><i18n>{ tag.Singular }</i18n> 340 + // Letting the "music" tag would create a duplicate w/ the collection 341 + if tag.URLName() != "music" { 342 + <a class={ tagLink() } href={ templ.URL("/" + tag.URLName()) }> 343 + <span class={ hashtag() }>#</span><i18n>{ tag.Singular }</i18n> 344 + </a> 345 + } 346 + } 347 + <span class={ hashtag() }>·</span> 348 + for _, c := range collections { 349 + <a class={ tagLink() } href={ templ.URL("/" + c.ID) }> 350 + <span class={ hashtag() }>#</span>{ c.Title[lang] } 309 351 </a> 310 352 } 311 353 </section> ··· 331 373 </div> 332 374 } 333 375 </section> 376 + if len(work.Content[lang].Footnotes) > 0 { 377 + <section class="footnotes"> 378 + <h2 i18n>Footnotes</h2> 379 + <ol> 380 + for ref, footnote := range work.Content[lang].Footnotes { 381 + <li id={ "fn:" + ref }> 382 + @shared.HTML(footnote) 383 + <sup><a href={ templ.SafeURL("#fnref:" + ref) }>↑</a></sup> 384 + </li> 385 + } 386 + </ol> 387 + </section> 388 + } 334 389 if shared.TimeSpentOnProject(work).Hours() > 0 { 335 390 <section class="time-spent"> 336 391 <h2 i18n>Time spent</h2> ··· 338 393 </section> 339 394 } 340 395 if len(techs) > 0 { 341 - <section class="made-with"> 342 - <h2 i18n>Made with</h2> 343 - <ul> 396 + <section class={ standardWorkSection() }> 397 + <h2 i18n>made with</h2> 398 + <ul class={ madeWithTechnologiesList() }> 344 399 for _, tool := range techs { 345 - <li> 346 - <a href={ templ.URL("/using/" + tool.Slug) }>{ tool.Name }</a> 400 + <li class={ madeWithTechnology() }> 401 + <img class={ madeWithTechnologyLogo() } src={ string(shared.Asset(fmt.Sprintf("logos/%s.svg", tool.Slug))) }/> 402 + @components.SimpleLink("/using/" + tool.Slug) { 403 + { tool.Name } 404 + } 405 + <div class={ madeWithTechnologyDescription() } i18n> 406 + @shared.HTML(tool.Description) 407 + </div> 347 408 </li> 348 409 } 349 410 </ul> 350 - </section> 351 - } 352 - if len(work.Content[lang].Footnotes) > 0 { 353 - <section class="footnotes"> 354 - <h2 i18n>Footnotes</h2> 355 - <ol> 356 - for ref, footnote := range work.Content[lang].Footnotes { 357 - <li id={ "fn:" + ref }> 358 - @shared.HTML(footnote) 359 - <sup><a href={ templ.SafeURL("#fnref:" + ref) }>↑</a></sup> 360 - </li> 361 - } 362 - </ol> 363 411 </section> 364 412 } 365 413 </main>
+46
public/icons/close.svg
··· 1 + <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 + <svg 3 + width="21" 4 + height="21" 5 + viewBox="0 0 21 21" 6 + fill="none" 7 + version="1.1" 8 + id="svg4" 9 + sodipodi:docname="close.svg" 10 + inkscape:version="1.2.1 (9c6d41e410, 2022-07-14, custom)" 11 + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" 12 + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" 13 + xmlns="http://www.w3.org/2000/svg" 14 + xmlns:svg="http://www.w3.org/2000/svg"> 15 + <defs 16 + id="defs8" /> 17 + <sodipodi:namedview 18 + id="namedview6" 19 + pagecolor="#ffffff" 20 + bordercolor="#666666" 21 + borderopacity="1.0" 22 + inkscape:showpageshadow="2" 23 + inkscape:pageopacity="0.0" 24 + inkscape:pagecheckerboard="0" 25 + inkscape:deskcolor="#d1d1d1" 26 + showgrid="false" 27 + inkscape:zoom="33.600463" 28 + inkscape:cx="6.0862256" 29 + inkscape:cy="12.574231" 30 + inkscape:window-width="1920" 31 + inkscape:window-height="1080" 32 + inkscape:window-x="0" 33 + inkscape:window-y="0" 34 + inkscape:window-maximized="1" 35 + inkscape:current-layer="svg4" /> 36 + <path 37 + style="fill:none;stroke:#000000;stroke-width:2.92512;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" 38 + d="M 1.1074475,1.058842 19.968611,19.968194" 39 + id="path1195" 40 + sodipodi:nodetypes="cc" /> 41 + <path 42 + style="fill:none;stroke:#000000;stroke-width:2.92512;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" 43 + d="M 19.930427,1.0451515 1.0692456,19.954514" 44 + id="path1195-6" 45 + sodipodi:nodetypes="cc" /> 46 + </svg>
+57
public/icons/hamburger.svg
··· 1 + <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 + <svg 3 + width="21" 4 + height="21" 5 + viewBox="0 0 21 21" 6 + fill="none" 7 + version="1.1" 8 + id="svg4" 9 + sodipodi:docname="hamburger.svg" 10 + inkscape:version="1.2.1 (9c6d41e410, 2022-07-14, custom)" 11 + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" 12 + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" 13 + xmlns="http://www.w3.org/2000/svg" 14 + xmlns:svg="http://www.w3.org/2000/svg"> 15 + <defs 16 + id="defs8" /> 17 + <sodipodi:namedview 18 + id="namedview6" 19 + pagecolor="#ffffff" 20 + bordercolor="#666666" 21 + borderopacity="1.0" 22 + inkscape:showpageshadow="2" 23 + inkscape:pageopacity="0.0" 24 + inkscape:pagecheckerboard="0" 25 + inkscape:deskcolor="#d1d1d1" 26 + showgrid="false" 27 + inkscape:zoom="22.853185" 28 + inkscape:cx="-1.9034546" 29 + inkscape:cy="10.961273" 30 + inkscape:window-width="1920" 31 + inkscape:window-height="1080" 32 + inkscape:window-x="0" 33 + inkscape:window-y="0" 34 + inkscape:window-maximized="1" 35 + inkscape:current-layer="svg4" /> 36 + <rect 37 + style="fill:#000000;stroke:none;stroke-width:1.21228" 38 + id="rect267" 39 + width="21.059233" 40 + height="3.8504581" 41 + x="-0.060698148" 42 + y="-0.019991903" /> 43 + <rect 44 + style="fill:#000000;stroke:none;stroke-width:1.21228" 45 + id="rect267-3" 46 + width="21.059233" 47 + height="3.8504581" 48 + x="-0.059473194" 49 + y="8.125473" /> 50 + <rect 51 + style="fill:#000000;stroke:none;stroke-width:1.21228" 52 + id="rect267-3-6" 53 + width="21.059233" 54 + height="3.8504581" 55 + x="-0.058047015" 56 + y="17.126282" /> 57 + </svg>
+3
public/icons/magnifying_glass.svg
··· 1 + <svg width="21" height="21" viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 + <path fill-rule="evenodd" clip-rule="evenodd" d="M18.8591 7.64706C18.8591 10.8958 16.2255 13.5294 12.9768 13.5294C9.72805 13.5294 7.09443 10.8958 7.09443 7.64706C7.09443 4.39832 9.72805 1.76471 12.9768 1.76471C16.2255 1.76471 18.8591 4.39832 18.8591 7.64706ZM20.6238 7.64706C20.6238 11.8704 17.2001 15.2941 12.9768 15.2941C11.183 15.2941 9.53355 14.6765 8.22934 13.6424L1.24784 20.6239L0 19.376L6.9815 12.3945C5.94733 11.0903 5.32973 9.44082 5.32973 7.64706C5.32973 3.4237 8.75343 0 12.9768 0C17.2001 0 20.6238 3.4237 20.6238 7.64706Z" fill="black"/> 3 + </svg>
+3
public/icons/tag.svg
··· 1 + <svg width="21" height="21" viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 + <path fill-rule="evenodd" clip-rule="evenodd" d="M11.0086 0H10.5944L10.3015 0.292893L0.707107 9.88732L0 10.5944L0.707107 11.3015L9.19448 19.7889L9.90159 20.496L10.6087 19.7889L19.8341 10.5635L20.127 10.2706V9.85639V1V0H19.127H11.0086ZM2.82843 10.5944L11.4229 2H18.127V9.44218L9.90159 17.6676L2.82843 10.5944ZM14.6988 6.90431C15.514 6.90431 16.1749 6.24346 16.1749 5.42825C16.1749 4.61304 15.514 3.95218 14.6988 3.95218C13.8836 3.95218 13.2227 4.61304 13.2227 5.42825C13.2227 6.24346 13.8836 6.90431 14.6988 6.90431Z" fill="black"/> 3 + </svg>
+7
shared/css.go
··· 41 41 selector: rules, 42 42 }) 43 43 } 44 + 45 + func OnFocus(class templ.CSSClass, rules Declarations) templ.Component { 46 + selector := fmt.Sprintf(".%s:focus", class.ClassName()) 47 + return CSS(Selectors{ 48 + selector: rules, 49 + }) 50 + }
+36 -15
shared/tags_and_techs.go
··· 54 54 return stringsLooselyMatch(name, t.Slug, t.Name) || stringsLooselyMatch(name, t.Aliases...) 55 55 } 56 56 57 - // CalculateTimeSpent updates the time spent on the technology, using wakatime data 58 - func (t *Technology) CalculateTimeSpent() error { 59 - // In dev, don't calculate times, it just slows everything down 60 - // if IsDev() { 61 - // return nil 62 - // } 57 + func (t Technology) ExtendsTech(tech Technology) bool { 58 + for _, extendedTech := range t.Extends { 59 + if tech.ReferredToBy(extendedTech) { 60 + return true 61 + } 62 + } 63 + return false 64 + } 63 65 66 + // CalculateTimeSpent updates the time spent on the technology, using wakatime data 67 + func (t *Technology) CalculateTimeSpent(techs []Technology) (time.Duration, error) { 64 68 if len(timeSpentOnTechs) > 0 { 69 + totalDuration := time.Duration(0) 65 70 for techName, duration := range timeSpentOnTechs { 66 71 if t.ReferredToBy(techName) { 67 - t.TimeSpent = duration 68 - return nil 72 + totalDuration += duration 73 + extendors := make([]Technology, 0) 74 + for _, otherTech := range techs { 75 + if otherTech.ExtendsTech(*t) { 76 + extendors = append(extendors, otherTech) 77 + } 78 + } 79 + if len(extendors) > 0 { 80 + for _, otherTech := range extendors { 81 + timeSpentOnOtherTech, err := otherTech.CalculateTimeSpent(techs) 82 + if err != nil { 83 + return 0, fmt.Errorf("while resolving time spent of tech %s that extends %s: %w", otherTech.Name, t.Name, err) 84 + } 85 + totalDuration += timeSpentOnOtherTech 86 + } 87 + } 69 88 } 70 89 } 71 - return nil 90 + t.TimeSpent = totalDuration 91 + return totalDuration, nil 72 92 } 73 93 74 94 var stats struct { ··· 76 96 } 77 97 78 98 resp, err := wakatimeRequest("users/current/stats/all_time") 99 + if err != nil { 100 + return 0, fmt.Errorf("while fetcing user stats: %w", err) 101 + } 102 + 103 + 79 104 decoder := json.NewDecoder(resp.Body) 80 105 decoder.Decode(&stats) 81 106 if err != nil { 82 - return fmt.Errorf("while fetching user stats: %w", err) 107 + return 0, fmt.Errorf("while decoding user stats: %w", err) 83 108 } 84 109 85 110 for _, tech := range stats.Data.Languages { 86 111 timeSpentOnTechs[tech.Name] = time.Duration(tech.TotalSeconds) * time.Second 87 - if t.ReferredToBy(tech.Name) { 88 - t.TimeSpent = time.Duration(tech.TotalSeconds) * time.Second 89 - return nil 90 - } 91 112 } 92 113 93 - return nil 114 + return t.CalculateTimeSpent(techs) 94 115 } 95 116 96 117 func TagsOf(all []Tag, metadata ortfodb.WorkMetadata) []Tag {
+28 -12
shared/types.go
··· 20 20 } 21 21 22 22 type Technology struct { 23 - Slug string `yaml:"slug"` 24 - Name string `yaml:"name"` 25 - By string `yaml:"by"` 26 - Files []string `yaml:"files"` 27 - Aliases []string `yaml:"aliases"` 28 - LearnMoreURL string `yaml:"learn more at"` 29 - Description string `yaml:"description"` 23 + Slug string `yaml:"slug"` 24 + Name string `yaml:"name"` 25 + By string `yaml:"by"` 26 + Files []string `yaml:"files"` 27 + Aliases []string `yaml:"aliases"` 28 + LearnMoreURL string `yaml:"learn more at"` 29 + Description ortfodb.HTMLString `yaml:"description"` 30 + Extends []string `yaml:"extends"` 30 31 TimeSpent time.Duration 31 32 } 32 33 ··· 42 43 } 43 44 44 45 type Tag struct { 45 - Singular string `yaml:"singular"` 46 - Plural string `yaml:"plural"` 47 - Aliases []string `yaml:"aliases"` 48 - Description string `yaml:"description"` 49 - LearnMoreURL string `yaml:"learn more at"` 46 + Singular string `yaml:"singular"` 47 + Plural string `yaml:"plural"` 48 + Aliases []string `yaml:"aliases"` 49 + Description ortfodb.HTMLString `yaml:"description"` 50 + LearnMoreURL string `yaml:"learn more at"` 50 51 } 51 52 52 53 func (tag Tag) Works(db ortfodb.Database) (worksWithTag []ortfodb.AnalyzedWork) { ··· 61 62 } 62 63 63 64 type Collection struct { 65 + ID string 64 66 Title map[string]string `yaml:"title"` 65 67 Aliases []string `yaml:"aliases"` 66 68 Includes string `yaml:"includes"` ··· 82 84 } 83 85 return urlsToNames 84 86 } 87 + 88 + func (cs Collections) ThatIncludeWork(work ortfodb.AnalyzedWork, workIDs []string, tags []Tag, techs []Technology) []Collection { 89 + out := make([]Collection, 0) 90 + for _, c := range cs { 91 + if ok, err := c.Contains(work, workIDs, tags, techs); ok || err != nil { 92 + if err != nil { 93 + panic(err) 94 + } 95 + 96 + out = append(out, c) 97 + } 98 + } 99 + return out 100 + }
+7
shared/utils.go
··· 41 41 return out 42 42 } 43 43 44 + func Keys[K comparable, V any](m map[K]V) (keys []K) { 45 + for k := range m { 46 + keys = append(keys, k) 47 + } 48 + return 49 + } 50 + 44 51 func Color(color string) string { 45 52 if color == "" { 46 53 return "inherit"
+3 -3
shared/wakatime.go
··· 53 53 54 54 func TimeSpentOnProject(work ortfodb.AnalyzedWork) time.Duration { 55 55 // In dev, don't calculate times, it just slows everything down 56 - // if IsDev() { 57 - // return 0, nil 58 - // } 56 + if IsDev() { 57 + return 0 58 + } 59 59 60 60 timeSpentOnProjects.mu.Lock() 61 61 defer timeSpentOnProjects.mu.Unlock()
+19
technologies.yaml
··· 15 15 16 16 - slug: assembly 17 17 name: Assembly 18 + by: Tayasui 18 19 learn more at: https://apps.apple.com/us/app/assembly-graphic-design-art/id1024210402 19 20 description: | 20 21 An intuitive vector graphics editor for iOS ··· 22 23 - slug: coffeescript 23 24 name: CoffeeScript 24 25 files: [.coffee] 26 + extends: [javascript] 25 27 learn more at: https://coffeescript.org/ 26 28 description: | 27 29 A programming language that compiles to JavaScript. It adds syntactic sugar inspired by Ruby, Python and Haskell in an effort to enhance JavaScript's brevity and readability. Specific additional features include list comprehension and destructuring assignment ··· 51 53 - slug: djangorestframework 52 54 name: Django REST Framework 53 55 by: encode 56 + extends: [django] 54 57 autodetect: djangorestframework in pyproject.toml 55 58 learn more at: https://www.django-rest-framework.org 56 59 description: | ··· 58 61 59 62 - slug: django 60 63 name: Django 64 + extends: [python] 61 65 autodetect: django in pyproject.toml 62 66 learn more at: https://www.djangoproject.com 63 67 description: | ··· 81 85 - slug: fishshell 82 86 name: Fish Shell 83 87 files: [.fish] 88 + extends: [shell] 84 89 autodetect: shebang /usr/bin/env fish 85 90 aliases: ["fish"] 86 91 learn more at: https://fishshell.com ··· 160 165 - slug: livescript 161 166 name: LiveScript 162 167 files: [.ls] 168 + extends: [javascript] 163 169 learn more at: https://livescript.net 164 170 description: | 165 171 A language which compiles to JavaScript that adds many features to assist in functional style programming. LiveScript is an indirect descendant of CoffeeScript, with which it has much compatibility. ··· 180 186 181 187 - slug: nestjs 182 188 name: NestJS 189 + extends: [javascript] 183 190 autodetect: "@nestjs/core in package.json" 184 191 learn more at: https://nestjs.com/ 185 192 description: | ··· 195 202 - slug: nuxt 196 203 name: Nuxt 197 204 aliases: ["nuxtjs"] 205 + extends: [vue] 198 206 autodetect: nuxt in package.json 199 207 learn more at: https://nuxtjs.org 200 208 description: | ··· 204 212 name: Oclif 205 213 by: Heroku 206 214 autodetect: oclif in package.json 215 + extends: [javascript] 207 216 learn more at: https://oclif.io/ 208 217 description: | 209 218 An open Node.js CLI framework ··· 232 241 233 242 - slug: postgresql 234 243 name: PostGreSQL 244 + extends: [sql] 235 245 autodetect: postgres in docker-compose.yaml 236 246 learn more at: https://www.postgresql.org/ 237 247 description: | ··· 248 258 - slug: pug 249 259 name: Pug 250 260 files: [.pug] 261 + extends: [html] 251 262 learn more at: https://pugjs.org 252 263 description: | 253 264 A high-performance template engine heavily influenced by <a href='https://haml.info/'>Haml</a> and implemented with JavaScript for Node.js and browsers. ··· 270 281 - slug: rubyonrails 271 282 name: Ruby On Rails 272 283 autodetect: rails in Gemfile 284 + extends: [ruby] 273 285 learn more at: https://rubyonrails.org 274 286 description: | 275 287 A web-application framework that includes everything needed to create database-backed web applications according to the Model-View-Controller (MVC) pattern ··· 294 306 name: SvelteKit 295 307 by: Svelte 296 308 alias: [sapper] 309 + extends: [svelte] 297 310 autodetect: "@sveltejs/kit in package.json" 298 311 learn more at: https://kit.svelte.dev 299 312 description: | ··· 304 317 alias: [scss] 305 318 learn more at: https://sass-lang.com/ 306 319 files: [.scss, .sass] 320 + extends: [css] 307 321 description: | 308 322 “Syntactically Awesome Style Sheets”—A mature, stable, and powerful professional grade CSS extension language. 309 323 ··· 316 330 name: Stylus 317 331 learn more at: https://stylus-lang.com 318 332 files: [.styl] 333 + extends: [css] 319 334 description: | 320 335 Expressive, robust, feature-rich CSS language built for nodejs 321 336 322 337 - slug: svelte 323 338 name: Svelte 324 339 files: [.svelte] 340 + extends: [html, js, css] 325 341 learn more at: https://svelte.dev/ 326 342 description: | 327 343 A radical new approach to building user interfaces. Whereas traditional frameworks like React and Vue do the bulk of their work in the browser, Svelte shifts that work into a compile step that happens when you build your app. ··· 337 353 name: TypeScript 338 354 by: Microsoft 339 355 files: [.ts] 356 + extends: [javascript] 340 357 learn more at: https://www.typescriptlang.org/ 341 358 description: | 342 359 An open-source language which builds on JavaScript by adding static type definitions ··· 345 362 name: Vue 346 363 aliases: ["vuejs"] 347 364 files: [.vue] 365 + extends: [html, javascript, css] 348 366 learn more at: https://vuejs.org 349 367 description: | 350 368 The progressive JavaScript framework ··· 431 449 432 450 - slug: symfony 433 451 name: symfony 452 + extends: [php] 434 453 autodetect: symfony/symfony in composer.json 435 454 description: A PHP Model-View-Controller framework 436 455 learn more at: https://symfony.com