My personal website, in gleam+lustre!
0
fork

Configure Feed

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

Threw toghether some Cynthia code and a Lustre example and well so far so good I guess

MLC Bloeiman 22724571

+804
+3
.envrc
··· 1 + if nix flake show &> /dev/null; then 2 + use flake 3 + fi
+12
.gitignore
··· 1 + *.beam 2 + *.ez 3 + /build 4 + erl_crash.dump 5 + src/homepage/from_prebuild/ 6 + 7 + # Ignore direnv cache 8 + .direnv/ 9 + 10 + #Added automatically by Lustre Dev Tools 11 + /.lustre 12 + /dist
+3
.vscode/settings.json
··· 1 + { 2 + "git.enabled": false 3 + }
+24
README.md
··· 1 + # homepage 2 + 3 + [![Package Version](https://img.shields.io/hexpm/v/homepage)](https://hex.pm/packages/homepage) 4 + [![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/homepage/) 5 + 6 + ```sh 7 + gleam add homepage@1 8 + ``` 9 + ```gleam 10 + import homepage 11 + 12 + pub fn main() -> Nil { 13 + // TODO: An example of the project in use 14 + } 15 + ``` 16 + 17 + Further documentation can be found at <https://hexdocs.pm/homepage>. 18 + 19 + ## Development 20 + 21 + ```sh 22 + gleam run # Run the project 23 + gleam test # Run the tests 24 + ```
+126
dev/homepage/prepare.gleam
··· 1 + //// Most of this file was borrowed from the project that was replaced by this: 2 + //// https://forge.strawmelonjuice.com/CynthiaWebsiteEngine/ByYou/src/branch/main/by-you/src/byyou/cli.gleam 3 + 4 + import gleam/erlang/application 5 + import gleam/io 6 + import gleam/json 7 + import gleam/list 8 + import gleam/result 9 + import gleam/string 10 + import simplifile 11 + 12 + @external(javascript, "./nonexistent.js", "okay") 13 + fn assertive_priv_file_read(file: String) -> String { 14 + case 15 + result.flatten({ 16 + use path <- result.map( 17 + result.map( 18 + application.priv_directory("homepage") 19 + |> result.replace_error( 20 + "The package did not exist. This should be impossible.", 21 + ), 22 + string.append(_, "/"), 23 + ), 24 + ) 25 + simplifile.read(path <> file) 26 + |> result.replace_error("Could not read '" <> path <> file <> "'.") 27 + }) 28 + { 29 + Ok(z) -> z 30 + Error(error) -> { 31 + panic as error 32 + } 33 + } 34 + } 35 + 36 + pub fn main() { 37 + use files <- result.try(list_files()) 38 + 39 + use dir <- result.try( 40 + result.map( 41 + simplifile.is_directory("./src/homepage/from_prebuild/") 42 + |> result.replace_error( 43 + "❌\tCouldn't check for the existence of directory `src/homepage/from_prebuild/`.", 44 + ), 45 + fn(exists) { 46 + case exists { 47 + False -> { 48 + simplifile.create_directory_all("./src/homepage/from_prebuild/") 49 + |> result.replace_error( 50 + "❌\tSomething went wrong creating directory `src/homepage/from_prebuild/`.", 51 + ) 52 + |> result.replace("✅\tCreated: `src/homepage/from_prebuild/`") 53 + } 54 + True -> { 55 + Ok("🆗\tAlready exists: `src/homepage/from_prebuild/`") 56 + } 57 + } 58 + }, 59 + ) 60 + |> result.flatten, 61 + ) 62 + io.println(dir) 63 + use _ <- result.try( 64 + simplifile.write( 65 + "./src/homepage/from_prebuild/data.gleam", 66 + assertive_priv_file_read("codegen-templates/data.gleam") 67 + |> string.replace("todo as \"templated:files\"", files), 68 + ) 69 + |> result.replace_error( 70 + "❌\tSomething went wrong writing `src/homepage/from_prebuild/data.gleam`.", 71 + ), 72 + ) 73 + io.println("✅\tWrote: `src/homepage/from_prebuild/data.gleam`") 74 + |> Ok 75 + } 76 + 77 + fn list_files() { 78 + use dir <- result.try( 79 + result.map( 80 + simplifile.is_directory("./written-contents/") 81 + |> result.replace_error( 82 + "❌\tCouldn't check for the existence of directory `./written-contents/`.", 83 + ), 84 + fn(exists) { 85 + case exists { 86 + False -> { 87 + simplifile.create_directory_all("./written-contents/") 88 + |> result.replace_error( 89 + "❌\tSomething went wrong creating directory `written-contents/`.", 90 + ) 91 + |> result.replace("✅\tCreated: `written-contents/`") 92 + } 93 + True -> { 94 + Ok("🆗\tExists: `written-contents/`") 95 + } 96 + } 97 + }, 98 + ) 99 + |> result.flatten, 100 + ) 101 + io.println(dir) 102 + use files <- result.try( 103 + simplifile.get_files("./written-contents/") 104 + |> result.replace_error( 105 + "❌\tSomething went wrong reading directory `written-contents/`.", 106 + ), 107 + ) 108 + 109 + list.try_map(files, fn(file) { 110 + use contents <- result.try( 111 + simplifile.read(file) 112 + |> result.replace_error( 113 + "❌\tSomething went wrong reading file `" <> file <> "`.", 114 + ), 115 + ) 116 + Ok( 117 + "#(" 118 + <> json.to_string(json.string(file)) 119 + <> ", " 120 + <> json.to_string(json.string(contents)) 121 + <> ")", 122 + ) 123 + }) 124 + |> result.map(string.join(_, ",")) 125 + |> result.map(fn(series) { "[" <> series <> "]" }) 126 + }
+61
flake.lock
··· 1 + { 2 + "nodes": { 3 + "nixpkgs": { 4 + "locked": { 5 + "lastModified": 1772773019, 6 + "narHash": "sha256-E1bxHxNKfDoQUuvriG71+f+s/NT0qWkImXsYZNFFfCs=", 7 + "owner": "NixOS", 8 + "repo": "nixpkgs", 9 + "rev": "aca4d95fce4914b3892661bcb80b8087293536c6", 10 + "type": "github" 11 + }, 12 + "original": { 13 + "owner": "NixOS", 14 + "ref": "nixos-unstable", 15 + "repo": "nixpkgs", 16 + "type": "github" 17 + } 18 + }, 19 + "root": { 20 + "inputs": { 21 + "nixpkgs": "nixpkgs", 22 + "utils": "utils" 23 + } 24 + }, 25 + "systems": { 26 + "locked": { 27 + "lastModified": 1681028828, 28 + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 29 + "owner": "nix-systems", 30 + "repo": "default", 31 + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 32 + "type": "github" 33 + }, 34 + "original": { 35 + "owner": "nix-systems", 36 + "repo": "default", 37 + "type": "github" 38 + } 39 + }, 40 + "utils": { 41 + "inputs": { 42 + "systems": "systems" 43 + }, 44 + "locked": { 45 + "lastModified": 1731533236, 46 + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 47 + "owner": "numtide", 48 + "repo": "flake-utils", 49 + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 50 + "type": "github" 51 + }, 52 + "original": { 53 + "owner": "numtide", 54 + "repo": "flake-utils", 55 + "type": "github" 56 + } 57 + } 58 + }, 59 + "root": "root", 60 + "version": 7 61 + }
+27
flake.nix
··· 1 + { 2 + inputs = { 3 + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 4 + utils.url = "github:numtide/flake-utils"; 5 + }; 6 + 7 + outputs = { self, nixpkgs, utils }: 8 + utils.lib.eachDefaultSystem (system: 9 + let 10 + pkgs = import nixpkgs { inherit system; }; 11 + in 12 + { 13 + devShells.default = pkgs.mkShell { 14 + buildInputs = with pkgs; [ 15 + gleam 16 + erlang_28 17 + rebar3 18 + bun 19 + just 20 + ]; 21 + 22 + shellHook = '' 23 + just --list 24 + ''; 25 + }; 26 + }); 27 + }
+25
gleam.toml
··· 1 + name = "homepage" 2 + version = "1.0.0" 3 + 4 + # Fill out these fields if you intend to generate HTML documentation or publish 5 + # your project to the Hex package manager. 6 + # 7 + # description = "" 8 + # licences = ["Apache-2.0"] 9 + # repository = { type = "github", user = "", repo = "" } 10 + # links = [{ title = "Website", href = "" }] 11 + # 12 + # For a full reference of all the available options, you can have a look at 13 + # https://gleam.run/writing-gleam/gleam-toml/. 14 + 15 + [dependencies] 16 + gleam_stdlib = ">= 0.44.0 and < 2.0.0" 17 + lustre = ">= 5.6.0 and < 6.0.0" 18 + modem = ">= 2.1.2 and < 3.0.0" 19 + jot = ">= 10.1.1 and < 11.0.0" 20 + 21 + [dev-dependencies] 22 + lustre_dev_tools = ">= 2.3.4 and < 3.0.0" 23 + simplifile = ">= 2.3.2 and < 3.0.0" 24 + gleam_erlang = ">= 1.3.0 and < 2.0.0" 25 + gleam_json = ">= 3.1.0 and < 4.0.0"
+11
justfile
··· 1 + # https://just.systems 2 + 3 + [private] 4 + default: 5 + @just --list 6 + 7 + prepare: 8 + gleam run -m homepage/prepare 9 + 10 + build: prepare 11 + gleam run -m lustre/dev build
+55
manifest.toml
··· 1 + # This file was generated by Gleam 2 + # You typically do not need to edit this file 3 + 4 + packages = [ 5 + { name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" }, 6 + { name = "booklet", version = "1.1.0", build_tools = ["gleam"], requirements = [], otp_app = "booklet", source = "hex", outer_checksum = "08E0FDB78DC4D8A5D3C80295B021505C7D2A2E7B6C6D5EAB7286C36F4A53C851" }, 7 + { name = "directories", version = "1.2.0", build_tools = ["gleam"], requirements = ["envoy", "gleam_stdlib", "platform", "simplifile"], otp_app = "directories", source = "hex", outer_checksum = "D13090CFCDF6759B87217E8DDD73A75903A700148A82C1D33799F333E249BF9E" }, 8 + { name = "envoy", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "envoy", source = "hex", outer_checksum = "850DA9D29D2E5987735872A2B5C81035146D7FE19EFC486129E44440D03FD832" }, 9 + { name = "exception", version = "2.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "exception", source = "hex", outer_checksum = "329D269D5C2A314F7364BD2711372B6F2C58FA6F39981572E5CA68624D291F8C" }, 10 + { name = "filepath", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "B06A9AF0BF10E51401D64B98E4B627F1D2E48C154967DA7AF4D0914780A6D40A" }, 11 + { name = "gleam_community_ansi", version = "1.4.4", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_regexp", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "1B3AEA6074AB34D5F0674744F36DDC7290303A03295507E2DEC61EDD6F5777FE" }, 12 + { name = "gleam_community_colour", version = "2.0.4", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "6DB4665555D7D2B27F0EA32EF47E8BEBC4303821765F9C73D483F38EE24894F0" }, 13 + { name = "gleam_crypto", version = "1.5.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_crypto", source = "hex", outer_checksum = "50774BAFFF1144E7872814C566C5D653D83A3EBF23ACC3156B757A1B6819086E" }, 14 + { name = "gleam_erlang", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "1124AD3AA21143E5AF0FC5CF3D9529F6DB8CA03E43A55711B60B6B7B3874375C" }, 15 + { name = "gleam_http", version = "4.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "82EA6A717C842456188C190AFB372665EA56CE13D8559BF3B1DD9E40F619EE0C" }, 16 + { name = "gleam_httpc", version = "5.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gleam_httpc", source = "hex", outer_checksum = "C545172618D07811494E97AAA4A0FB34DA6F6D0061FDC8041C2F8E3BE2B2E48F" }, 17 + { name = "gleam_json", version = "3.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "44FDAA8847BE8FC48CA7A1C089706BD54BADCC4C45B237A992EDDF9F2CDB2836" }, 18 + { name = "gleam_otp", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "BA6A294E295E428EC1562DC1C11EA7530DCB981E8359134BEABC8493B7B2258E" }, 19 + { name = "gleam_regexp", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "9C215C6CA84A5B35BB934A9B61A9A306EC743153BE2B0425A0D032E477B062A9" }, 20 + { name = "gleam_stdlib", version = "0.70.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "86949BF5D1F0E4AC0AB5B06F235D8A5CC11A2DFC33BF22F752156ED61CA7D0FF" }, 21 + { name = "gleam_time", version = "1.7.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_time", source = "hex", outer_checksum = "56DB0EF9433826D3B99DB0B4AF7A2BFED13D09755EC64B1DAAB46F804A9AD47D" }, 22 + { name = "gleam_yielder", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_yielder", source = "hex", outer_checksum = "8E4E4ECFA7982859F430C57F549200C7749823C106759F4A19A78AEA6687717A" }, 23 + { name = "glint", version = "1.2.1", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_community_colour", "gleam_stdlib", "snag"], otp_app = "glint", source = "hex", outer_checksum = "2214C7CEFDE457CEE62140C3D4899B964E05236DA74E4243DFADF4AF29C382BB" }, 24 + { name = "glisten", version = "8.0.3", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib", "logging", "telemetry"], otp_app = "glisten", source = "hex", outer_checksum = "86B838196592D9EBDE7A1D2369AE3A51E568F7DD2D168706C463C42D17B95312" }, 25 + { name = "gramps", version = "6.0.0", build_tools = ["gleam"], requirements = ["gleam_crypto", "gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gramps", source = "hex", outer_checksum = "8B7195978FBFD30B43DF791A8A272041B81E45D245314D7A41FC57237AA882A0" }, 26 + { name = "group_registry", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib"], otp_app = "group_registry", source = "hex", outer_checksum = "BC798A53D6F2406DB94E27CB45C57052CB56B32ACF7CC16EA20F6BAEC7E36B90" }, 27 + { name = "houdini", version = "1.2.0", build_tools = ["gleam"], requirements = [], otp_app = "houdini", source = "hex", outer_checksum = "5DB1053F1AF828049C2B206D4403C18970ABEF5C18671CA3C2D2ED0DD64F6385" }, 28 + { name = "hpack_erl", version = "0.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "hpack", source = "hex", outer_checksum = "D6137D7079169D8C485C6962DFE261AF5B9EF60FBC557344511C1E65E3D95FB0" }, 29 + { name = "jot", version = "10.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "houdini", "splitter"], otp_app = "jot", source = "hex", outer_checksum = "395907A191D8558C7FA39653157D77674467D15B07794A1543A6CF24F6A10278" }, 30 + { name = "justin", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "justin", source = "hex", outer_checksum = "7FA0C6DB78640C6DC5FBFD59BF3456009F3F8B485BF6825E97E1EB44E9A1E2CD" }, 31 + { name = "logging", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "logging", source = "hex", outer_checksum = "1098FBF10B54B44C2C7FDF0B01C1253CAFACDACABEFB4B0D027803246753E06D" }, 32 + { name = "lustre", version = "5.6.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_json", "gleam_otp", "gleam_stdlib", "houdini"], otp_app = "lustre", source = "hex", outer_checksum = "EE558CD4DB9F09FCC16417ADF0183A3C2DAC3E4B21ED3AC0CAE859792AB810CA" }, 33 + { name = "lustre_dev_tools", version = "2.3.4", build_tools = ["gleam"], requirements = ["argv", "booklet", "filepath", "gleam_community_ansi", "gleam_crypto", "gleam_erlang", "gleam_http", "gleam_httpc", "gleam_json", "gleam_otp", "gleam_regexp", "gleam_stdlib", "glint", "group_registry", "justin", "lustre", "mist", "polly", "simplifile", "tom", "wisp"], otp_app = "lustre_dev_tools", source = "hex", outer_checksum = "5D5C479E465A3EA018205EFCD2F2FE430A9B9783CAC21670E6CB25703069407D" }, 34 + { name = "marceau", version = "1.3.0", build_tools = ["gleam"], requirements = [], otp_app = "marceau", source = "hex", outer_checksum = "2D1C27504BEF45005F5DFB18591F8610FB4BFA91744878210BDC464412EC44E9" }, 35 + { name = "mist", version = "5.0.4", build_tools = ["gleam"], requirements = ["exception", "gleam_erlang", "gleam_http", "gleam_otp", "gleam_stdlib", "gleam_yielder", "glisten", "gramps", "hpack_erl", "logging"], otp_app = "mist", source = "hex", outer_checksum = "7CED4B2D81FD547ADB093D97B9928B9419A7F58B8562A30A6CC17A252B31AD05" }, 36 + { name = "modem", version = "2.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib", "lustre"], otp_app = "modem", source = "hex", outer_checksum = "3F9682EBCBF4D26045F1038A7507E8C7967E49D43F9CA6BA68EF0C971B195A7F" }, 37 + { name = "platform", version = "1.0.0", build_tools = ["gleam"], requirements = [], otp_app = "platform", source = "hex", outer_checksum = "8339420A95AD89AAC0F82F4C3DB8DD401041742D6C3F46132A8739F6AEB75391" }, 38 + { name = "polly", version = "3.1.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_erlang", "gleam_otp", "gleam_stdlib", "simplifile"], otp_app = "polly", source = "hex", outer_checksum = "51FB565D81FF6212FDF3306D44419601F2A7C4EDD1F00FC9DA5C376A00AED4FE" }, 39 + { name = "simplifile", version = "2.3.2", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "E049B4DACD4D206D87843BCF4C775A50AE0F50A52031A2FFB40C9ED07D6EC70A" }, 40 + { name = "snag", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "snag", source = "hex", outer_checksum = "274F41D6C3ECF99F7686FDCE54183333E41D2C1CA5A3A673F9A8B2C7A4401077" }, 41 + { name = "splitter", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "splitter", source = "hex", outer_checksum = "3DFD6B6C49E61EDAF6F7B27A42054A17CFF6CA2135FF553D0CB61C234D281DD0" }, 42 + { name = "telemetry", version = "1.4.0", build_tools = ["rebar3"], requirements = [], otp_app = "telemetry", source = "hex", outer_checksum = "D1FF426F988AC1092F9D684D34D08E51042A70567C16BE793FBC8F399FD2E77D" }, 43 + { name = "tom", version = "2.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "gleam_time"], otp_app = "tom", source = "hex", outer_checksum = "90791DA4AACE637E30081FE77049B8DB850FBC8CACC31515376BCC4E59BE1DD2" }, 44 + { name = "wisp", version = "2.2.0", build_tools = ["gleam"], requirements = ["directories", "exception", "filepath", "gleam_crypto", "gleam_erlang", "gleam_http", "gleam_json", "gleam_stdlib", "houdini", "logging", "marceau", "mist", "simplifile"], otp_app = "wisp", source = "hex", outer_checksum = "655163D4DE19E3DD4AC75813A991BFD5523CB4FF2FC5F9F58FD6FB39D5D1806D" }, 45 + ] 46 + 47 + [requirements] 48 + gleam_erlang = { version = ">= 1.3.0 and < 2.0.0" } 49 + gleam_json = { version = ">= 3.1.0 and < 4.0.0" } 50 + gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" } 51 + jot = { version = ">= 10.1.1 and < 11.0.0" } 52 + lustre = { version = ">= 5.6.0 and < 6.0.0" } 53 + lustre_dev_tools = { version = ">= 2.3.4 and < 3.0.0" } 54 + modem = { version = ">= 2.1.2 and < 3.0.0" } 55 + simplifile = { version = ">= 2.3.2 and < 3.0.0" }
+5
priv/codegen-templates/data.gleam
··· 1 + import gleam/dict 2 + 3 + pub fn files() { 4 + dict.from_list(todo as "templated:files") 5 + }
+315
src/homepage.gleam
··· 1 + // Post data 2 + 3 + fn posts() { 4 + [ 5 + Post( 6 + id: 0, 7 + title: "Test", 8 + summary: "Testing", 9 + body: File(Plain, "./written-contents/test.dj"), 10 + aliases: [], 11 + ), 12 + ] 13 + } 14 + 15 + // The site itself 16 + 17 + import gleam/dict.{type Dict} 18 + import gleam/int 19 + import gleam/list 20 + import gleam/pair 21 + import gleam/uri.{type Uri} 22 + import lustre 23 + import lustre/attribute.{type Attribute} 24 + import lustre/effect.{type Effect} 25 + import lustre/element.{type Element} 26 + import lustre/element/html 27 + import modem 28 + 29 + pub fn main() { 30 + let app = lustre.application(init, update, view) 31 + let assert Ok(_) = lustre.start(app, "#app", Nil) 32 + 33 + Nil 34 + } 35 + 36 + // Some types for your consideration 37 + 38 + type Model { 39 + Model( 40 + posts: Dict(Int, NormalizedPost), 41 + aliases: Dict(String, Int), 42 + route: Route, 43 + ) 44 + } 45 + 46 + type Post { 47 + Post( 48 + id: Int, 49 + title: String, 50 + summary: String, 51 + body: Body, 52 + aliases: List(String), 53 + ) 54 + } 55 + 56 + type NormalizedPost { 57 + NormalizedPost(id: Int, title: String, summary: String, body: Element(Msg)) 58 + } 59 + 60 + type Body { 61 + Lustre(Element(Msg)) 62 + File(markup: Markup, path: String) 63 + String(markup: Markup, string: String) 64 + } 65 + 66 + pub type Markup { 67 + Djot 68 + Plain 69 + HTML 70 + } 71 + 72 + type Msg { 73 + UserNavigatedTo(route: Route) 74 + } 75 + 76 + type Route { 77 + Index 78 + Posts 79 + PostById(id: Int) 80 + About 81 + NotFound(uri: Uri) 82 + } 83 + 84 + import homepage/djotparse 85 + import homepage/from_prebuild/data 86 + 87 + fn post_normalize(takes: Post) -> NormalizedPost { 88 + let Post(id:, title:, summary:, body: _, aliases: _) = takes 89 + NormalizedPost(id:, title:, summary:, body: post_body_normalize(takes)) 90 + } 91 + 92 + fn post_body_normalize(takes: Post) { 93 + case takes.body { 94 + Lustre(a) -> a 95 + File(markup:, path:) -> { 96 + let assert Ok(inner) = data.files() |> dict.get(path) 97 + as "File does not exist in data." 98 + post_body_normalize(Post(..takes, body: String(markup:, string: inner))) 99 + } 100 + // The actual thing 101 + String(Djot, string:) -> 102 + djotparse.djot_to_html(string) 103 + |> element.unsafe_raw_html("", "div", [], _) 104 + String(markup: Plain, string:) -> html.pre([], [element.text(string)]) 105 + String(markup: HTML, string:) -> 106 + element.unsafe_raw_html("", "div", [], string) 107 + } 108 + } 109 + 110 + fn parse_route(uri: Uri) -> Route { 111 + case uri.path_segments(uri.path) { 112 + [] | [""] -> Index 113 + 114 + ["posts"] -> Posts 115 + 116 + ["post", post_id] -> 117 + case int.parse(post_id) { 118 + Ok(post_id) -> PostById(id: post_id) 119 + Error(_) -> NotFound(uri:) 120 + } 121 + 122 + ["about"] -> About 123 + 124 + _ -> NotFound(uri:) 125 + } 126 + } 127 + 128 + fn href(route: Route) -> Attribute(msg) { 129 + let url = case route { 130 + Index -> "/" 131 + About -> "/about" 132 + Posts -> "/posts" 133 + PostById(post_id) -> "/post/" <> int.to_string(post_id) 134 + NotFound(_) -> "/404" 135 + } 136 + 137 + attribute.href(url) 138 + } 139 + 140 + fn init(_) -> #(Model, Effect(Msg)) { 141 + let route = case modem.initial_uri() { 142 + Ok(uri) -> parse_route(uri) 143 + Error(_) -> Index 144 + } 145 + 146 + let aliases = 147 + list.map(posts(), fn(post) { 148 + list.map(post.aliases, fn(alias) { #(post.id, alias) }) 149 + }) 150 + |> list.flatten 151 + |> list.map(pair.swap) 152 + |> dict.from_list 153 + 154 + let posts = 155 + posts() 156 + |> list.map(fn(post) { #(post.id, post |> post_normalize) }) 157 + |> dict.from_list 158 + 159 + let model = Model(route:, posts:, aliases:) 160 + 161 + let effect = 162 + modem.init(fn(uri) { 163 + uri 164 + |> parse_route 165 + |> UserNavigatedTo 166 + }) 167 + 168 + #(model, effect) 169 + } 170 + 171 + fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { 172 + case msg { 173 + UserNavigatedTo(route:) -> #(Model(..model, route:), effect.none()) 174 + } 175 + } 176 + 177 + fn view(model: Model) -> Element(Msg) { 178 + html.div([attribute.class("mx-auto max-w-2xl px-32")], [ 179 + html.nav([attribute.class("flex justify-between items-center my-16")], [ 180 + html.h1([attribute.class("text-purple-600 font-medium text-xl")], [ 181 + html.a([href(Index)], [html.text("My little Blog")]), 182 + ]), 183 + html.ul([attribute.class("flex space-x-8")], [ 184 + view_header_link(current: model.route, to: Posts, label: "Posts"), 185 + view_header_link(current: model.route, to: About, label: "About"), 186 + ]), 187 + ]), 188 + html.main([attribute.class("my-16")], { 189 + case model.route { 190 + Index -> view_index() 191 + Posts -> view_posts(model) 192 + PostById(post_id) -> view_post(model, post_id) 193 + About -> view_about() 194 + NotFound(_) -> view_not_found() 195 + } 196 + }), 197 + ]) 198 + } 199 + 200 + fn view_header_link( 201 + to target: Route, 202 + current current: Route, 203 + label text: String, 204 + ) -> Element(msg) { 205 + let is_active = case current, target { 206 + PostById(_), Posts -> True 207 + _, _ -> current == target 208 + } 209 + 210 + html.li( 211 + [ 212 + attribute.classes([ 213 + #("border-transparent border-b-2 hover:border-purple-600", True), 214 + #("text-purple-600", is_active), 215 + ]), 216 + ], 217 + [html.a([href(target)], [html.text(text)])], 218 + ) 219 + } 220 + 221 + fn view_index() -> List(Element(msg)) { 222 + [ 223 + title("Hello, Joe"), 224 + leading( 225 + "Or whoever you may be! This is were I will share random ramblings 226 + and thoughts about life.", 227 + ), 228 + html.p([attribute.class("mt-14")], [ 229 + html.text("There is not much going on at the moment, but you can still "), 230 + link(Posts, "read my ramblings ->"), 231 + ]), 232 + paragraph("If you like <3"), 233 + ] 234 + } 235 + 236 + fn view_posts(model: Model) -> List(Element(msg)) { 237 + let posts = 238 + model.posts 239 + |> dict.values 240 + |> list.sort(fn(a, b) { int.compare(a.id, b.id) }) 241 + |> list.map(fn(post) { 242 + html.article([attribute.class("mt-14")], [ 243 + html.h3([attribute.class("text-xl text-purple-600 font-light")], [ 244 + html.a([attribute.class("hover:underline"), href(PostById(post.id))], [ 245 + html.text(post.title), 246 + ]), 247 + ]), 248 + html.p([attribute.class("mt-1")], [html.text(post.summary)]), 249 + ]) 250 + }) 251 + 252 + [title("Posts"), ..posts] 253 + } 254 + 255 + fn view_post(model: Model, post_id: Int) -> List(Element(Msg)) { 256 + case dict.get(model.posts, post_id) { 257 + Error(_) -> view_not_found() 258 + Ok(post) -> [ 259 + html.article([], [ 260 + title(post.title), 261 + leading(post.summary), 262 + post.body, 263 + ]), 264 + html.p([attribute.class("mt-14")], [link(Posts, "<- Go back?")]), 265 + ] 266 + } 267 + } 268 + 269 + fn view_about() -> List(Element(msg)) { 270 + [ 271 + title("Me"), 272 + paragraph( 273 + "I document the odd occurrences that catch my attention and rewrite my own 274 + narrative along the way. I'm fine being referred to with pronouns.", 275 + ), 276 + paragraph( 277 + "If you enjoy these glimpses into my mind, feel free to come back 278 + semi-regularly. But not too regularly, you creep.", 279 + ), 280 + ] 281 + } 282 + 283 + fn view_not_found() -> List(Element(msg)) { 284 + [ 285 + title("Not found"), 286 + paragraph( 287 + "You glimpse into the void and see -- nothing? 288 + Well that was somewhat expected.", 289 + ), 290 + ] 291 + } 292 + 293 + fn title(title: String) -> Element(msg) { 294 + html.h2([attribute.class("text-3xl text-purple-800 font-light")], [ 295 + html.text(title), 296 + ]) 297 + } 298 + 299 + fn leading(text: String) -> Element(msg) { 300 + html.p([attribute.class("mt-8 text-lg")], [html.text(text)]) 301 + } 302 + 303 + fn paragraph(text: String) -> Element(msg) { 304 + html.p([attribute.class("mt-14")], [html.text(text)]) 305 + } 306 + 307 + fn link(target: Route, title: String) -> Element(msg) { 308 + html.a( 309 + [ 310 + href(target), 311 + attribute.class("text-purple-600 hover:underline cursor-pointer"), 312 + ], 313 + [html.text(title)], 314 + ) 315 + }
+136
src/homepage/djotparse.gleam
··· 1 + //// Stolen from my famous: 2 + //// https://forge.strawmelonjuice.com/CynthiaWebsiteEngine/Mini/src/branch/2.0.0/cynthia_websites_mini_server/src/cynthia_websites_mini_server/utils/djotparse.gleam 3 + 4 + import gleam/list 5 + import gleam/string 6 + import jot 7 + 8 + pub fn djot_to_html(djot djot: String) -> String { 9 + djot 10 + |> preprocess_tables 11 + |> jot.to_html 12 + |> string.replace("<a ", "<a class=\"text-info underline\"") 13 + } 14 + 15 + pub fn preprocess_tables(djot: String) -> String { 16 + let lines = string.split(djot, on: "\n") 17 + process_lines(lines, [], False, False, False) 18 + } 19 + 20 + fn process_lines( 21 + lines: List(String), 22 + acc: List(String), 23 + in_table: Bool, 24 + next_is_header: Bool, 25 + tbody_opened: Bool, 26 + ) -> String { 27 + case lines { 28 + [] -> string.join(list.reverse(acc), "\n") 29 + [line, ..rest] -> { 30 + let is_table_line = string.starts_with(string.trim(line), "|") 31 + let is_header = case 32 + line 33 + |> string.trim 34 + |> string.split("|") 35 + |> string.concat 36 + |> string.trim 37 + |> string.replace("-", "") 38 + |> string.trim 39 + { 40 + "" -> { 41 + // Next line has a |----| structure. 42 + True 43 + } 44 + _ -> False 45 + } 46 + let tstart = 47 + "<table class=\"table table-zebra w-full my-4 border border-neutral-content\">" 48 + case is_header, in_table, is_table_line { 49 + // Header is above 50 + True, True, True -> { 51 + process_lines(rest, ["<tbody>", ..acc], True, True, True) 52 + } 53 + // Table starts 54 + _, False, True -> { 55 + process_lines( 56 + rest, 57 + [ 58 + "\n``` =html\n" 59 + <> tstart 60 + <> "\n" 61 + <> line_to_row(line, next_is_header), 62 + ..acc 63 + ], 64 + True, 65 + is_header, 66 + tbody_opened, 67 + ) 68 + } 69 + 70 + // Table continues 71 + False, True, True -> 72 + process_lines( 73 + rest, 74 + [line_to_row(line, next_is_header), ..acc], 75 + True, 76 + False, 77 + tbody_opened, 78 + ) 79 + 80 + // Table ends 81 + _, True, False -> { 82 + let acc = case tbody_opened { 83 + // No header was found, paste a <tbody> right after the <table> tag. 84 + False -> 85 + list.map(acc, string.replace(_, tstart, tstart <> "<tbody>")) 86 + True -> acc 87 + } 88 + process_lines( 89 + rest, 90 + [line, "</tbody></table>\n```\n", ..acc], 91 + False, 92 + False, 93 + True, 94 + ) 95 + } 96 + 97 + // Normal text 98 + _, False, False -> 99 + process_lines(rest, [line, ..acc], False, False, False) 100 + } 101 + } 102 + } 103 + } 104 + 105 + fn line_to_row(line: String, as_header: Bool) -> String { 106 + let cells = 107 + line 108 + |> string.trim 109 + |> string.split("|") 110 + 111 + case as_header { 112 + True -> 113 + "<thead class=\"bg-neutral text-neutral-content\"><tr>" 114 + <> { 115 + list.map(cells, fn(c) { 116 + " <th class=\"px-4 py-2 text-left font-bold\">" 117 + <> string.trim(c) |> jot.to_html 118 + <> "</th>" 119 + }) 120 + |> string.join("\n") 121 + } 122 + <> "</tr></thead>" 123 + 124 + False -> 125 + "<tr>" 126 + <> { 127 + list.map(cells, fn(c) { 128 + " <td class=\"px-4 py-2 border-t border-neutral-content\">" 129 + <> string.trim(c) |> jot.to_html 130 + <> "</td>" 131 + }) 132 + |> string.join("\n") 133 + } 134 + <> "</tr>" 135 + } 136 + }
+1
written-contents/test.dj
··· 1 + hoi "testing", ik probeer