Compare commits

...

3 Commits

Author SHA1 Message Date
Spencer Brower 4fa7227b94 feat: Emoji shorthand. 2026-07-11 19:37:36 -04:00
Spencer Brower 241068b487 feat: Added alerts. 2026-07-11 19:18:56 -04:00
Spencer Brower 88eea07fec feat: Added robots.txt 2026-07-11 10:46:36 -04:00
5 changed files with 556 additions and 6 deletions
+3 -4
View File
@@ -3,15 +3,14 @@
- Look at the source for 3 template languages that support mustache syntax, - Look at the source for 3 template languages that support mustache syntax,
and see how they implement the tempaltes, then pick the best one. One of them and see how they implement the tempaltes, then pick the best one. One of them
should be Hugo / go. should be Hugo / go.
- [ ] robots.txt?
- [ ] Evaluate Tufte CSS — borrow sidenote CSS or replace TailwindCSS entirely - [ ] Evaluate Tufte CSS — borrow sidenote CSS or replace TailwindCSS entirely
- Option B: Steal Tufte's sidenote/margin-note CSS (adapt for dark theme), keep TailwindCSS - Option B: Steal Tufte's sidenote/margin-note CSS (adapt for dark theme), keep TailwindCSS
- Option C: Full Tufte CSS — drop TailwindCSS, no build step, semantic HTML, customize for dark theme + Roboto - Option C: Full Tufte CSS — drop TailwindCSS, no build step, semantic HTML, customize for dark theme + Roboto
- Our sidenote HTML pattern already matches Tufte's exactly - Our sidenote HTML pattern already matches Tufte's exactly
- [ ] GitHub alerts (`> [!CAUTION]` → styled blockquote) — 2 posts
- [ ] Block attributes on code fences (`{ #ex-1 }`) — hello-world.md - [ ] Block attributes on code fences (`{ #ex-1 }`) — hello-world.md
- [ ] Emoji shortcodes (`:shrug:` etc.) — 2 instances - [x] Emoji shortcodes (`:shrug:` etc.) — 2 instances
- [ ] Backslash in shrug not visible.
- [ ] include-code shortcode (`{{< include-code ... >}}`) — i-ported-fd-to-odin - [ ] include-code shortcode (`{{< include-code ... >}}`) — i-ported-fd-to-odin
- [ ] Nix build integration — main flake runs thor + tailwindcss instead of Hugo - [x] Nix build integration — main flake runs thor + tailwindcss instead of Hugo
- [ ] Copy-to-clipboard button on code blocks (was in Hugo's custom-head.html) - [ ] Copy-to-clipboard button on code blocks (was in Hugo's custom-head.html)
- [ ] OpenGraph meta tags - [ ] OpenGraph meta tags
+108
View File
@@ -0,0 +1,108 @@
#+feature dynamic-literals
package main
import "core:fmt"
import "core:strings"
ALERT_STYLES: map[string]string = {
"note" = "border-l-blue-500 bg-blue-950/30 text-slate-300",
"tip" = "border-l-green-500 bg-green-950/30 text-slate-300",
"important" = "border-l-purple-500 bg-purple-950/30 text-slate-300",
"warning" = "border-l-yellow-500 bg-yellow-950/30 text-slate-300",
"caution" = "border-l-red-500 bg-red-950/30 text-slate-300",
}
ALERT_EMOJIS: map[string]string = {
"note" = "\xe2\x84\xb9\xef\xb8\x8f",
"tip" = "\xf0\x9f\x92\xa1",
"important" = "\xe2\x9d\x97",
"warning" = "\xe2\x9a\xa0\xef\xb8\x8f",
"caution" = "\xe2\x9d\x97",
}
inject_alerts :: proc(html: string) -> string {
parts: [dynamic]string
defer delete(parts)
remaining := html
for {
bq_start := strings.index(remaining, "<blockquote>")
if bq_start < 0 {
append(&parts, remaining)
break
}
bq_close := strings.index(remaining, "</blockquote>")
if bq_close < 0 {
append(&parts, remaining)
break
}
bq_end := bq_close + len("</blockquote>")
bq := remaining[bq_start:bq_end]
// Append text before this blockquote
append(&parts, remaining[:bq_start])
// Transform if it's an alert, otherwise keep as-is
append(&parts, transform_alert(bq))
remaining = remaining[bq_end:]
}
return strings.join(parts[:], "")
}
transform_alert :: proc(bq: string) -> string {
// Skip <blockquote> tag and whitespace
pos := len("<blockquote>")
for pos < len(bq) && (bq[pos] == '\n' || bq[pos] == '\r' || bq[pos] == ' ' || bq[pos] == '\t') {
pos += 1
}
// Check for <p>[!
if pos + 4 >= len(bq) || bq[pos] != '<' || bq[pos + 1] != 'p' || bq[pos + 2] != '>' ||
bq[pos + 3] != '[' || bq[pos + 4] != '!' {
return bq
}
// Extract alert type until ]
close := strings.index(bq[pos + 5:], "]")
if close < 0 {
return bq
}
type_raw := bq[pos + 5 : pos + 5 + close]
type_lower := strings.to_lower(type_raw)
style, has_style := ALERT_STYLES[type_lower]
emoji, has_emoji := ALERT_EMOJIS[type_lower]
if !has_style || !has_emoji {
return bq
}
// Position after ]
after_type := pos + 5 + close + 1
// Skip optional + or -
content_start := after_type
if content_start < len(bq) && (bq[content_start] == '+' || bq[content_start] == '-') {
content_start += 1
}
// Skip space after marker
if content_start < len(bq) && bq[content_start] == ' ' {
content_start += 1
}
// Rebuild: styled blockquote + bold title paragraph + rest
rest := bq[content_start:]
return fmt.aprintf(
`<blockquote class="alert %s rounded-r py-2">
<p class="font-bold mb-1">%s %s`,
style,
emoji,
rest,
)
}
+3 -2
View File
@@ -173,9 +173,10 @@ load_page :: proc(
if strings.has_suffix(file_path, ".html") { if strings.has_suffix(file_path, ".html") {
page.body_html = strings.clone(body) page.body_html = strings.clone(body)
} else { } else {
clean_body, defs := strip_definitions(body) expanded := expand_emoji(body)
clean_body, defs := strip_definitions(expanded)
html := cm.markdown_to_html_from_string(clean_body, {.Unsafe}) html := cm.markdown_to_html_from_string(clean_body, {.Unsafe})
page.body_html = inject_sidenotes(html, defs) page.body_html = inject_alerts(inject_sidenotes(html, defs))
} }
switch page_type { switch page_type {
+438
View File
@@ -0,0 +1,438 @@
#+feature dynamic-literals
package main
import "core:strings"
EMOJIS: map[string]string = {
// People
"smile" = "\xf0\x9f\x98\x84",
"laughing" = "\xf0\x9f\x98\x86",
"blush" = "\xf0\x9f\x98\x8a",
"smiley" = "\xf0\x9f\x98\x83",
"wink" = "\xf0\x9f\x98\x89",
"joy" = "\xf0\x9f\xa4\xa3",
"rofl" = "\xf0\x9f\xa4\xa3",
"relaxed" = "\xe2\x98\xba\xef\xb8\x8f",
"thinking" = "\xf0\x9f\xa4\x94",
"neutral_face" = "\xf0\x9f\x98\x90",
"expressionless" = "\xf0\x9f\x98\x91",
"no_mouth" = "\xf0\x9f\x98\xb6",
"rolling_eyes" = "\xf0\x9f\x99\x84",
"smirk" = "\xf0\x9f\x98\x8f",
"persevere" = "\xf0\x9f\x98\xa3",
"disappointed_relieved" = "\xf0\x9f\x98\xa5",
"open_mouth" = "\xf0\x9f\x98\xae",
"zipper_mouth_face" = "\xf0\x9f\xa4\x90",
"hushed" = "\xf0\x9f\x98\xaf",
"sleepy" = "\xf0\x9f\x98\xaa",
"tired_face" = "\xf0\x9f\x98\xab",
"sleeping" = "\xf0\x9f\x98\xb4",
"relieved" = "\xf0\x9f\x98\x8c",
"stuck_out_tongue" = "\xf0\x9f\x98\x9b",
"stuck_out_tongue_winking_eye" = "\xf0\x9f\x98\x9c",
"stuck_out_tongue_closed_eyes" = "\xf0\x9f\x98\x9d",
"drooling_face" = "\xf0\x9f\xa4\xa4",
"unamused" = "\xf0\x9f\x98\x92",
"sweat" = "\xf0\x9f\x98\x85",
"pensive" = "\xf0\x9f\x98\x94",
"confused" = "\xf0\x9f\x98\x95",
"upside_down_face" = "\xf0\x9f\x99\x83",
"money_mouth_face" = "\xf0\x9f\xa4\x91",
"astonished" = "\xf0\x9f\x98\xb2",
"white_frowning_face" = "\xe2\x98\xb9\xef\xb8\x8f",
"slightly_sad_face" = "\xe2\x98\xb9\xef\xb8\x8f",
"confounded" = "\xf0\x9f\x98\x96",
"disappointed" = "\xf0\x9f\x98\x9e",
"worried" = "\xf0\x9f\x98\x9f",
"triumph" = "\xf0\x9f\x98\xa4",
"cry" = "\xf0\x9f\x98\xa2",
"sob" = "\xf0\x9f\x98\xad",
"frowning" = "\xf0\x9f\x98\xa6",
"frowning_face" = "\xf0\x9f\x99\x81",
"anguished" = "\xf0\x9f\x98\xa7",
"fearful" = "\xf0\x9f\x98\xa8",
"weary" = "\xf0\x9f\x98\xa9",
"grimacing" = "\xf0\x9f\x98\xac",
"cold_sweat" = "\xf0\x9f\x98\xb0",
"scream" = "\xf0\x9f\x98\xb1",
"flushed" = "\xf0\x9f\x98\xb3",
"dizzy_face" = "\xf0\x9f\x98\xb5",
"rage" = "\xf0\x9f\x98\xa1",
"angry" = "\xf0\x9f\x98\xa0",
"innocent" = "\xf0\x9f\x98\x87",
"cowboy_hat_face" = "\xf0\x9f\xa4\xa0",
"clown_face" = "\xf0\x9f\xa4\xa1",
"mask" = "\xf0\x9f\x98\xb7",
"thermometer_face" = "\xf0\x9f\xa4\x92",
"head_bandage" = "\xf0\x9f\xa4\x95",
"nauseated_face" = "\xf0\x9f\xa4\xa2",
"sneezing_face" = "\xf0\x9f\xa4\xa7",
"smiling_imp" = "\xf0\x9f\x98\x88",
"imp" = "\xf0\x9f\x91\xbf",
"shrug" = "\xc2\xaf\\_(\xe3\x83\x84)_/\xc2\xaf",
"facepalm" = "\xf0\x9f\xa4\xa6",
"facepunch" = "\xf0\x9f\x91\x8a",
"wave" = "\xf0\x9f\x91\x8b",
"ok_hand" = "\xf0\x9f\x91\x8c",
"thumbsup" = "\xf0\x9f\x91\x8d",
"thumbsdown" = "\xf0\x9f\x91\x8e",
"clap" = "\xf0\x9f\x91\x8f",
"pray" = "\xf0\x9f\x99\x8f",
"point_up" = "\xe2\x98\x9d\xef\xb8\x8f",
"point_down" = "\xf0\x9f\x91\x87",
"point_left" = "\xf0\x9f\x91\x88",
"point_right" = "\xf0\x9f\x89\x89",
"v" = "\xe2\x9c\x8c\xef\xb8\x8f",
"raised_hands" = "\xf0\x9f\x99\x8c",
"muscle" = "\xf0\x9f\x92\xaa",
"fist" = "\xe2\x9c\x8a",
"hand" = "\xe2\x9c\x8b",
// Hearts & symbols
"heart" = "\xe2\x9d\xa4\xef\xb8\x8f",
"orange_heart" = "\xf0\x9f\xa7\xa1",
"yellow_heart" = "\xf0\x9f\x92\x9b",
"green_heart" = "\xf0\x9f\x92\x9a",
"blue_heart" = "\xf0\x9f\x92\x99",
"purple_heart" = "\xf0\x9f\x92\x9c",
"broken_heart" = "\xf0\x9f\x92\x94",
"sparkling_heart" = "\xf0\x9f\x92\x96",
"100" = "\xf0\x9f\x92\xaf",
"anger" = "\xf0\x9f\x92\xa2",
"checkered_flag" = "\xf0\x9f\x8f\x81",
"crossed_flags" = "\xf0\x9f\x9a\xa9",
"rocket" = "\xf0\x9f\x9a\x80",
"star" = "\xe2\xad\x90",
"star2" = "\xf0\x9f\x8c\x9f",
"sparkles" = "\xe2\x9c\xa8",
"boom" = "\xf0\x9f\x92\xa5",
"exclamation" = "\xe2\x9d\x97",
"question" = "\xe2\x9d\x93",
"grey_exclamation" = "\xe2\x9d\x95",
"grey_question" = "\xe2\x9d\x94",
"zzz" = "\xf0\x9f\x92\xa4",
"warning" = "\xe2\x9a\xa0\xef\xb8\x8f",
"no_entry_sign" = "\xf0\x9f\x9a\xab",
"no_entry" = "\xe2\x9b\x94",
"white_check_mark" = "\xe2\x9c\x85",
"negative_squared_cross_mark" = "\xe2\x9d\x8e",
"x" = "\xe2\x9d\x8c",
"o" = "\xe2\xad\x95",
"heavy_plus_sign" = "\xe2\x9e\x95",
"heavy_minus_sign" = "\xe2\x9e\x96",
"heavy_division_sign" = "\xe2\x9e\x97",
"copyright" = "\xc2\xa9\xef\xb8\x8f",
"registered" = "\xc2\xae\xef\xb8\x8f",
"tm" = "\xe2\x84\xa2\xef\xb8\x8f",
// Nature & weather
"fire" = "\xf0\x9f\x94\xa5",
"zap" = "\xe2\x9a\xa1",
"sunny" = "\xe2\x98\x80\xef\xb8\x8f",
"cloud" = "\xe2\x98\x81\xef\xb8\x8f",
"rainbow" = "\xf0\x9f\x8c\x88",
"snowflake" = "\xe2\x9d\x84\xef\xb8\x8f",
"dash" = "\xf0\x9f\x92\xa8",
"tornado" = "\xf0\x9f\x8c\xaa\xef\xb8\x8f",
"deciduous_tree" = "\xf0\x9f\x8c\xb3",
"evergreen_tree" = "\xf0\x9f\x8c\xb2",
"palm_tree" = "\xf0\x9f\x8c\xb4",
"cactus" = "\xf0\x9f\x8c\xb5",
"tulip" = "\xf0\x9f\x8c\xb7",
"rose" = "\xf0\x9f\x8c\xb9",
"sunflower" = "\xf0\x9f\x8c\xbb",
"hibiscus" = "\xf0\x9f\x8c\xba",
"earth_africa" = "\xf0\x9f\x8c\x8d",
"earth_americas" = "\xf0\x9f\x8c\x8e",
"earth_asia" = "\xf0\x9f\x8c\x8f",
"new_moon" = "\xf0\x9f\x8c\x9a",
"full_moon" = "\xf0\x9f\x8c\x95",
// Food & drink
"coffee" = "\xe2\x98\x95",
"tea" = "\xf0\x9f\x8d\xb5",
"beer" = "\xf0\x9f\x8d\xba",
"beers" = "\xf0\x9f\x8d\xbb",
"wine_glass" = "\xf0\x9f\x8d\xb7",
"tropical_drink" = "\xf0\x9f\x8d\xb9",
"apple" = "\xf0\x9f\x8d\x8e",
"green_apple" = "\xf0\x9f\x8d\x8f",
"orange" = "\xf0\x9f\x8d\x8a",
"lemon" = "\xf0\x9f\x8d\x8b",
"banana" = "\xf0\x9f\x8d\x8c",
"watermelon" = "\xf0\x9f\x8d\x89",
"grapes" = "\xf0\x9f\x8d\x87",
"strawberry" = "\xf0\x9f\x8d\x93",
"melon" = "\xf0\x9f\x8d\x88",
"cherries" = "\xf0\x9f\x8d\x92",
"peach" = "\xf0\x9f\x8d\x91",
"pineapple" = "\xf0\x9f\x8d\x8d",
"pizza" = "\xf0\x9f\x8d\x95",
"hamburger" = "\xf0\x9f\x8d\x94",
"hotdog" = "\xf0\x9f\x8c\xad",
"taco" = "\xf0\x9f\x8c\xae",
"burrito" = "\xf0\x9f\x8c\xaf",
"cake" = "\xf0\x9f\x8d\xb0",
"cookie" = "\xf0\x9f\x8d\xaa",
"chocolate_bar" = "\xf0\x9f\x8d\xab",
"candy" = "\xf0\x9f\x8d\xac",
"popcorn" = "\xf0\x9f\x8d\xbf",
// Animals
"dog" = "\xf0\x9f\x90\xb6",
"cat" = "\xf0\x9f\x90\xb1",
"mouse" = "\xf0\x9f\x90\xad",
"hamster" = "\xf0\x9f\x90\xb9",
"rabbit" = "\xf0\x9f\x90\xb0",
"bear" = "\xf0\x9f\x90\xbb",
"panda_face" = "\xf0\x9f\x90\xbc",
"koala" = "\xf0\x9f\x90\xa8",
"tiger" = "\xf0\x9f\x90\xaf",
"lion_face" = "\xf0\x9f\xa6\x81",
"cow" = "\xf0\x9f\x90\xae",
"pig" = "\xf0\x9f\x90\xb7",
"frog" = "\xf0\x9f\x90\xb8",
"monkey_face" = "\xf0\x9f\x90\xb5",
"see_no_evil" = "\xf0\x9f\x99\x88",
"hear_no_evil" = "\xf0\x9f\x99\x89",
"speak_no_evil" = "\xf0\x9f\x99\x8a",
"owl" = "\xf0\x9f\xa6\x89",
"bat" = "\xf0\x9f\xa6\x87",
"wolf" = "\xf0\x9f\x90\xba",
"boar" = "\xf0\x9f\x90\x97",
"horse" = "\xf0\x9f\x90\x8e",
"unicorn" = "\xf0\x9f\xa6\x84",
"honeybee" = "\xf0\x9f\x90\x9d",
"bug" = "\xf0\x9f\x90\x9b",
"snail" = "\xf0\x9f\x90\x8c",
"beetle" = "\xf0\x9f\x90\x9e",
"ant" = "\xf0\x9f\x90\x9c",
"spider" = "\xf0\x9f\x95\xb7",
"scorpion" = "\xf0\x9f\xa6\x82",
"turtle" = "\xf0\x9f\x90\xa2",
"snake" = "\xf0\x9f\x90\x8d",
"octopus" = "\xf0\x9f\x90\x99",
"shell" = "\xf0\x9f\x90\x9a",
"whale" = "\xf0\x9f\x90\xb3",
"dolphin" = "\xf0\x9f\x90\xac",
"fish" = "\xf0\x9f\x90\x9f",
"tropical_fish" = "\xf0\x9f\x90\xa0",
"blowfish" = "\xf0\x9f\x90\xa1",
"penguin" = "\xf0\x9f\x90\xa7",
"chicken" = "\xf0\x9f\x90\x94",
"bird" = "\xf0\x9f\x90\xa6",
"eagle" = "\xf0\x9f\xa6\x85",
// Objects
"computer" = "\xf0\x9f\x92\xbb",
"iphone" = "\xf0\x9f\x93\xb1",
"keyboard" = "\xe2\x8c\xa8\xef\xb8\x8f",
"desktop_computer" = "\xf0\x9f\x96\xa5",
"printer" = "\xf0\x9f\x96\xa8",
"mouse_three_button" = "\xf0\x9f\x96\xb1",
"joystick" = "\xf0\x9f\x95\xb9",
"stash" = "\xf0\x9f\x92\xbe",
"floppy_disk" = "\xf0\x9f\x92\xbe",
"cd" = "\xf0\x9f\x92\xbf",
"dvd" = "\xf0\x9f\x93\x80",
"vhs" = "\xf0\x9f\x93\xbc",
"camera" = "\xf0\x9f\x93\xb7",
"video_camera" = "\xf0\x9f\x93\xb9",
"tv" = "\xf0\x9f\x93\xba",
"radio" = "\xf0\x9f\x93\xbb",
"pager" = "\xf0\x9f\x93\x9f",
"telephone" = "\xe2\x98\x8e\xef\xb8\x8f",
"fax" = "\xf0\x9f\x93\xa0",
"bulb" = "\xf0\x9f\x92\xa1",
"candle" = "\xf0\x9f\x95\xaf",
"bathtub" = "\xf0\x9f\x9b\x81",
"shower" = "\xf0\x9f\x9a\xbf",
"toilet" = "\xf0\x9f\x9a\xbd",
"wrench" = "\xf0\x9f\x94\xa7",
"hammer" = "\xf0\x9f\x94\xa8",
"nut_and_bolt" = "\xf0\x9f\x94\xa9",
"gear" = "\xe2\x9a\x99\xef\xb8\x8f",
"link" = "\xf0\x9f\x94\x97",
"lock" = "\xf0\x9f\x94\x92",
"unlock" = "\xf0\x9f\x94\x93",
"key" = "\xf0\x9f\x94\x91",
"bell" = "\xf0\x9f\x94\x94",
"bookmark" = "\xf0\x9f\x94\x96",
"pushpin" = "\xf0\x9f\x93\x8c",
"paperclip" = "\xf0\x9f\x93\x8e",
"memo" = "\xf0\x9f\x93\x9d",
"pencil2" = "\xe2\x9c\x8f\xef\xb8\x8f",
"black_nib" = "\xe2\x9c\x92\xef\xb8\x8f",
"pen" = "\xf0\x9f\x96\x8a",
"paintbrush" = "\xf0\x9f\x96\x8c",
"crayon" = "\xf0\x9f\x96\x8d",
"book" = "\xf0\x9f\x93\x97",
"books" = "\xf0\x9f\x93\x9a",
"ledger" = "\xf0\x9f\x93\x92",
"notebook" = "\xf0\x9f\x93\x93",
"scroll" = "\xf0\x9f\x93\x9c",
"page_with_curl" = "\xf0\x9f\x93\x84",
"newspaper" = "\xf0\x9f\x93\xb0",
"chart_with_upwards_trend" = "\xf0\x9f\x93\x88",
"chart_with_downwards_trend" = "\xf0\x9f\x93\x89",
"bar_chart" = "\xf0\x9f\x93\x8a",
"calendar" = "\xf0\x9f\x93\x85",
"date" = "\xf0\x9f\x93\x85",
"hourglass" = "\xe2\x8f\xb3",
"hourglass_flowing_sand" = "\xe2\x8f\xb3",
"clock" = "\xf0\x9f\x95\x90",
"alarm_clock" = "\xe2\x8f\xb0",
"stopwatch" = "\xe2\x8f\xb1",
"watch" = "\xe2\x8c\x9a",
"moneybag" = "\xf0\x9f\x92\xb0",
"yen" = "\xf0\x9f\x92\xb4",
"dollar" = "\xf0\x9f\x92\xb5",
"euro" = "\xf0\x9f\x92\xb6",
"pound" = "\xf0\x9f\x92\xb7",
"money_with_wings" = "\xf0\x9f\x92\xb8",
"credit_card" = "\xf0\x9f\x92\xb3",
"gem" = "\xf0\x9f\x92\x8e",
"bomb" = "\xf0\x9f\x92\xa3",
"gift" = "\xf0\x9f\x8e\x81",
"balloon" = "\xf0\x9f\x8e\x88",
"tada" = "\xf0\x9f\x8e\x89",
"confetti_ball" = "\xf0\x9f\x8e\x8a",
"package" = "\xf0\x9f\x93\xa6",
"mailbox" = "\xf0\x9f\x93\xab",
"inbox_tray" = "\xf0\x9f\x93\xa5",
"outbox_tray" = "\xf0\x9f\x93\xa4",
"email" = "\xe2\x9c\x89\xef\xb8\x8f",
"envelope" = "\xe2\x9c\x89\xef\xb8\x8f",
"incoming_envelope" = "\xf0\x9f\x93\xa8",
// Activities
"soccer" = "\xe2\x9a\xbd",
"basketball" = "\xf0\x9f\x8f\x80",
"football" = "\xf0\x9f\x8f\x88",
"baseball" = "\xe2\x9a\xbe",
"tennis" = "\xf0\x9f\x8e\xbe",
"8ball" = "\xf0\x9f\x8e\xb1",
"bowling" = "\xf0\x9f\x8e\xb3",
"video_game" = "\xf0\x9f\x8e\xae",
"dart" = "\xf0\x9f\x8e\xaf",
"game_die" = "\xf0\x9f\x8e\xb2",
"slot_machine" = "\xf0\x9f\x8e\xb0",
"cards" = "\xf0\x9f\x83\x8f",
"black_joker" = "\xf0\x9f\x83\x9f",
"mahjong" = "\xf0\x9f\x80\x84",
"musical_note" = "\xf0\x9f\x8e\xb5",
"notes" = "\xf0\x9f\x8e\xb6",
"saxophone" = "\xf0\x9f\x8e\xb7",
"guitar" = "\xf0\x9f\x8e\xb8",
"musical_keyboard" = "\xf0\x9f\x8e\xb9",
"trumpet" = "\xf0\x9f\x8e\xba",
"violin" = "\xf0\x9f\x8e\xbb",
"drum" = "\xf0\x9f\xa5\x81",
"headphones" = "\xf0\x9f\x8e\xa7",
"microphone" = "\xf0\x9f\x8e\xa4",
"level_slider" = "\xf0\x9f\x8e\x9a",
"control_knobs" = "\xf0\x9f\x8e\x9b",
"ticket" = "\xf0\x9f\x8e\xab",
"art" = "\xf0\x9f\x8e\xa8",
"circus_tent" = "\xf0\x9f\x8e\xaa",
"theater_masks" = "\xf0\x9f\x8e\xad",
"clapper" = "\xf0\x9f\x8e\xac",
// Travel
"car" = "\xf0\x9f\x9a\x97",
"taxi" = "\xf0\x9f\x9a\x95",
"bus" = "\xf0\x9f\x9a\x8d",
"train" = "\xf0\x9f\x9a\x86",
"metro" = "\xf0\x9f\x9a\x87",
"light_rail" = "\xf0\x9f\x9a\x88",
"tram" = "\xf0\x9f\x9a\x8a",
"bike" = "\xf0\x9f\x9a\xb2",
"motorcycle" = "\xf0\x9f\x8f\x8d",
"airplane" = "\xe2\x9c\x88\xef\xb8\x8f",
"helicopter" = "\xf0\x9f\x9a\x81",
"boat" = "\xe2\x9b\xb5",
"sailboat" = "\xe2\x9b\xb5",
"ship" = "\xf0\x9f\x9a\xa2",
"fuelpump" = "\xe2\x9b\xbd",
"construction" = "\xf0\x9f\x9a\xa7",
"house" = "\xf0\x9f\x8f\xa0",
"house_with_garden" = "\xf0\x9f\x8f\xa1",
"office" = "\xf0\x9f\x8f\xa2",
"post_office" = "\xf0\x9f\x8f\xa3",
"hospital" = "\xf0\x9f\x8f\xa5",
"bank" = "\xf0\x9f\x8f\xa6",
"hotel" = "\xf0\x9f\x8f\xa8",
"school" = "\xf0\x9f\x8f\xab",
"department_store" = "\xf0\x9f\x8f\xac",
"church" = "\xe2\x9b\xaa",
"castle" = "\xf0\x9f\x8f\xb0",
"factory" = "\xf0\x9f\x8f\xad",
"tokyo_tower" = "\xf0\x9f\x97\xbc",
"statue_of_liberty" = "\xf0\x9f\x97\xbd",
"fountain" = "\xe2\x9b\xb2",
"tent" = "\xe2\x9b\xba",
"mountain" = "\xf0\x9f\x8f\x94",
"snow_capped_mountain" = "\xf0\x9f\x8f\x94",
"beach" = "\xf0\x9f\x8f\x96",
"camping" = "\xf0\x9f\x8f\x95",
"world_map" = "\xf0\x9f\x97\xba",
"japan" = "\xf0\x9f\x97\xbe",
};
expand_emoji :: proc(text: string) -> string {
if len(EMOJIS) == 0 {
return text
}
parts: [dynamic]string
defer delete(parts)
remaining := text
for {
colon := strings.index(remaining, ":")
if colon < 0 {
append(&parts, remaining)
break
}
// Find the closing colon
after := remaining[colon + 1:]
end := strings.index(after, ":")
if end < 0 {
append(&parts, remaining)
break
}
shortcode := remaining[colon + 1 : colon + 1 + end]
// Validate: shortcode must be all lowercase letters, digits, or underscores
// and must not contain whitespace
valid := true
for c in shortcode {
if !(c >= 'a' && c <= 'z') && !(c >= '0' && c <= '9') && c != '_' && c != '+' && c != '-' {
valid = false
break
}
}
if !valid || len(shortcode) == 0 {
append(&parts, remaining[:colon + 1])
remaining = remaining[colon + 1:]
continue
}
emoji, found := EMOJIS[shortcode]
if !found {
append(&parts, remaining[:colon + 1])
remaining = remaining[colon + 1:]
continue
}
// Replace :shortcode: with emoji
append(&parts, remaining[:colon])
append(&parts, emoji)
remaining = remaining[colon + 1 + end + 1:]
}
return strings.join(parts[:], "")
}
+4
View File
@@ -58,6 +58,10 @@ render_site :: proc(pages: []Page, content_path: string, output_dir: string, bas
// Copy static assets (avatar, favicon, etc.) // Copy static assets (avatar, favicon, etc.)
copy_static_assets(content_path, output_dir) copy_static_assets(content_path, output_dir)
// Generate robots.txt
robots := fmt.aprintf("User-agent: *\nAllow: /\nSitemap: %s/sitemap.xml\n", base_url)
write_file(fmt.tprintf("%s/robots.txt", output_dir), robots)
total := len(pages) + 1 total := len(pages) + 1
if !has_home { if !has_home {
total += 1 total += 1