diff --git a/index.ts b/index.ts index c33093f..e6490e6 100644 --- a/index.ts +++ b/index.ts @@ -79,7 +79,7 @@ test_assert_equal(parse_md_to_html("> ```\n> alert('e')\n> ```"), "
\ test_assert_equal(parse_md_to_html("> a\n\n> b"), "\n\na
\n\n", "block quote test 3"); -test_assert_equal(parse_md_to_html("> - burger\n> -winter melons\n> abcdefg\n> - fries\n- p**i**zza\nabacus"), "b
\n\n\n\n
\n- burger
\n-winter melons
\nabcdefg
\n\n
\n- fries
\n\n
\n- pizza
\nabacus
", "unordered lists test"); +test_assert_equal(parse_md_to_html("> - burger\n> -winter melons\n> abcdefg\n> - fries\n- p**i**zza\na"), "\n\n\n
\n- burger
\n-winter melons
\nabcdefg
\n\n
\n- fries
\n\n
\n- pizza
\na
", "unordered lists test"); test_assert_equal(parse_md_to_html("1. a\n2. b\n3. c\n5. should *fail*\n> 1. d\n> 2. e\n3. should fail too"), "\n
\n- a
\n- b
\n- c
\n5. should fail
\n\n\n\n
\n- d
\n- e
\n3. should fail too
", "ordered lists test 1") diff --git a/makoto.ts b/makoto.ts index ccaa60b..a67d655 100644 --- a/makoto.ts +++ b/makoto.ts @@ -225,6 +225,11 @@ export function parse_md_to_html_with_warnings(md: string): ParseResult { } } } + warnings.push({ + type: "italic-not-closed", + message: "Italic not closed, may be missing closing '*'? Backslash the '*' if this is intentional", + line_number, + }); } else if (asterisk_num === 2) { //remove the last and replace it with a ** let split: string[] = html_line.split(""); @@ -239,6 +244,11 @@ export function parse_md_to_html_with_warnings(md: string): ParseResult { } } } + warnings.push({ + type: "bold-not-closed", + message: "Bold not closed, may be missing closing '**'? Backslash the '**' if this is intentional", + line_number, + }); } asterisk_num = 0; asterisk_out_num = 0; @@ -260,6 +270,11 @@ export function parse_md_to_html_with_warnings(md: string): ParseResult { } } in_superscript = false; + warnings.push({ + type: "superscript-not-closed", + message: "Superscript not closed, may be missing closing '^'? Backslash the '^' if this is intentional", + line_number, + }); } //ending table row if (in_table) { @@ -286,7 +301,23 @@ export function parse_md_to_html_with_warnings(md: string): ParseResult { } html_line = ""; horizontal_num = 0; - line_number++; + if (char === "\n") { + line_number++; + } + //check to see if unordered list is ending + if (in_unordered_list && char === "\n" && ((chars.slice(i+1, i+3) !== "- " && !blockquote_list) || (chars.slice(i+1, i+5) !== "> - " && blockquote_list))) { + html += "\n"; + in_unordered_list = false; + blockquote_list = false; + } + //check to see if ordered list is ending + let ol_num_length: number = String(ordered_list_num+1).length; + if (in_ordered_list && char === "\n" && ((chars.slice(i+1, i+ol_num_length+3) !== `${ordered_list_num+1}. ` && !blockquote_list) || (chars.slice(i+1, i+ol_num_length+5) !== `> ${ordered_list_num+1}. ` && blockquote_list))) { + html += "\n"; + ordered_list_num = 0; + in_ordered_list = false; + blockquote_list = false; + } if (horizontal_rule || was_image || was_link) { if (i !== chars.length - 1 && html[html.length-1] !== "\n") { //only add new line if there isn't already one, and isn't last character @@ -434,10 +465,6 @@ export function parse_md_to_html_with_warnings(md: string): ParseResult { blockquote_list = true; } continue; - } else if (in_unordered_list && ((((chars[i-1] === "\n" && char !== "-") || (chars[i-2] === "\n" && char !== " ")) && !blockquote_list) || (((chars[i-3] === "\n" && char !== "-") || (chars[i-4] === "\n" && char !== " ")) && blockquote_list))) { - html += "\n"; - in_unordered_list = false; - blockquote_list = false; } //handle ordered lists let ol_num_length: number = String(ordered_list_num+1).length; @@ -452,11 +479,6 @@ export function parse_md_to_html_with_warnings(md: string): ParseResult { blockquote_list = true; } continue; - } else if (in_ordered_list && ((chars[i-ol_num_length-2] === "\n" && chars.slice(i-ol_num_length-1, i+1) !== `${ordered_list_num+1}. ` && !in_blockquote) || (in_blockquote && chars[i-ol_num_length-4] === "\n" && chars.slice(i-ol_num_length-3, i+1) !== `> ${ordered_list_num+1}. `))) { - html += "\n"; - ordered_list_num = 0; - in_ordered_list = false; - blockquote_list = false; } //handle code if (char === "`" && !in_code) { @@ -534,7 +556,7 @@ export function parse_md_to_html_with_warnings(md: string): ParseResult { //ensure headings are continuous and have after it ("#a##" or "##abc" are not a valid headings), and are at the beginning of the line //ensure headings are possible in block quotes if (chars.slice(i-heading_level-1, i) === "\n"+"#".repeat(heading_level) || (is_first_line && chars.slice(0, i) === "#".repeat(heading_level)) || (chars.slice(i-heading_level-3, i) === "\n> "+"#".repeat(heading_level) && in_blockquote) || (is_first_line && chars.slice(0, i) === "> "+"#".repeat(heading_level) && in_blockquote)) { - if (char === "#" && !in_heading && heading_level <= 6) { + if (char === "#" && !in_heading && heading_level < 6) { heading_level++; continue; } else if (heading_level > 0 && char === " " && !in_heading) { @@ -542,6 +564,12 @@ export function parse_md_to_html_with_warnings(md: string): ParseResult { html_line = ``; header_num++; continue; + } else if (char === "#" && heading_level === 6) { + warnings.push({ + type: "too-much-header", + message: "Header cannot be more than 6 levels", + line_number, + }) } else if (heading_level > 0) { //not a heading html_line = " "+"#".repeat(heading_level); @@ -569,11 +597,13 @@ export function parse_md_to_html_with_warnings(md: string): ParseResult { } else if (horizontal_num > 0) { //no longer a horizontal line html_line = "
"+"-".repeat(horizontal_num); - warnings.push({ - type: "horizontal-rule-broken", - message: "Horizontal rule broken", - line_number, - }); + if (horizontal_num > 2) { + warnings.push({ + type: "horizontal-rule-broken", + message: "Horizontal rule broken", + line_number, + }); + } } } //handle images @@ -591,6 +621,13 @@ export function parse_md_to_html_with_warnings(md: string): ParseResult { if (chars[i+1] === ")" && i+1 === chars.length-1) { image_src += char; } + if (image_alt === "") { + warnings.push({ + type: "missing-image-alt", + message: "Image is missing alt text, this is bad for accessibility", + line_number, + }); + } html_line += `
`; was_image = true; image_alt = undefined; @@ -625,6 +662,13 @@ export function parse_md_to_html_with_warnings(md: string): ParseResult { if (chars[before_link] === "\n" || before_link === -1) { html_line = "
"; } + if (link_content === "") { + warnings.push({ + type: "empty-link", + message: "Link missing text", + line_number, + }); + } html_line += `${link_content}`; was_link = true; link_content = undefined; diff --git a/styles/makoto.css b/styles/makoto.css index 34b2689..1197402 100644 --- a/styles/makoto.css +++ b/styles/makoto.css @@ -7,7 +7,7 @@ blockquote { } p { - margin: 4px 0px; + margin: 7px 0px; } h1, h2, h3, h4, h5, h6 { @@ -18,6 +18,8 @@ h1, h2, h3, h4, h5, h6 { .code-block { font-family: 'Courier New', Courier, monospace; background-color: #d8d8d8; + padding: 2px 4px; + word-break: break-all; } img { diff --git a/styles/style.css b/styles/style.css index 613e17a..87f1c62 100644 --- a/styles/style.css +++ b/styles/style.css @@ -26,15 +26,14 @@ html, body { #editor { box-sizing: border-box; width: 95%; - width: calc(100% - 15px); + width: calc(100% - 58px); height: 95%; margin: 0; word-break: break-all; font-family: 'Courier New', Courier, monospace; font-size: 0.9em; padding: 3px; - padding-left: 35px; - margin-left: 15px; + padding-left: 58px; overflow-y: auto; } @@ -44,7 +43,7 @@ html, body { #rendered-text { padding: 5px; - word-break: break-all; + word-break: break-word; font-family: Arial, sans-serif; } @@ -56,3 +55,16 @@ html, body { #rendered-text.dark .code-block { color: #2d2d2d; } + +.line-warning { + position: relative; + display: inline-block; + width: 0; + left: -58px; + margin-top: -1px; + font-size: 0.9em; +} + +.line-warning::after { + content: "⚠️"; +} diff --git a/web.ts b/web.ts index f6781fd..6d05a95 100644 --- a/web.ts +++ b/web.ts @@ -8,27 +8,49 @@ let dark_theme_toggle: HTMLInputElement = document.getElementById("dark-theme-to let unedited: boolean = true; function render_warnings(warnings: Warning[]) { - console.log(warnings) + document.querySelectorAll(".line-warning").forEach((item: Element) => item.remove()); warnings.forEach((warning: Warning) => { if (warning.line_number) { - // + let line: Element = editor.children[warning.line_number-1]; + let warning_span: HTMLElement = document.createElement("SPAN"); + warning_span.classList.add("line-warning"); + warning_span.title = warning.message; + line.insertBefore(warning_span, line.childNodes[0]); } }); } const refresh_html = () => { - let parsed: ParseResult = parse_md_to_html_with_warnings(editor.innerText); + //go through the lines and get the editor text + let editor_text: string = Array.from(editor.children).map((item) => item.textContent).reduce((added, current) => added+"\n"+current); + let parsed: ParseResult = parse_md_to_html_with_warnings(editor_text); render_warnings(parsed.warnings); preview.innerHTML = parsed.html; }; -editor.addEventListener("keyup", refresh_html); +editor.addEventListener("keyup", () => { + if (editor.children.length === 0) { + //the first
was deleted + let li: HTMLElement = document.createElement("LI"); + unedited = true; + editor.appendChild(li); + //reset cursor position + let range: Range = document.createRange(); + range.setStart(li, 0); + range.collapse(true); + let selection: Selection = window.getSelection(); + selection.removeAllRanges(); + selection.addRange(range); + } + refresh_html(); +}); + document.addEventListener("click", (e: MouseEvent) => { if (e.target === editor && unedited) { (editor.children[0] as HTMLElement).innerText = ""; unedited = false; refresh_html(); - } else if (e.target !== editor && editor.innerText.trim() === "") { + } else if (e.target !== editor && editor.innerText.trim() === "" && editor.children.length === 1) { (editor.children[0] as HTMLElement).innerText = "markdown goes here..."; unedited = true; } @@ -44,9 +66,42 @@ const theme_change = () => { dark_theme_toggle.addEventListener("change", theme_change); -refresh_html(); -theme_change(); - -if (editor.innerText !== "markdown goes here...") { +//applies if the browser remembers what the user typed in +if (editor.innerText.trim() !== "markdown goes here...") { unedited = false; } + +//give a quick intro to markdown +let params: URLSearchParams = new URLSearchParams(window.location.search); +if (params.get("help") === "true") { + (editor.children[0] as HTMLElement).innerText = "# Makoto Markdown Parser"; + let extra_lines: string[] = [ + "This markdown parser is powered by spaghetti. You can have **bold text** or *italic text*, and even ^superscripts!^", + "Of course, you can have [links](https://en.wikipedia.org), and use backslashes to \*escape\*. Here's a list:", + "- uno", + "- dos", + "- tres", + "> ## Wow! A blockquote!", + "> Reasons why blockquotes are cool:", + "> 1. They are blocks", + "> 2. They are also quotes", + "Now here's some `code`!!!!", + "```rust", + "fn main() {", + " println!('HOLA MUNDO');", + "}", + "```", + "---", + "" + ]; + for (let i=0; i < extra_lines.length; i++) { + let additional_line: HTMLElement = document.createElement("LI"); + //needs to be .innerHTML so the html space entity can be parsed + additional_line.innerHTML = extra_lines[i]; + editor.appendChild(additional_line); + } + // +} + +refresh_html(); +theme_change();