a recusive descent markdown parser in PHP
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

393 lines
8.4 KiB

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>parkdown playground</title>
<script type="module">
const $ = selector => {
const elements = document.querySelectorAll(selector);
return elements.length < 2 ? elements[0] : elements;
};
const output = $("#output");
const input = $("#input");
const numbers = $(".linenumbers");
let timeout;
const updateOutput = (event) => {
const text = event ? event.target.value : input.value;
const lines = text.split("\n").length;
let html = "";
for (let i = 1; i <= lines; i++) {
html += `${i}<br>`;
}
numbers.innerHTML = html;
timeout = setTimeout(() => {
fetch("ajax.php", {
method : "POST",
headers: {
"Content-Type" : "application/json"
},
body : text
}).then(response => response.text()).then(text => output.innerHTML = text);
}, 1000);
}
document.addEventListener("DOMContentLoaded", () => {
updateOutput();
input.addEventListener("keydown", event => {
if (event.key !== "Tab")
return;
event.preventDefault();
const start = input.selectionStart;
const end = input.selectionEnd;
const value = input.value;
input.value = value.substring(0, start) + "\t" + value.substring(end);
input.selectionStart = input.selectionEnd = (start + 1);
});
input.addEventListener("input", event => {
clearTimeout(timeout);
updateOutput(event);
});
});
window.highlight = (col, row) => {
const lines = input.value.split("\n");
let start = 0;
let end = 0;
let i = 0;
for (const line of lines) {
if (++i === row) {
end = start + line.length;
break;
}
start += line.length + 1;
}
start = start + col - 1 === end ? start : start + col;
input.focus();
input.setSelectionRange(start, end);
const lineHeight = input.clientHeight / lines.length;
$(".editor").scrollTop = lineHeight * (row - 10);
input.scrollLeft = 0;
};
</script>
<style rel="stylesheet">
* {
box-sizing: border-box;
outline: 0 !important;
}
a {
color: dodgerblue;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
a.error {
color: palevioletred;
}
body {
display: grid;
grid-template-columns: 1fr 1fr;
font-family: sans-serif;
margin: 0;
padding: 0;
height: 100vh;
overflow: hidden;
background: #333;
color: #eee;
}
body > * {
width: 100%;
border: solid 1px #111;
padding: 0;
margin: 0;
}
textarea {
width: 100%;
height: 100%;
font-family: monospace;
box-sizing: content-box;
background: transparent;
tab-size: 4;
border: none;
padding-bottom: 4rem;
font-size: 1.2rem;
overflow-y: clip;
overflow-x: scroll;
white-space: pre;
resize: none;
margin: 1rem 0;
color: #FAF08B;
}
textarea::selection {
background-color: dodgerblue;
color: white;
}
.editor {
display: grid;
grid-template-columns: 80px auto;
gap: 2rem;
height: 100%;
border-right: none;
overflow-y: auto;
overflow-x: hidden;
}
.linenumbers {
font-size: 1.2rem;
text-align: right;
padding: 1rem;
color: #aaa;
font-family: monospace;
background: #222;
}
#output {
overflow-y: auto;
padding: 4rem;
max-width: 100%;
}
#output code {
word-break: break-word;
white-space: break-spaces;
}
#output img {
max-width: 100%;
height: auto;
}
::-webkit-scrollbar {
background-color: transparent;
width: 16px;
}
::-webkit-scrollbar-track {
background-color: transparent;
}
::-webkit-scrollbar-thumb {
background-color: rgba(255,255,255,0.1);
border-radius: 16px;
border: 4px solid #333;
}
::-webkit-scrollbar-button {
display:none;
}
@media (max-width: 920px) {
body {
grid-template-columns: 1fr !important;
grid-template-rows: 1fr 1fr;
}
textarea, .linenumbers {
font-size: 1rem;
}
.linenumbers {
padding: 0.5rem;
}
.editor {
grid-template-columns: 50px auto;
gap: 1rem;
}
}
</style>
</head>
<body>
<section class="editor">
<section class="linenumbers">1</section>
<textarea id="input">
# Parkdown
a simple recursive descent Markdown parser for PHP *(version >= 8.1)*
![Markdown is a simple markup language](https://git.mike-ochmann.de/MassiveDynamic/Parkdown/raw/branch/master/docs/logo_parkdown.svg)
## Specification
### Index
* [Block types](#supported_block_types)
* [Inline types](#supported_inline_types)
* [Examples](#examples)
* [Paragraphs](#paragraphs)
* [Images](#images)
* [Horizontal Rules](#horizontal_rules)
* [Block quotes](#block_quotes)
* [Code blocks](#code_blocks)
* [Tables](#tables)
* [References](#references)
* [Usage](#usage)
* [Testing](#testing)
### Supported block types
Parkdown currently support the following block types:
* codeblocks *(with the ability to specify a language for the code block)*
* tables *(with alignment specification)*
* paragraphs
* block quotes
* lists *(like this one)*
* also nested
* horizontal rules `---`
### Supported inline types
Parkdown currently support the following block types:
* bold text (`**bold**`)
* italic text (`*italic*`)
* code snippets
* images (`![alt text](src url)`)
* links (`[link text][url or reference]`)
### Additional functionality
* references (`[marker]: URL`)
## Examples
### Paragraphs
```markdown
A simple paragraph can contain **bold text**, `inline codeblocks` and *italic text*. We can also link [with a direct url][https://google.com] *(i.e. to google)*
or via reference to [a later defined url][massivedynamic], if we so desire.
```
A simple paragraph can contain **bold text**, `inline codeblocks` and *italic text*. We can also link [with a direct url](https://google.com) *(i.e. to google)*
or via reference to [a later defined url][massivedynamic], if we so desire.
Paragraphs can be annotated with `id` and `class` attributes:
```markdown
Paragraphs can be annotated with ids and classes {.thisIsAClass, .anotherClass, #thisIsAnID}
```
results in
Paragraphs can be annotated with ids and classes {.thisIsAClass, .anotherClass, #thisIsAnID}
```html
<p class="thisIsAClass anotherClass" id="thisIsAnID">
Paragraphs can be annotated with ids and classes
</p>
```
### Images
```markdown
![this is an alt text](https://images.unsplash.com/photo-1571171637578-41bc2dd41cd2?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&h=300&w=1740&q=80\)
```
![this is an alt text](https://images.unsplash.com/photo-1571171637578-41bc2dd41cd2?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&h=300&w=1740&q=80)
### Horizontal rules
```markdown
---
```
---
### Block quotes
```markdown
> Only two things are infinite,
> the universe and human stupidity,
> i am not totally shure about the universe, though...
> – Albert Einstein
```
> Only two things are infinite,
> the universe and human stupidity,
> i am not totally shure about the universe, though...
> Albert Einstein
### Code blocks
```markdown
\`\`\`php
function main(int $argc, array $argv) : int {
echo "Hello World!";
return 0;
}
\`\`\`
```
```php
function main(int $argc, array $argv) : int {
echo "Hello World!";
return 0;
}
```
### Tables
```markdown
| Product name | Amount | Price |
|--------------|:--------:|-------:|
| Football | 7 | $18,00 |
| Golfball | 122 | $7,00 |
| Fooseball | 355 | $1,00 |
| Puck | 58 | $12,00 |
```
| Product name | Amount | Price |
|--------------|:--------:|-------:|
| Football | 7 | $18,00 |
| Golfball | 122 | $7,00 |
| Fooseball | 355 | $1,00 |
| Puck | 58 | $12,00 |
### References
```markdown
[massivedynamic]: https://massivedynamic.eu
```
[massivedynamic]: https://massivedynamic.eu
## Usage
Simply construct an new `parkdown\Parkdown` object and pass the Markdown source code to it's constructor. The parsed `DOMDocument` or it's `HTML` output can then be retrieved through the `::html()` and `::tree()` member functions.
**Example**
```php
use parkdown\Parkdown;
$source = "
This is a **bold** word in a paragraph.
";
$parser = new Parkdown($source);
$tree = $parser->tree();
print_r($tree);
echo $parser->html();
```
## Testing
Unit tests can be run via `composer`:
```
composer test
```
</textarea>
</section>
<section id="output"></section>
</body>
</html>