Scripts
This commit is contained in:
@@ -5,7 +5,35 @@ style="<style>$(cat styles.css)</style>"
|
||||
doctype='<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'
|
||||
meta='<meta http-equiv="Content-Type" content="text/html; charset=utf-8">'
|
||||
start="$doctype<html><head><title>TypoML Preview</title>$meta$style</head><body>"
|
||||
end="</body></html>"
|
||||
script='
|
||||
<script>
|
||||
(function () {
|
||||
var defs = {};
|
||||
var spans = document.querySelectorAll("[data-tlm-def]");
|
||||
for (var i = 0; i < spans.length; i++) {
|
||||
var el = spans[i];
|
||||
defs[el.dataset.tlmDef] = el.dataset.tlmValue;
|
||||
}
|
||||
var tlm = new Proxy({}, {
|
||||
get: function (_, name) {
|
||||
return defs[name];
|
||||
},
|
||||
set: function (_, name, value) {
|
||||
defs[name] = value;
|
||||
var refs = document.querySelectorAll("[data-tlm-var=\"" + name + "\"]");
|
||||
for (var i = 0; i < refs.length; i++) {
|
||||
refs[i].textContent = value;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
ownKeys: function () {
|
||||
return Object.keys(defs);
|
||||
}
|
||||
});
|
||||
window.tlm = tlm;
|
||||
})();
|
||||
</script>'
|
||||
end="$script</body></html>"
|
||||
output="$start$input$end"
|
||||
echo "$output" > /tmp/preview.html
|
||||
nohup xdg-open /tmp/preview.html > /dev/null 2>&1 &
|
||||
|
||||
@@ -14,6 +14,34 @@ get_text_content :: proc(token: ^Token, b: ^strings.Builder) {
|
||||
}
|
||||
}
|
||||
|
||||
check_only_var_defs :: proc(token: ^Token) -> bool {
|
||||
for child in token.children {
|
||||
if child.type == .VariableDef || child.type == .BreakLine {
|
||||
continue
|
||||
}
|
||||
if child.type == .Text {
|
||||
if child.value != nil {
|
||||
if v, ok := child.value.(string); ok && v != "" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
has_non_var_def := false
|
||||
for gc in child.children {
|
||||
if gc.type != .VariableDef {
|
||||
has_non_var_def = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if has_non_var_def {
|
||||
return false
|
||||
}
|
||||
continue
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
slugifyifyifyify :: proc(text: string) -> string {
|
||||
lower := strings.to_lower(text, context.temp_allocator)
|
||||
res, _ := strings.replace_all(lower, " ", "-", context.temp_allocator)
|
||||
@@ -51,11 +79,20 @@ print_html :: proc(token: ^Token) {
|
||||
fmt.printf("</h%d>\n", level)
|
||||
|
||||
case TokenType.Paragraph:
|
||||
only_var_defs := check_only_var_defs(token)
|
||||
if !only_var_defs {
|
||||
fmt.print("<p>")
|
||||
for child in token.children {
|
||||
print_html(child)
|
||||
}
|
||||
fmt.print("</p>\n")
|
||||
} else {
|
||||
for child in token.children {
|
||||
if child.type != .BreakLine {
|
||||
print_html(child)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case TokenType.Blockquote:
|
||||
fmt.print("<blockquote>\n")
|
||||
@@ -316,6 +353,34 @@ print_html :: proc(token: ^Token) {
|
||||
fmt.printf("<script src=\"%s\"></script>", url)
|
||||
}
|
||||
|
||||
case TokenType.ScriptBlock:
|
||||
if content, ok := token.value.(string); ok {
|
||||
fmt.printf("<script>%s</script>", content)
|
||||
}
|
||||
|
||||
case TokenType.Variable:
|
||||
if name, ok := token.value.(string); ok {
|
||||
fmt.printf("<span data-tlm-var=\"%s\">", name)
|
||||
for child in token.children {
|
||||
print_html(child)
|
||||
}
|
||||
fmt.print("</span>")
|
||||
}
|
||||
|
||||
case TokenType.VariableDef:
|
||||
if name, ok := token.value.(string); ok {
|
||||
b := strings.builder_make(context.temp_allocator)
|
||||
for child in token.children {
|
||||
get_text_content(child, &b)
|
||||
}
|
||||
val := strings.to_string(b)
|
||||
fmt.printf(
|
||||
"<template data-tlm-def=\"%s\" data-tlm-value=\"%s\"></template>",
|
||||
name,
|
||||
val,
|
||||
)
|
||||
}
|
||||
|
||||
case TokenType.BreakLine:
|
||||
fmt.print("<br>\n")
|
||||
|
||||
|
||||
@@ -85,6 +85,12 @@ print_token :: proc(token: ^Token, depth: int = 0) {
|
||||
type_str = "Iframe"
|
||||
case TokenType.Script:
|
||||
type_str = "Script"
|
||||
case TokenType.ScriptBlock:
|
||||
type_str = "ScriptBlock"
|
||||
case TokenType.Variable:
|
||||
type_str = "Variable"
|
||||
case TokenType.VariableDef:
|
||||
type_str = "VariableDef"
|
||||
}
|
||||
|
||||
value_str := ""
|
||||
|
||||
+247
-9
@@ -1,5 +1,6 @@
|
||||
package main
|
||||
|
||||
import "core:fmt"
|
||||
import "core:strconv"
|
||||
import "core:strings"
|
||||
|
||||
@@ -39,6 +40,9 @@ TokenType :: enum {
|
||||
Video,
|
||||
Iframe,
|
||||
Script,
|
||||
ScriptBlock,
|
||||
Variable,
|
||||
VariableDef,
|
||||
}
|
||||
|
||||
TokenValue :: union {
|
||||
@@ -362,15 +366,39 @@ match_pair :: proc(s: string, open1, close1, open2, close2: byte) -> (int, strin
|
||||
if len(s) == 0 || s[0] != open1 {
|
||||
return 0, "", "", false
|
||||
}
|
||||
close1_idx := strings.index_byte(s, close1)
|
||||
|
||||
depth := 1
|
||||
close1_idx := -1
|
||||
for i := 1; i < len(s); i += 1 {
|
||||
if s[i] == open1 {
|
||||
depth += 1
|
||||
} else if s[i] == close1 {
|
||||
depth -= 1
|
||||
if depth == 0 {
|
||||
close1_idx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if close1_idx == -1 {
|
||||
return 0, "", "", false
|
||||
}
|
||||
|
||||
if close1_idx + 1 < len(s) && s[close1_idx + 1] == open2 {
|
||||
close2_idx := strings.index_byte(s[close1_idx + 2:], close2)
|
||||
depth = 1
|
||||
close2_idx := -1
|
||||
for i := close1_idx + 2; i < len(s); i += 1 {
|
||||
if s[i] == open2 {
|
||||
depth += 1
|
||||
} else if s[i] == close2 {
|
||||
depth -= 1
|
||||
if depth == 0 {
|
||||
close2_idx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if close2_idx != -1 {
|
||||
close2_idx += close1_idx + 2
|
||||
return close2_idx + 1, s[1:close1_idx], s[close1_idx + 2:close2_idx], true
|
||||
}
|
||||
}
|
||||
@@ -481,6 +509,42 @@ parse_links :: proc(token: ^Token) {
|
||||
}
|
||||
}
|
||||
|
||||
find_script_block :: proc(
|
||||
str: string,
|
||||
) -> (
|
||||
start_idx, end_idx: int,
|
||||
is_block: bool,
|
||||
block_type: string,
|
||||
content: string,
|
||||
) {
|
||||
for i := 0; i < len(str); i += 1 {
|
||||
if str[i] == '$' && i + 1 < len(str) && str[i + 1] == '[' {
|
||||
if end, inner, ok := match_single(str[i + 1:], '[', ']'); ok {
|
||||
inner = strings.trim_space(inner)
|
||||
if strings.has_prefix(inner, "/") {
|
||||
continue
|
||||
}
|
||||
if inner == "script" || inner == "js" || inner == "javascript" {
|
||||
b := strings.builder_make(context.temp_allocator)
|
||||
strings.write_string(&b, "$[/")
|
||||
strings.write_string(&b, inner)
|
||||
strings.write_string(&b, "]")
|
||||
close_tag := strings.to_string(b)
|
||||
close_idx := strings.index(str[i + 1 + end:], close_tag)
|
||||
if close_idx != -1 {
|
||||
close_idx += i + 1 + end
|
||||
content := str[i + 1 + end:close_idx]
|
||||
return i, close_idx + len(
|
||||
close_tag,
|
||||
), true, inner, strings.trim_space(content)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1, -1, false, "", ""
|
||||
}
|
||||
|
||||
find_embed :: proc(
|
||||
str: string,
|
||||
) -> (
|
||||
@@ -593,6 +657,23 @@ parse_embeds :: proc(token: ^Token) {
|
||||
return
|
||||
}
|
||||
|
||||
s_start, s_end, is_script, s_type, s_content := find_script_block(str)
|
||||
if is_script {
|
||||
token.value = nil
|
||||
|
||||
if s_start > 0 {
|
||||
push_token(token, .Text, str[:s_start])
|
||||
}
|
||||
|
||||
script_token := push_token(token, .ScriptBlock, s_content)
|
||||
|
||||
if s_end < len(str) {
|
||||
rem := push_token(token, .Text, str[s_end:])
|
||||
parse_embeds(rem)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
start_idx, end_idx, is_embed, url, alt, e_type := find_embed(str)
|
||||
if !is_embed {
|
||||
return
|
||||
@@ -627,6 +708,126 @@ parse_embeds :: proc(token: ^Token) {
|
||||
}
|
||||
}
|
||||
|
||||
find_variable :: proc(
|
||||
str: string,
|
||||
) -> (
|
||||
start_idx, end_idx: int,
|
||||
is_var: bool,
|
||||
name: string,
|
||||
val: string,
|
||||
is_def: bool,
|
||||
) {
|
||||
for i := 0; i < len(str); i += 1 {
|
||||
if str[i] == '$' && i + 1 < len(str) && str[i + 1] == '(' {
|
||||
if end, inner, ok := match_single(str[i + 1:], '(', ')'); ok {
|
||||
eq := strings.index_byte(inner, '=')
|
||||
if eq != -1 {
|
||||
return i,
|
||||
i + 1 + end,
|
||||
true,
|
||||
strings.trim_space(inner[:eq]),
|
||||
strings.trim_space(inner[eq + 1:]),
|
||||
true
|
||||
} else if is_url(inner) {
|
||||
continue
|
||||
} else {
|
||||
return i, i + 1 + end, true, strings.trim_space(inner), "", false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1, -1, false, "", "", false
|
||||
}
|
||||
|
||||
parse_variables :: 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_variables(token.children[i])
|
||||
}
|
||||
}
|
||||
|
||||
if token.type == .Text {
|
||||
str, ok := token.value.(string)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
start_idx, end_idx, is_var, name, val, is_def := find_variable(str)
|
||||
if !is_var {
|
||||
return
|
||||
}
|
||||
|
||||
token.value = nil
|
||||
|
||||
if start_idx > 0 {
|
||||
push_token(token, .Text, str[:start_idx])
|
||||
}
|
||||
|
||||
if is_def {
|
||||
def_token := push_token(token, .VariableDef, name)
|
||||
push_token(def_token, .Text, val)
|
||||
} else {
|
||||
push_token(token, .Variable, name)
|
||||
}
|
||||
|
||||
if end_idx < len(str) {
|
||||
rem := push_token(token, .Text, str[end_idx:])
|
||||
parse_variables(rem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resolve_variables :: proc(token: ^Token, vars: ^map[string]string) {
|
||||
if token.type == .VariableDef {
|
||||
if name, ok := token.value.(string); ok {
|
||||
if len(token.children) > 0 {
|
||||
if val, vok := token.children[0].value.(string); vok {
|
||||
vars[name] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if token.type == .Variable {
|
||||
if name, ok := token.value.(string); ok {
|
||||
if val, found := vars[name]; found {
|
||||
if token.children == nil {
|
||||
push_token(token, .Text, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if token.type == .Link || token.type == .HeaderLink {
|
||||
if url, ok := token.value.(string); ok {
|
||||
for i := 0; i < len(url); i += 1 {
|
||||
if url[i] == '$' && i + 1 < len(url) && url[i + 1] == '(' {
|
||||
if end, inner, ok := match_single(url[i + 1:], '(', ')'); ok {
|
||||
var_name := strings.trim_space(inner)
|
||||
if val, found := vars[var_name]; found {
|
||||
b := strings.builder_make(context.temp_allocator)
|
||||
strings.write_string(&b, url[:i])
|
||||
strings.write_string(&b, val)
|
||||
strings.write_string(&b, url[i + 1 + end:])
|
||||
token.value = strings.to_string(b)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if token.children != nil {
|
||||
for child in token.children {
|
||||
resolve_variables(child, vars)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
add_breaklines :: proc(token: ^Token) {
|
||||
if token.type == .CodeBlock {
|
||||
return
|
||||
@@ -668,6 +869,9 @@ parse :: proc(lines: []string, allocator := context.allocator) -> Token {
|
||||
// FIRST PASS
|
||||
|
||||
in_code_block := false
|
||||
in_script_block := false
|
||||
script_block_content: [dynamic]string
|
||||
script_block_type := ""
|
||||
|
||||
for untrimmed_line, line_index in lines {
|
||||
line := strings.trim_space(untrimmed_line)
|
||||
@@ -682,6 +886,21 @@ parse :: proc(lines: []string, allocator := context.allocator) -> Token {
|
||||
continue
|
||||
}
|
||||
|
||||
if in_script_block {
|
||||
if strings.has_prefix(line, "$[/") {
|
||||
full_content := strings.join(script_block_content[:], "\n")
|
||||
script_token := push_token(&root, TokenType.ScriptBlock, full_content)
|
||||
push_token(script_token, TokenType.Text, script_block_type)
|
||||
in_script_block = false
|
||||
script_block_content = nil
|
||||
script_block_type = ""
|
||||
current_token = &root
|
||||
} else {
|
||||
append(&script_block_content, untrimmed_line)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.has_prefix(line, "```") {
|
||||
lang := strings.trim_space(line[3:])
|
||||
current_token = push_token(&root, TokenType.CodeBlock, lang)
|
||||
@@ -689,6 +908,17 @@ parse :: proc(lines: []string, allocator := context.allocator) -> Token {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.has_prefix(line, "$[") && strings.has_suffix(line, "]") {
|
||||
inner := strings.trim_space(line[2:len(line) - 1])
|
||||
if inner == "script" || inner == "js" || inner == "javascript" {
|
||||
in_script_block = true
|
||||
script_block_type = inner
|
||||
script_block_content = nil
|
||||
current_token = &root
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if len(line) >= 3 {
|
||||
is_hr := true
|
||||
ch := line[0]
|
||||
@@ -811,17 +1041,25 @@ parse :: proc(lines: []string, allocator := context.allocator) -> Token {
|
||||
// SECOND PASS
|
||||
|
||||
for token in root.children {
|
||||
add_breaklines(token)
|
||||
parse_inline_code(token)
|
||||
parse_links(token)
|
||||
parse_variables(token)
|
||||
parse_embeds(token)
|
||||
parse_inline_footnotes(token)
|
||||
pass_through_children(token)
|
||||
}
|
||||
|
||||
// THIRD PASS
|
||||
|
||||
for token in root.children {
|
||||
parse_inline_code(token)
|
||||
parse_embeds(token)
|
||||
parse_inline_footnotes(token)
|
||||
parse_links(token)
|
||||
pass_through_children(token)
|
||||
add_breaklines(token)
|
||||
}
|
||||
|
||||
// VARIABLE RESOLUTION PASS
|
||||
|
||||
vars: map[string]string
|
||||
for child in root.children {
|
||||
resolve_variables(child, &vars)
|
||||
}
|
||||
|
||||
// FOURTH PASS
|
||||
|
||||
Reference in New Issue
Block a user