diff --git a/README.md b/README.md
new file mode 100644
index 0000000..1d6dae9
--- /dev/null
+++ b/README.md
@@ -0,0 +1,52 @@
+# Makoto Markdown to HTML
+The Makoto parser converts markdown to HTML, powered by several hundred lines of spaghetti code. Specifically, it is a state machine (I think) that processes the markdown character by character.
+
+Makoto uses the MIT license.
+
+## Web Editor
+A web markdown editor (output in HTML, of course), is available at [makoto.prussia.dev](https://makoto.prussia.dev) or [makoto.pages.dev](https://makoto.pages.dev).
+
+### Query Params
+The web editor features various query params.
+
+- `help`: if "true", will display a quick intro to Makoto-flavoured markdown
+- `save`: if "true", will save the user inputted markdown to local storage, and will retrieve any existing saved text from local storage
+- `ignore`: if "all", all warnings will be ignored. Else, it can be a CSV (comma separated values) of warning types to ignore
+
+Example: [https://makoto.prussia.dev/?help=true&ignore=unknown-language,empty-link](https://makoto.prussia.dev/?help=true&ignore=unknown-language,empty-link)
+
+## HTML Output
+Some example CSS to style the HTML output can be found in `styles/makoto.css`.
+
+- Headings are given automatically generated ids ('header-0', 'header-1' etc) so url anchors (https://example.com/blog#header-1) are possible
+- Newlines will be put only after headings, paragraphs, and horizontal rules, all others will not be in final output (exceptions: no newlines in the last line, and newlines in code blocks are preserved)
+- Spaces at the beginning of the line are normally cut off in html, so spaces will be replaced with the html entity for spaces ( ) at the beginning of the line in code blocks
+- Lines in code blocks will be split with
s
+- If a language for the code block is provided, the parser will add a css class `code-` to the resulting code block div (which btw has class `code-block`)
+
+## Notable Differences From Standard Markdown
+- Only one newline between text is needed for a new paragraph, not two
+- Superscripts are supported! "^1^" become "1". Use these for footnotes too, I'm not implementing those
+- Underlined headers are not supported, just use a header and a horizontal rule
+- Nested blockquotes and lists won't work
+- The first row of a table will be assumed to be the header row, and don't bother with a row of dashes after it.
+- Also, the pipe ("|") in tables is mandatory at the end of the row, otherwise weird things will happen
+- Bold, italic, and other elements are not supported in tables
+
+## List of Warning Types
+- `unknown-language`
+- `image-incomplete`
+- `link-incomplete`
+- `italic-not-closed`
+- `bold-not-closed`
+- `superscript-not-closed`
+- `blockquote-broken`
+- `code-block-not-closed`
+- `unordered-list-broken`
+- `code-snippet-not-closed`
+- `too-much-header`
+- `heading-broken`
+- `horizontal-rule-broken`
+- `missing-image-alt`
+- `empty-link`
+- `weird-href`
diff --git a/index.html b/index.html
index fa8a089..ab762fe 100644
--- a/index.html
+++ b/index.html
@@ -16,7 +16,14 @@
diff --git a/makoto.ts b/makoto.ts
index ae886d6..fe6bf6d 100644
--- a/makoto.ts
+++ b/makoto.ts
@@ -471,7 +471,7 @@ export function parse_md_to_html_with_warnings(md: string): ParseResult {
blockquote_list = true;
}
continue;
- } else if (char !== " " && chars[i-1] === "-" && (chars[i-2] === "\n" || i === 1)) {
+ } else if (char !== " " && char !== "-" && chars[i-1] === "-" && (chars[i-2] === "\n" || i === 1)) {
warnings.push({
type: "unordered-list-broken",
message: "Missing space after unordered list",
diff --git a/styles/style.css b/styles/style.css
index 87f1c62..7504a8c 100644
--- a/styles/style.css
+++ b/styles/style.css
@@ -26,7 +26,7 @@ html, body {
#editor {
box-sizing: border-box;
width: 95%;
- width: calc(100% - 58px);
+ width: 100%;
height: 95%;
margin: 0;
word-break: break-all;
@@ -56,15 +56,32 @@ html, body {
color: #2d2d2d;
}
-.line-warning {
+#preview-tools {
+ font-family: Arial, sans-serif;
+ float: left;
+ width: 100%;
+}
+
+.line-warning::before {
position: relative;
display: inline-block;
width: 0;
left: -58px;
margin-top: -1px;
font-size: 0.9em;
-}
-
-.line-warning::after {
content: "⚠️";
}
+
+.mobile-only {
+ display: none;
+}
+
+#see-docs {
+ float: right;
+}
+
+@media screen and (max-width: 950px) {
+ .mobile-only {
+ display: block;
+ }
+}
diff --git a/web.ts b/web.ts
index 2c27576..4136026 100644
--- a/web.ts
+++ b/web.ts
@@ -2,7 +2,6 @@ import { parse_md_to_html_with_warnings, ParseResult } from './makoto.js';
import type { Warning } from './endosulfan.js';
/*todo:
-- FIX not being able to delete at start of line when there is warning on the line
- show multiple warnings per line
- show warnings that do not have line number
- show warning type, ability to ignore warnings based on type
@@ -12,18 +11,24 @@ import type { Warning } from './endosulfan.js';
let editor: HTMLTextAreaElement = document.getElementById("editor")! as HTMLTextAreaElement;
let preview: HTMLElement = document.getElementById("rendered-text")!;
let dark_theme_toggle: HTMLInputElement = document.getElementById("dark-theme-toggle")! as HTMLInputElement;
+let html_toggle: HTMLInputElement = document.getElementById("html-toggle")! as HTMLInputElement;
+let show_html: boolean = false;
let unedited: boolean = true;
+let use_local_storage: boolean = false;
+
+let ignore_all_warnings: boolean = false;
+let ignore_warnings: string[] = [];
+
function render_warnings(warnings: Warning[]) {
- document.querySelectorAll(".line-warning").forEach((item: Element) => item.remove());
+ document.querySelectorAll(".line-warning").forEach((item: Element) => item.classList.remove("line-warning"));
warnings.forEach((warning: Warning) => {
+ if (ignore_warnings.includes(warning.type)) return;
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]);
+ let line: HTMLElement = editor.children[warning.line_number-1] as HTMLElement;
+ line.classList.add("line-warning");
+ line.title = warning.message;
}
});
}
@@ -31,9 +36,18 @@ function render_warnings(warnings: Warning[]) {
const refresh_html = () => {
//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);
+ if (use_local_storage) {
+ localStorage.setItem("markdown", editor_text);
+ }
let parsed: ParseResult = parse_md_to_html_with_warnings(editor_text);
- render_warnings(parsed.warnings);
- preview.innerHTML = parsed.html;
+ if (!ignore_all_warnings) {
+ render_warnings(parsed.warnings);
+ }
+ if (show_html) {
+ preview.innerText = parsed.html;
+ } else {
+ preview.innerHTML = parsed.html;
+ }
};
editor.addEventListener("keyup", () => {
@@ -74,14 +88,24 @@ const theme_change = () => {
dark_theme_toggle.addEventListener("change", theme_change);
+const html_change = () => {
+ let changed: boolean = show_html !== html_toggle.checked;
+ if (changed) {
+ show_html = show_html ? false : true;
+ refresh_html();
+ }
+}
+
+html_toggle.addEventListener("change", html_change);
+
//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") {
+ //give a quick intro to markdown
(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!^",
@@ -108,8 +132,36 @@ if (params.get("help") === "true") {
additional_line.innerHTML = extra_lines[i];
editor.appendChild(additional_line);
}
- //
+}
+
+if (params.get("save") === "true") {
+ //save to local storage, and retrieve from it
+ if (localStorage) {
+ use_local_storage = true;
+ }
+ let stored_markdown: string = localStorage.getItem("markdown");
+ if (stored_markdown) {
+ let extra_lines: string[] = stored_markdown.split("\n");
+ (editor.children[0] as HTMLElement).innerText = extra_lines.shift();
+ 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);
+ }
+ unedited = false;
+ }
+}
+
+if (params.get("ignore")) {
+ //allow user to specify warnings to ignore
+ if (params.get("ignore") === "all") {
+ ignore_all_warnings = true;
+ } else {
+ ignore_warnings = params.get("ignore").split(",");
+ }
}
refresh_html();
theme_change();
+html_change();