Compare commits
No commits in common. 'development' and 'master' have entirely different histories.
developmen
...
master
11 changed files with 75 additions and 732 deletions
@ -1,35 +0,0 @@ |
|||||||
<?php declare(strict_types=1); |
|
||||||
|
|
||||||
require __DIR__."/../vendor/autoload.php"; |
|
||||||
|
|
||||||
$source = file_get_contents("php://input"); |
|
||||||
|
|
||||||
try { |
|
||||||
$Instance = new parkdown\Parkdown($source, false); |
|
||||||
echo $Instance->html(); |
|
||||||
} catch (parkdown\ParserError $error) { |
|
||||||
echo "<pre>"; |
|
||||||
|
|
||||||
$message = explode(" ", $error->getMessage()); |
|
||||||
$location = array_shift($message); |
|
||||||
$loc = explode(":", $location); |
|
||||||
$file = array_shift($loc); |
|
||||||
$line = substr(implode(":", $loc), 0, -1); |
|
||||||
[$row, $col] = explode(":", $line); |
|
||||||
|
|
||||||
echo "<a class='error' href=\"javascript: highlight($col, $row);\">$location</a> ".implode(" ", $message); |
|
||||||
|
|
||||||
|
|
||||||
$stackTrace = explode("\n", $error->getTraceAsString()); |
|
||||||
|
|
||||||
echo "<p><small>"; |
|
||||||
foreach ($stackTrace as $step) { |
|
||||||
$step = explode(" ", $step); |
|
||||||
array_shift($step); |
|
||||||
$location = array_shift($step); |
|
||||||
$location = preg_replace("/\(([0-9]+)\):/", ":\$1:", $location); |
|
||||||
|
|
||||||
echo "<a class='error' href='vscode://file/".substr($location, 0, -1)."'>$location</a> ".implode(" ", $step)."<br>"; |
|
||||||
} |
|
||||||
echo "</small></p>"; |
|
||||||
} |
|
@ -1,473 +0,0 @@ |
|||||||
<!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"); |
|
||||||
const position = $("#position"); |
|
||||||
const percent = $("#percent"); |
|
||||||
const editor = $(".editor"); |
|
||||||
let lastLine = 1; |
|
||||||
let timeout; |
|
||||||
let lastSelection; |
|
||||||
|
|
||||||
const onLineChange = event => { |
|
||||||
const start = input.selectionStart; |
|
||||||
if (start === lastSelection) |
|
||||||
return; |
|
||||||
|
|
||||||
const linesToCursor = input.value.substr(0, start).split("\n"); |
|
||||||
const currentLine = linesToCursor.length; |
|
||||||
const char = linesToCursor[linesToCursor.length - 1].length; |
|
||||||
const ll = $(`#line_${lastLine}`); |
|
||||||
if (ll) |
|
||||||
ll.classList.remove("active"); |
|
||||||
$(`#line_${currentLine}`).classList.add("active"); |
|
||||||
|
|
||||||
position.innerHTML = `[${currentLine}:${char}]`; |
|
||||||
|
|
||||||
lastLine = currentLine; |
|
||||||
lastSelection = start; |
|
||||||
} |
|
||||||
|
|
||||||
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 += `<span id="line_${i}">${i}</span>`; |
|
||||||
} |
|
||||||
numbers.innerHTML = html; |
|
||||||
onLineChange(); |
|
||||||
|
|
||||||
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(); |
|
||||||
|
|
||||||
for (event of ["click", "change", "keydown", "focus"]) |
|
||||||
input.addEventListener(event, () => onLineChange()); |
|
||||||
|
|
||||||
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); |
|
||||||
}); |
|
||||||
|
|
||||||
editor.addEventListener("scroll", () => { |
|
||||||
const height = input.clientHeight - editor.clientHeight; |
|
||||||
const top = editor.scrollTop; |
|
||||||
|
|
||||||
const fromTop = Math.min(100, Math.max(0, Math.round(top * 100 / height))); |
|
||||||
percent.innerHTML = `${fromTop}%`; |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
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; |
|
||||||
grid-template-rows: auto 30px; |
|
||||||
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; |
|
||||||
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; |
|
||||||
border-bottom: none; |
|
||||||
overflow-x: hidden; |
|
||||||
} |
|
||||||
|
|
||||||
.linenumbers { |
|
||||||
font-size: 1.2rem; |
|
||||||
text-align: right; |
|
||||||
padding: 1rem 0; |
|
||||||
color: #aaa; |
|
||||||
font-family: monospace; |
|
||||||
background: #222; |
|
||||||
} |
|
||||||
.linenumbers span { |
|
||||||
display: block; |
|
||||||
padding: 0 1rem; |
|
||||||
} |
|
||||||
.linenumbers span.active { |
|
||||||
color: yellow; |
|
||||||
background: rgba(255,255,255,0.05); |
|
||||||
} |
|
||||||
|
|
||||||
.statusbar { |
|
||||||
font-size: 0.6rem; |
|
||||||
line-height: 30px; |
|
||||||
padding: 0 1rem; |
|
||||||
background: rgba(0,0,0,0.2); |
|
||||||
text-align: right; |
|
||||||
border: none; |
|
||||||
color: #888; |
|
||||||
} |
|
||||||
.statusbar > * { |
|
||||||
margin-left: 0.5rem; |
|
||||||
} |
|
||||||
|
|
||||||
#position { |
|
||||||
color: dodgerblue; |
|
||||||
font-weight: bold; |
|
||||||
} |
|
||||||
|
|
||||||
#output { |
|
||||||
overflow-y: auto; |
|
||||||
padding: 4rem; |
|
||||||
max-width: 100%; |
|
||||||
grid-row: span 2; |
|
||||||
} |
|
||||||
#output code { |
|
||||||
word-break: break-word; |
|
||||||
white-space: break-spaces; |
|
||||||
} |
|
||||||
|
|
||||||
#output table { |
|
||||||
width: 100%; |
|
||||||
} |
|
||||||
|
|
||||||
#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; |
|
||||||
} |
|
||||||
|
|
||||||
textarea { |
|
||||||
margin: 0.5rem 0; |
|
||||||
} |
|
||||||
.linenumbers { |
|
||||||
padding: 0.5rem; |
|
||||||
} |
|
||||||
|
|
||||||
.editor { |
|
||||||
grid-template-columns: 50px auto; |
|
||||||
gap: 1rem; |
|
||||||
} |
|
||||||
|
|
||||||
#output { |
|
||||||
padding: 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)* |
|
||||||
|
|
||||||
 |
|
||||||
|
|
||||||
## 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 (``) |
|
||||||
* 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 |
|
||||||
 |
|
||||||
``` |
|
||||||
|
|
||||||
 |
|
||||||
|
|
||||||
### 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> |
|
||||||
<section class="statusbar"> |
|
||||||
<span id="percent">0%</span> |
|
||||||
<span id="position">[1:2]</span> |
|
||||||
</section> |
|
||||||
|
|
||||||
</body> |
|
||||||
</html> |
|
@ -1,7 +0,0 @@ |
|||||||
<?php declare(strict_types=1); |
|
||||||
|
|
||||||
namespace parkdown; |
|
||||||
|
|
||||||
use AssertionError; |
|
||||||
|
|
||||||
class ParserError extends AssertionError {} |
|
Loading…
Reference in new issue