From 9355c098e03def2b3ca6d2a80202b5833c776ade Mon Sep 17 00:00:00 2001 From: "N0\\A" Date: Tue, 14 Jul 2026 21:33:54 +0200 Subject: [PATCH] Embeds --- README.tlm | 1 + src/embed.odin | 7 +++ src/html.odin | 33 +++++++++++ src/main.odin | 8 +++ src/parser.odin | 149 ++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 198 insertions(+) diff --git a/README.tlm b/README.tlm index 99b75b9..0f4c43d 100644 --- a/README.tlm +++ b/README.tlm @@ -12,6 +12,7 @@ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam blandit dolor nibh, $[https://krzak.org/krzak-icon.png] $(https://krzak.org/krzak-icon.png) +$[https://krzak.org/krzak-icon.png|img] *italic* **bold** diff --git a/src/embed.odin b/src/embed.odin index 0aaf3bb..169652d 100644 --- a/src/embed.odin +++ b/src/embed.odin @@ -2,6 +2,13 @@ package main import "core:strings" +is_url :: proc(url: string) -> bool { + if strings.contains(url, ":") || strings.contains(url, "/") || strings.contains(url, ".") { + return true + } + return false +} + get_ext :: proc(url: string) -> string { clean := url if question := strings.index_byte(clean, '?'); question != -1 { diff --git a/src/html.odin b/src/html.odin index 8d94979..eb226e0 100644 --- a/src/html.odin +++ b/src/html.odin @@ -278,6 +278,39 @@ print_html :: proc(token: ^Token) { fmt.print("") } + case TokenType.Image: + if url, ok := token.value.(string); ok { + b := strings.builder_make(context.temp_allocator) + for child in token.children { + get_text_content(child, &b) + } + alt_text := strings.to_string(b) + fmt.printf("\"%s\"", url, alt_text) + } + + case TokenType.Audio: + if url, ok := token.value.(string); ok { + fmt.printf("") + } + + case TokenType.Video: + if url, ok := token.value.(string); ok { + fmt.printf("") + } + + case TokenType.Iframe: + if url, ok := token.value.(string); ok { + fmt.printf("", url) + } + case TokenType.BreakLine: fmt.print("
\n") diff --git a/src/main.odin b/src/main.odin index 2d0dff0..fdc9e14 100644 --- a/src/main.odin +++ b/src/main.odin @@ -75,6 +75,14 @@ print_token :: proc(token: ^Token, depth: int = 0) { type_str = "HeaderLink" case TokenType.Link: type_str = "Link" + case TokenType.Image: + type_str = "Image" + case TokenType.Audio: + type_str = "Audio" + case TokenType.Video: + type_str = "Video" + case TokenType.Iframe: + type_str = "Iframe" } value_str := "" diff --git a/src/parser.odin b/src/parser.odin index f4abf89..5bc1056 100644 --- a/src/parser.odin +++ b/src/parser.odin @@ -34,6 +34,10 @@ TokenType :: enum { Subscript, HeaderLink, Link, + Image, + Audio, + Video, + Iframe, } TokenValue :: union { @@ -476,6 +480,150 @@ parse_links :: proc(token: ^Token) { } } +find_embed :: proc( + str: string, +) -> ( + start_idx, end_idx: int, + is_embed: bool, + url: string, + alt: string, + embed_type: string, +) { + for i := 0; i < len(str); i += 1 { + if str[i] == '$' && i + 1 < len(str) { + if str[i + 1] == '[' { + if end, alt_text, url_text, ok := match_pair(str[i + 1:], '[', ']', '(', ')'); ok { + clean_url := url_text + type_hint := "" + if pipe_idx := strings.index_byte(url_text, '|'); pipe_idx != -1 { + clean_url = url_text[:pipe_idx] + type_hint = url_text[pipe_idx + 1:] + } + + if is_url(clean_url) { + e_type := "" + if type_hint != "" { + e_type = parse_type_hint(type_hint) + } + if e_type == "" { + e_type = detect_embed_type(clean_url) + } + return i, i + 1 + end, true, clean_url, alt_text, e_type + } + } + if end, inner, ok := match_single(str[i + 1:], '[', ']'); ok { + url_text := inner + type_hint := "" + if pipe_idx := strings.index_byte(inner, '|'); pipe_idx != -1 { + url_text = inner[:pipe_idx] + type_hint = inner[pipe_idx + 1:] + } + + if is_url(url_text) { + e_type := "" + if type_hint != "" { + e_type = parse_type_hint(type_hint) + } + if e_type == "" { + e_type = detect_embed_type(url_text) + } + return i, i + 1 + end, true, url_text, url_text, e_type + } + } + } else if str[i + 1] == '(' { + if end, url_text, alt_text, ok := match_pair(str[i + 1:], '(', ')', '[', ']'); ok { + clean_url := url_text + type_hint := "" + if pipe_idx := strings.index_byte(url_text, '|'); pipe_idx != -1 { + clean_url = url_text[:pipe_idx] + type_hint = url_text[pipe_idx + 1:] + } + + if is_url(clean_url) { + e_type := "" + if type_hint != "" { + e_type = parse_type_hint(type_hint) + } + if e_type == "" { + e_type = detect_embed_type(clean_url) + } + return i, i + 1 + end, true, clean_url, alt_text, e_type + } + } + if end, inner, ok := match_single(str[i + 1:], '(', ')'); ok { + url_text := inner + type_hint := "" + if pipe_idx := strings.index_byte(inner, '|'); pipe_idx != -1 { + url_text = inner[:pipe_idx] + type_hint = inner[pipe_idx + 1:] + } + + if is_url(url_text) { + e_type := "" + if type_hint != "" { + e_type = parse_type_hint(type_hint) + } + if e_type == "" { + e_type = detect_embed_type(url_text) + } + return i, i + 1 + end, true, url_text, url_text, e_type + } + } + } + } + } + + return -1, -1, false, "", "", "" +} + +parse_embeds :: proc(token: ^Token) { + if token.type == .CodeBlock || token.type == .InlineCode { + return + } + if token.children != nil { + for i := 0; i < len(token.children); i += 1 { + parse_embeds(token.children[i]) + } + } + + if token.type == .Text { + str, ok := token.value.(string) + if !ok { + return + } + + start_idx, end_idx, is_embed, url, alt, e_type := find_embed(str) + if !is_embed { + return + } + + token.value = nil + + if start_idx > 0 { + push_token(token, .Text, str[:start_idx]) + } + + embed_token_type: TokenType + if e_type == "image" { + embed_token_type = .Image + } else if e_type == "audio" { + embed_token_type = .Audio + } else if e_type == "video" { + embed_token_type = .Video + } else { + embed_token_type = .Iframe + } + + embed_token := push_token(token, embed_token_type, url) + push_token(embed_token, .Text, alt) + + if end_idx < len(str) { + rem := push_token(token, .Text, str[end_idx:]) + parse_embeds(rem) + } + } +} + add_breaklines :: proc(token: ^Token) { if token.type == .CodeBlock { return @@ -667,6 +815,7 @@ parse :: proc(lines: []string, allocator := context.allocator) -> Token { for token in root.children { parse_inline_code(token) + parse_embeds(token) parse_links(token) parse_inline_footnotes(token) pass_through_children(token)