diff --git a/src/markdown.rs b/src/markdown.rs
index 0a48004..44fc157 100644
--- a/src/markdown.rs
+++ b/src/markdown.rs
@@ -1,14 +1,7 @@
use std::collections::HashSet;
pub fn render_markdown(input: &str) -> String {
- let html = tetratto_shared::markdown::render_markdown_dirty(&parse_page(&parse_details(
- &parse_text_color(&parse_highlight(&parse_link(&parse_image(
- &parse_image_size(&parse_toc(&parse_underline(&parse_markdown_element(
- &parse_comment(&input.replace("[/]", "
")),
- )))),
- )))),
- )))
- .replace("$per", "%");
+ let html = tetratto_shared::markdown::render_markdown_dirty(input);
let mut allowed_attributes = HashSet::new();
allowed_attributes.insert("id");
@@ -20,1023 +13,6 @@ pub fn render_markdown(input: &str) -> String {
allowed_attributes.insert("align");
allowed_attributes.insert("src");
allowed_attributes.insert("style");
- allowed_attributes.insert("controls");
- allowed_attributes.insert("autoplay");
- allowed_attributes.insert("loop");
- tetratto_shared::markdown::clean_html(
- html.replace("", ":temp_style")
- .replace("")
-}
-
-pub(crate) fn is_numeric(value: &str) -> bool {
- let mut is_numeric = false;
-
- for char in value.chars() {
- is_numeric = char.is_numeric();
- }
-
- is_numeric
-}
-
-pub(crate) fn slice(x: &str, range: core::ops::RangeFrom) -> String {
- (&x.chars().collect::>()[range])
- .iter()
- .collect::()
-}
-
-fn parse_text_color_line(output: &mut String, buffer: &mut String, line: &str) {
- let mut in_color_buffer = false;
- let mut in_main_buffer = false;
- let mut color_buffer = String::new();
- let mut close_1 = false;
-
- for (i, char) in line.chars().enumerate() {
- if close_1 && char != '%' {
- // we expected to see another percentage to close the main buffer,
- // not getting that means this wasn't meant to be a color
- buffer.push('%');
- in_main_buffer = false;
- close_1 = false;
- }
-
- match char {
- '%' => {
- if in_color_buffer {
- in_color_buffer = false;
- in_main_buffer = true;
- continue;
- }
-
- if in_main_buffer {
- // ending
- if !close_1 {
- close_1 = true;
- continue;
- }
-
- // by this point, we have: !
- // %color_buffer%main_buffer%%
- output.push_str(&format!(
- "{buffer}"
- ));
-
- color_buffer.clear();
- buffer.clear();
-
- // ...
- in_main_buffer = false;
- close_1 = false;
- continue;
- }
-
- // start
- // scan ahead
- let ahead = slice(line, i..);
- if !ahead.contains("%%") {
- // no closing sequence, we're done
- buffer.push(char);
- continue;
- }
-
- // flush buffer
- output.push_str(&buffer);
- buffer.clear();
-
- // toggle open
- in_color_buffer = true;
- }
- ' ' => {
- if in_color_buffer == true {
- buffer.push_str(&color_buffer);
- color_buffer.clear();
- }
-
- buffer.push(char);
- }
- _ => {
- if in_color_buffer {
- color_buffer.push(char)
- } else {
- buffer.push(char)
- }
- }
- }
- }
-}
-
-fn parse_highlight_line(output: &mut String, buffer: &mut String, line: &str) {
- let mut open_1 = false;
- let mut open_2 = false;
- let mut close_1 = false;
- let mut is_open = false;
-
- for char in line.chars() {
- if close_1 && char != '=' {
- buffer.push('=');
- close_1 = false;
- }
-
- if open_1 && char != '=' {
- buffer.push('=');
- open_1 = false;
- is_open = false;
- }
-
- match char {
- '=' => {
- if !is_open {
- // flush buffer
- output.push_str(&buffer);
- buffer.clear();
-
- // toggle open
- open_1 = true;
- is_open = true;
- } else {
- if open_1 {
- // this is the second open we've recieved
- open_2 = true;
- open_1 = false;
- continue;
- }
-
- if close_1 {
- // this is the second close we've received
- output.push_str(&format!("{buffer}\n"));
- buffer.clear();
- open_1 = false;
- open_2 = false;
- close_1 = false;
- is_open = false;
- continue;
- }
-
- close_1 = true;
- }
- }
- _ => {
- if open_1 {
- open_1 = false;
- buffer.push('=');
- }
-
- if open_2 && is_open {
- open_2 = false;
- }
-
- buffer.push(char);
- }
- }
- }
-}
-
-fn parse_underline_line(output: &mut String, buffer: &mut String, line: &str) {
- let mut open_1 = false;
- let mut is_open = false;
- let mut close_1 = false;
-
- for char in line.chars() {
- if open_1 && char != '~' {
- is_open = false;
- open_1 = false;
-
- if char == '[' {
- // image
- buffer.push('!');
- } else {
- buffer.push_str("!");
- }
- }
-
- if close_1 && char != '!' {
- is_open = false;
- close_1 = false;
- buffer.push('~');
- }
-
- match char {
- '~' => {
- if open_1 {
- open_1 = false;
- is_open = true;
- } else if is_open {
- // open close
- close_1 = true;
- }
- }
- '!' => {
- if close_1 {
- // close
- let mut s: Vec<&str> = buffer.split(";").collect();
- let text = s.pop().unwrap_or(&"").trim();
- let mut style = String::new();
-
- for (i, mut x) in s.iter().enumerate() {
- if i == 0 {
- // color
- if x == &"default" {
- x = &"currentColor";
- }
-
- style.push_str(&format!("text-decoration-color: {x};"));
- } else if i == 1 {
- // style
- if x == &"default" {
- x = &"solid";
- }
-
- style.push_str(&format!("text-decoration-style: {x};"));
- } else if i == 2 {
- // line
- if x == &"default" {
- x = &"underline";
- }
-
- style.push_str(&format!("text-decoration-line: {x};"));
- } else if i == 3 {
- // thickness
- if x == &"default" {
- x = &"1px";
- }
-
- style.push_str(&format!("text-decoration-thickness: {x}px;"));
- }
- }
-
- // defaults
- if s.get(1).is_none() {
- style.push_str(&format!("text-decoration-style: solid;"));
- }
-
- if s.get(2).is_none() {
- style.push_str(&format!("text-decoration-line: underline;"));
- }
-
- if s.get(3).is_none() {
- style.push_str(&format!("text-decoration-thickness: 1px;"));
- }
-
- // ...
- output.push_str(&format!("{text}"));
- buffer.clear();
-
- open_1 = false;
- is_open = false;
- close_1 = false;
- continue;
- } else if is_open {
- buffer.push(char);
- continue;
- }
-
- // open
- open_1 = true;
-
- // flush buffer
- output.push_str(&buffer);
- buffer.clear();
- }
- _ => buffer.push(char),
- }
- }
-}
-
-fn parse_comment_line(output: &mut String, _: &mut String, line: &str) {
- if line.contains("]:") && line.starts_with("[") {
- return;
- }
-
- if line == "[..]" {
- output.push_str(" ");
- return;
- }
-
- output.push_str(line);
-}
-
-fn parse_image_size_line(output: &mut String, buffer: &mut String, line: &str) {
- let mut image_possible = false;
- let mut in_image = false;
- let mut in_size = false;
- let mut in_size_rhs = false;
-
- let mut size_lhs = String::new();
- let mut size_rhs = String::new();
-
- if !line.contains("{") {
- output.push_str(line);
- return;
- }
-
- for char in line.chars() {
- if image_possible && char != '[' {
- image_possible = false;
- output.push('!');
- }
-
- match char {
- '[' => {
- if image_possible {
- in_image = true;
- image_possible = false;
- continue;
- }
-
- if in_image {
- buffer.push(char);
- } else {
- output.push(char);
- }
- }
- '{' => {
- if in_image {
- in_size = true;
- continue;
- }
-
- if in_image {
- buffer.push(char);
- } else {
- output.push(char);
- }
- }
- ':' => {
- if in_size {
- in_size_rhs = true;
- continue;
- }
-
- if in_image {
- buffer.push(char);
- } else {
- output.push(char);
- }
- }
- '}' => {
- if in_size && in_size_rhs {
- // end
- output.push_str(&format!(
- "![{buffer}",
- if is_numeric(&size_lhs) {
- format!("{size_lhs}px")
- } else {
- size_lhs
- },
- if is_numeric(&size_rhs) {
- format!("{size_rhs}px")
- } else {
- size_rhs
- },
- if buffer.ends_with("#left)") {
- "left"
- } else if buffer.ends_with("#right)") {
- "right"
- } else {
- "unset"
- }
- ));
-
- size_lhs = String::new();
- size_rhs = String::new();
- in_image = false;
- in_size = false;
- in_size_rhs = false;
- image_possible = false;
-
- buffer.clear();
- continue;
- }
-
- if in_image {
- buffer.push(char);
- } else {
- output.push(char);
- }
- }
- '!' => {
- // flush buffer
- output.push_str(&buffer);
- buffer.clear();
-
- // ...
- image_possible = true
- }
- _ => {
- if in_image {
- if in_size {
- if in_size_rhs {
- size_rhs.push(char);
- } else {
- size_lhs.push(char);
- }
- } else {
- buffer.push(char);
- }
- } else {
- output.push(char)
- }
- }
- }
- }
-}
-
-fn parse_image_line(output: &mut String, buffer: &mut String, line: &str) {
- let mut image_possible = false;
- let mut in_image = false;
- let mut in_alt = false;
- let mut in_src = false;
- let mut alt = String::new();
-
- for char in line.chars() {
- if image_possible && char != '[' {
- image_possible = false;
- output.push('!');
- }
-
- match char {
- '[' => {
- if image_possible {
- in_image = true;
- image_possible = false;
- in_alt = true;
- continue;
- }
-
- if in_image {
- buffer.push(char);
- } else {
- output.push(char);
- }
- }
- ']' => {
- if in_alt {
- in_alt = false;
- in_src = true;
- continue;
- }
-
- output.push(char);
- }
- '(' => {
- if in_src {
- continue;
- }
-
- if in_image {
- buffer.push(char);
- } else {
- output.push(char);
- }
- }
- ')' => {
- if in_image {
- // end
- output.push_str(&format!(
- "
",
- buffer.replace(" ", "$per20"),
- if buffer.ends_with("#left") {
- "left"
- } else if buffer.ends_with("#right") {
- "right"
- } else {
- "unset"
- }
- ));
-
- alt = String::new();
- in_alt = false;
- in_src = false;
- in_image = false;
- image_possible = false;
-
- buffer.clear();
- continue;
- }
-
- output.push(char);
- }
- '!' => {
- // flush buffer
- output.push_str(&buffer);
- buffer.clear();
-
- // ...
- image_possible = true;
- }
- _ => {
- if in_image {
- if in_alt {
- alt.push(char)
- } else {
- buffer.push(char);
- }
- } else {
- output.push(char)
- }
- }
- }
- }
-}
-
-fn parse_link_line(output: &mut String, buffer: &mut String, line: &str) {
- let mut in_link = false;
- let mut in_text = false;
- let mut in_src = false;
- let mut text = String::new();
-
- for (i, char) in line.chars().enumerate() {
- match char {
- '[' => {
- // flush buffer
- output.push_str(&buffer);
- buffer.clear();
-
- // scan for closing, otherwise quit
- let haystack = slice(line, i..);
- if !haystack.contains("]") {
- output.push('[');
- continue;
- }
-
- // ...
- in_link = true;
- in_text = true;
- }
- ']' => {
- if in_text {
- in_text = false;
- in_src = true;
- continue;
- }
-
- output.push(char);
- }
- '(' => {
- if in_src {
- continue;
- }
-
- if in_link {
- buffer.push(char);
- } else {
- output.push(char);
- }
- }
- ')' => {
- if in_link {
- // end
- output.push_str(&format!(
- "{text}"
- ));
-
- text = String::new();
- in_text = false;
- in_src = false;
- in_link = false;
-
- buffer.clear();
- continue;
- }
-
- output.push(char);
- }
- _ => {
- if in_link {
- if in_text {
- text.push(char)
- } else {
- buffer.push(char);
- }
- } else {
- output.push(char)
- }
- }
- }
- }
-}
-
-/// Helper macro to quickly allow parsers to ignore fenced code blocks.
-macro_rules! parser_ignores_pre {
- ($body:ident, $input:ident) => {{
- let mut in_pre_block = false;
- let mut output = String::new();
- let mut buffer = String::new();
-
- for line in $input.split("\n") {
- if line.starts_with("```") | (line == "") {
- in_pre_block = !in_pre_block;
- output.push_str(&format!("{line}\n"));
- continue;
- }
-
- if in_pre_block {
- output.push_str(&format!("{line}\n"));
- continue;
- }
-
- $body(&mut output, &mut buffer, line);
- output.push_str(&format!("{buffer}\n"));
- buffer.clear();
- }
-
- output
- }};
-
- ($body:ident, $input:ident, $id:literal, ..) => {{
- let mut in_pre_block = false;
- let mut output = String::new();
- let mut buffer = String::new();
- let mut proc_str = String::new();
- let mut pre_blocks = Vec::new();
- let mut pre_idx = 0;
-
- for line in $input.split("\n") {
- if line.starts_with("```") {
- in_pre_block = !in_pre_block;
-
- pre_idx += 1;
- pre_blocks.push(String::new());
- pre_blocks[pre_idx - 1] += &(line.to_string() + "\n");
-
- proc_str += &format!("$pre:{}.{pre_idx}\n", $id);
- continue;
- }
-
- if in_pre_block {
- pre_blocks[pre_idx - 1] += &(line.to_string() + "\n");
- continue;
- }
-
- proc_str += &(line.to_string() + "\n");
- }
-
- $body(&mut output, &mut buffer, &proc_str);
- output.push_str(&format!("{buffer}\n"));
- buffer.clear();
-
- for (mut i, block) in pre_blocks.iter().enumerate() {
- i += 1;
-
- if block == "```\n" {
- output = output.replacen(&format!("$pre:{}.{i}", $id), "", 1);
- continue;
- }
-
- output = output.replacen(&format!("$pre:{}.{i}", $id), &format!("{block}```\n"), 1);
- }
-
- output
- }};
-}
-
-pub fn parse_text_color(input: &str) -> String {
- parser_ignores_pre!(parse_text_color_line, input, 0, ..)
-}
-
-pub fn parse_highlight(input: &str) -> String {
- parser_ignores_pre!(parse_highlight_line, input, 1, ..)
-}
-
-pub fn parse_underline(input: &str) -> String {
- parser_ignores_pre!(parse_underline_line, input, 2, ..)
-}
-
-pub fn parse_comment(input: &str) -> String {
- parser_ignores_pre!(parse_comment_line, input)
-}
-
-pub fn parse_image_size(input: &str) -> String {
- parser_ignores_pre!(parse_image_size_line, input)
-}
-
-pub fn parse_image(input: &str) -> String {
- parser_ignores_pre!(parse_image_line, input)
-}
-
-pub fn parse_link(input: &str) -> String {
- parser_ignores_pre!(parse_link_line, input)
-}
-
-/// Match page definitions.
-///
-/// Each page is denoted with two at symbols, followed by the name of the page.
-/// The page can also have an optional second argument (separated by a semicolon)
-/// which accepts the "visible" value; marking the page as visible by default.
-///
-/// To close a page (after you're done with the page's content), just put two
-/// at symbols with nothing else on the line.
-///
-/// You're able to put content AFTER the page closing line. This allows you to have
-/// persistant content which is shared between every page. Only content within pages
-/// is hidden when navigating to another page. This means everything in the entry
-/// that isn't part of a page will remian throughout navigations.
-///
-/// # Example
-/// ```md
-/// @@ home; visible
-/// this is the homepage which is shown by default!
-/// @@
-///
-/// @@ about
-/// this is the about page which is NOT shown by default! a link with an href of "#/about" will open this page
-/// @@
-/// ```
-pub fn parse_page(input: &str) -> String {
- let mut output = String::new();
- let mut buffer = String::new();
- let mut page_id = String::new();
-
- let mut start_shown = false;
- let mut in_page = false;
- let mut in_pre = false;
-
- for line in input.split("\n") {
- if line.starts_with("```") || line.starts_with("") {
- in_pre = !in_pre;
-
- if in_page {
- buffer.push_str(&format!("{line}\n"));
- } else {
- output.push_str(&format!("{line}\n"));
- }
-
- continue;
- }
-
- if in_pre {
- if in_page {
- buffer.push_str(&format!("{line}\n"));
- } else {
- output.push_str(&format!("{line}\n"));
- }
-
- continue;
- }
-
- // not in pre
- if line == "@@" {
- // ending block
- if in_page {
- output.push_str(&format!(
- "\n{}\n
",
- if !start_shown { "hidden " } else { "" },
- render_markdown(&buffer) // recurse to render markdown since the renderer is ignoring the div content :/
- ));
-
- start_shown = false;
- in_page = false;
-
- buffer.clear();
- continue;
- }
- } else if line.starts_with("@@") {
- if !in_page {
- in_page = true;
-
- let x = line.replace("@@", "").trim().to_string();
- let id_parts: Vec<&str> = x.split(";").map(|x| x.trim()).collect();
- page_id = id_parts[0].to_string();
-
- if let Some(x) = id_parts.get(1) {
- if *x == "visible" {
- start_shown = true;
- }
- }
-
- continue;
- }
- }
-
- // otherwise
- if in_page {
- buffer.push_str(&format!("{line}\n"));
- } else {
- output.push_str(&format!("{line}\n"));
- }
- }
-
- output
-}
-
-/// Parse the markdown syntax for the expandable `` element.
-///
-/// Similar to the [`parse_page`] page definitions, details elements are denoted
-/// with two ampersand symbols. The opening line should look like `&& [summary]`.
-///
-/// The block is closed with a line of exactly two ampersand symbols.
-///
-/// # Example
-/// ```md
-/// && other summary
-/// this element starts closed, but can be expanded
-/// &&
-/// ```
-pub fn parse_details(input: &str) -> String {
- let mut output = String::new();
- let mut buffer = String::new();
- let mut summary = String::new();
-
- let mut in_details = false;
- let mut in_pre = false;
-
- for line in input.split("\n") {
- if line.starts_with("```") || line.starts_with("") {
- in_pre = !in_pre;
-
- if in_details {
- buffer.push_str(&format!("{line}\n"));
- } else {
- output.push_str(&format!("{line}\n"));
- }
-
- continue;
- }
-
- if in_pre {
- if in_details {
- buffer.push_str(&format!("{line}\n"));
- } else {
- output.push_str(&format!("{line}\n"));
- }
-
- continue;
- }
-
- // not in pre
- if line == "&&" {
- // ending block
- if in_details {
- output.push_str(&format!(
- "{summary}
{}
",
- render_markdown(&buffer),
- ));
-
- in_details = false;
- buffer.clear();
- continue;
- }
- } else if line.starts_with("&&") {
- if !in_details {
- in_details = true;
- summary = line.replace("&&", "").trim().to_string();
- continue;
- }
- }
-
- // otherwise
- if in_details {
- buffer.push_str(&format!("{line}\n"));
- } else {
- output.push_str(&format!("{line}\n"));
- }
- }
-
- output
-}
-
-fn underscore_chars(mut x: String, chars: &[&str]) -> String {
- for y in chars {
- x = x.replace(y, "_");
- }
-
- x
-}
-
-/// Get the list of headers needed for [`parse_toc`].
-pub fn get_toc_list(input: &str) -> (String, String) {
- let mut output = String::new();
- let mut toc = String::new();
- let mut in_pre = false;
- let mut hc_offset: Option = None;
-
- for line in input.split("\n") {
- if line.starts_with("```") || line.starts_with("") {
- in_pre = !in_pre;
- output.push_str(&format!("{line}\n"));
- continue;
- }
-
- if in_pre {
- output.push_str(&format!("{line}\n"));
- continue;
- }
-
- // not in pre
- if line.starts_with("#") {
- // get heading count
- let mut hc = 0;
- let real_hc;
-
- for x in line.chars() {
- if x != '#' {
- break;
- }
-
- hc += 1;
- }
-
- real_hc = hc.clone();
- if hc_offset.is_none() {
- if hc > 1 {
- // offset this count to 1 so the list renders properly
- hc_offset = Some(hc - 1);
- hc = 1;
- } else {
- hc_offset = Some(0);
- }
- } else if let Some(offset) = hc_offset {
- hc -= offset;
- }
-
- // add heading with id
- let x = line.replacen(&"#".repeat(real_hc), "", 1);
- let htext = x.trim();
-
- let id = underscore_chars(
- htext.to_lowercase(),
- &[" ", "(", ")", "[", "]", "{", "}", ":", "?", "#", "&"],
- );
-
- output.push_str(&format!(
- "{}\n\n",
- render_markdown(&htext)
- ));
-
- // add heading to toc
- toc += &format!("{}- {htext}\n", " ".repeat(hc));
-
- // ...
- continue;
- }
-
- // otherwise
- output.push_str(&format!("{line}\n"));
- }
-
- (toc, output)
-}
-
-/// Parse the `[toc]` table-of-contents syntax.
-pub fn parse_toc(input: &str) -> String {
- let (toc_list, new_input) = get_toc_list(input);
-
- let mut output = String::new();
- let mut in_pre = false;
-
- for line in new_input.split("\n") {
- if line.starts_with("```") || line.starts_with("") {
- in_pre = !in_pre;
- output.push_str(&format!("{line}\n"));
- continue;
- }
-
- if in_pre {
- output.push_str(&format!("{line}\n"));
- continue;
- }
-
- // not in pre
- if line.len() == 5 && line.to_lowercase() == "[toc]" {
- // add toc
- output.push_str(&format!("\n{toc_list}"));
- continue;
- }
-
- // otherwise
- output.push_str(&format!("{line}\n"));
- }
-
- output
-}
-
-/// Handle the `` HTML element.
-fn parse_markdown_element_line(output: &mut String, buffer: &mut String, line: &str) {
- let mut in_markdown = false;
-
- for char in line.chars() {
- if buffer.ends_with("") {
- in_markdown = true;
- output.push_str(&buffer.replace("", ""));
- buffer.clear();
- } else if in_markdown && buffer.ends_with("") {
- in_markdown = false;
- output.push_str(&render_markdown(&buffer.replace("", "")));
- buffer.clear();
- }
-
- buffer.push(char);
- }
-}
-
-pub fn parse_markdown_element(input: &str) -> String {
- parser_ignores_pre!(parse_markdown_element_line, input)
+ tetratto_shared::markdown::clean_html(html, allowed_attributes)
}