Compare commits
No commits in common. 'feature/strict-mode' and 'master' have entirely different histories.
feature/st
...
master
10 changed files with 74 additions and 731 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