diff --git a/alerts.odin b/alerts.odin new file mode 100644 index 0000000..2be38ec --- /dev/null +++ b/alerts.odin @@ -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, "
") + if bq_start < 0 { + append(&parts, remaining) + break + } + + bq_close := strings.index(remaining, "
") + if bq_close < 0 { + append(&parts, remaining) + break + } + + bq_end := bq_close + len("") + 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
tag and whitespace + pos := len("
") + for pos < len(bq) && (bq[pos] == '\n' || bq[pos] == '\r' || bq[pos] == ' ' || bq[pos] == '\t') { + pos += 1 + } + + // Check for

[! + 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( + `

+

%s %s`, + style, + emoji, + rest, + ) +} diff --git a/content.odin b/content.odin index bbfc97c..ba7acd9 100644 --- a/content.odin +++ b/content.odin @@ -175,7 +175,7 @@ load_page :: proc( } else { clean_body, defs := strip_definitions(body) 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 {