new query param features, fixes
also added README
This commit is contained in:
52
README.md
Normal file
52
README.md
Normal file
@@ -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 <br>s
|
||||||
|
- If a language for the code block is provided, the parser will add a css class `code-<language name>` 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 "<sup>1</sup>". 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`
|
||||||
@@ -16,7 +16,14 @@
|
|||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
<div id="preview-container" class="section">
|
<div id="preview-container" class="section">
|
||||||
<label for="dark-theme-toggle">Dark Theme</label><input id="dark-theme-toggle" type="checkbox">
|
<div id="preview-tools">
|
||||||
|
<label for="dark-theme-toggle">Dark Theme</label><input id="dark-theme-toggle" type="checkbox">
|
||||||
|
<br class="mobile-only">
|
||||||
|
<label for="html-toggle">Show HTML</label><input id="html-toggle" type="checkbox">
|
||||||
|
<br class="mobile-only">
|
||||||
|
<a id="see-docs" href="https://github.com/jetstream0/Makoto-Markdown-to-HTML/blob/master/README.md" target="_blank" rel="noopener noreferrer">See Docs</a>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
<div id="rendered-text">
|
<div id="rendered-text">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -471,7 +471,7 @@ export function parse_md_to_html_with_warnings(md: string): ParseResult {
|
|||||||
blockquote_list = true;
|
blockquote_list = true;
|
||||||
}
|
}
|
||||||
continue;
|
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({
|
warnings.push({
|
||||||
type: "unordered-list-broken",
|
type: "unordered-list-broken",
|
||||||
message: "Missing space after unordered list",
|
message: "Missing space after unordered list",
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ html, body {
|
|||||||
#editor {
|
#editor {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
width: 95%;
|
width: 95%;
|
||||||
width: calc(100% - 58px);
|
width: 100%;
|
||||||
height: 95%;
|
height: 95%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
@@ -56,15 +56,32 @@ html, body {
|
|||||||
color: #2d2d2d;
|
color: #2d2d2d;
|
||||||
}
|
}
|
||||||
|
|
||||||
.line-warning {
|
#preview-tools {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
float: left;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-warning::before {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 0;
|
width: 0;
|
||||||
left: -58px;
|
left: -58px;
|
||||||
margin-top: -1px;
|
margin-top: -1px;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
}
|
|
||||||
|
|
||||||
.line-warning::after {
|
|
||||||
content: "⚠️";
|
content: "⚠️";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mobile-only {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#see-docs {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 950px) {
|
||||||
|
.mobile-only {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
74
web.ts
74
web.ts
@@ -2,7 +2,6 @@ import { parse_md_to_html_with_warnings, ParseResult } from './makoto.js';
|
|||||||
import type { Warning } from './endosulfan.js';
|
import type { Warning } from './endosulfan.js';
|
||||||
|
|
||||||
/*todo:
|
/*todo:
|
||||||
- FIX not being able to delete at start of line when there is warning on the line
|
|
||||||
- show multiple warnings per line
|
- show multiple warnings per line
|
||||||
- show warnings that do not have line number
|
- show warnings that do not have line number
|
||||||
- show warning type, ability to ignore warnings based on type
|
- 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 editor: HTMLTextAreaElement = document.getElementById("editor")! as HTMLTextAreaElement;
|
||||||
let preview: HTMLElement = document.getElementById("rendered-text")!;
|
let preview: HTMLElement = document.getElementById("rendered-text")!;
|
||||||
let dark_theme_toggle: HTMLInputElement = document.getElementById("dark-theme-toggle")! as HTMLInputElement;
|
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 unedited: boolean = true;
|
||||||
|
|
||||||
|
let use_local_storage: boolean = false;
|
||||||
|
|
||||||
|
let ignore_all_warnings: boolean = false;
|
||||||
|
let ignore_warnings: string[] = [];
|
||||||
|
|
||||||
function render_warnings(warnings: Warning[]) {
|
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) => {
|
warnings.forEach((warning: Warning) => {
|
||||||
|
if (ignore_warnings.includes(warning.type)) return;
|
||||||
if (warning.line_number) {
|
if (warning.line_number) {
|
||||||
let line: Element = editor.children[warning.line_number-1];
|
let line: HTMLElement = editor.children[warning.line_number-1] as HTMLElement;
|
||||||
let warning_span: HTMLElement = document.createElement("SPAN");
|
line.classList.add("line-warning");
|
||||||
warning_span.classList.add("line-warning");
|
line.title = warning.message;
|
||||||
warning_span.title = warning.message;
|
|
||||||
line.insertBefore(warning_span, line.childNodes[0]);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -31,9 +36,18 @@ function render_warnings(warnings: Warning[]) {
|
|||||||
const refresh_html = () => {
|
const refresh_html = () => {
|
||||||
//go through the lines and get the editor text
|
//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 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);
|
let parsed: ParseResult = parse_md_to_html_with_warnings(editor_text);
|
||||||
render_warnings(parsed.warnings);
|
if (!ignore_all_warnings) {
|
||||||
preview.innerHTML = parsed.html;
|
render_warnings(parsed.warnings);
|
||||||
|
}
|
||||||
|
if (show_html) {
|
||||||
|
preview.innerText = parsed.html;
|
||||||
|
} else {
|
||||||
|
preview.innerHTML = parsed.html;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
editor.addEventListener("keyup", () => {
|
editor.addEventListener("keyup", () => {
|
||||||
@@ -74,14 +88,24 @@ const theme_change = () => {
|
|||||||
|
|
||||||
dark_theme_toggle.addEventListener("change", 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
|
//applies if the browser remembers what the user typed in
|
||||||
if (editor.innerText.trim() !== "markdown goes here...") {
|
if (editor.innerText.trim() !== "markdown goes here...") {
|
||||||
unedited = false;
|
unedited = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//give a quick intro to markdown
|
|
||||||
let params: URLSearchParams = new URLSearchParams(window.location.search);
|
let params: URLSearchParams = new URLSearchParams(window.location.search);
|
||||||
if (params.get("help") === "true") {
|
if (params.get("help") === "true") {
|
||||||
|
//give a quick intro to markdown
|
||||||
(editor.children[0] as HTMLElement).innerText = "# Makoto Markdown Parser";
|
(editor.children[0] as HTMLElement).innerText = "# Makoto Markdown Parser";
|
||||||
let extra_lines: string[] = [
|
let extra_lines: string[] = [
|
||||||
"This markdown parser is powered by spaghetti. You can have **bold text** or *italic text*, and even ^superscripts!^",
|
"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];
|
additional_line.innerHTML = extra_lines[i];
|
||||||
editor.appendChild(additional_line);
|
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();
|
refresh_html();
|
||||||
theme_change();
|
theme_change();
|
||||||
|
html_change();
|
||||||
|
|||||||
Reference in New Issue
Block a user