mirror of
https://github.com/Karaka-Management/phpOMS.git
synced 2026-01-16 03:58:39 +00:00
add tests + some new markdown functionality
This commit is contained in:
parent
36b7143829
commit
bbb9d06f29
|
|
@ -32,6 +32,23 @@ use phpOMS\Uri\UriFactory;
|
|||
* @see https://github.com/BenjaminHoegh/ParsedownExtended
|
||||
* @see https://github.com/doowzs/parsedown-extreme
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @todo: Add
|
||||
* 2. Calendar (own widget)
|
||||
* 3. Event (own widget)
|
||||
* 4. Tasks (own widget)
|
||||
* 5. Vote/Survey (own widget)
|
||||
* 6. Website link/embed widgets (facebook, linkedIn, twitter, ...)
|
||||
* 7. User/Supplier/Client/Employee (own widget, should make use of schema)
|
||||
* 8. Address (own widget, should make use of schema)
|
||||
* 9. Contact (own widget, should make use of schema)
|
||||
* 10. Item (own widget, should make use of schema)
|
||||
* 11. Progress radial
|
||||
* 12. Timeline horizontal/vertical/matrix
|
||||
* 14. Tabs horizontal/vertical
|
||||
* 15. Checklist (own widget)
|
||||
* 16. Gallery
|
||||
* 17. Form (own widget)
|
||||
*/
|
||||
class Markdown
|
||||
{
|
||||
|
|
@ -70,7 +87,7 @@ class Markdown
|
|||
* @since 1.0.0
|
||||
*/
|
||||
protected array $underlineRegex = [
|
||||
'_' => '/^__((?:\\\\_|[^_]|_[^_]*+_)+?)__(?!_)/us',
|
||||
'_' => '/^[_]{2}((?:\\\\\_|[^_]|[_][^_]_+[_])+?)[_]{2}(?![_])/s',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -81,7 +98,6 @@ class Markdown
|
|||
*/
|
||||
protected array $emRegex = [
|
||||
'*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s',
|
||||
'_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -275,7 +291,7 @@ class Markdown
|
|||
* @since 1.0.0
|
||||
*/
|
||||
private const CONTINUABLE = [
|
||||
'Code', 'Comment', 'FencedCode', 'List', 'Quote', 'Table', 'Math', 'Checkbox', 'Footnote', 'DefinitionList', 'Markup'
|
||||
'Code', 'Comment', 'FencedCode', 'List', 'Quote', 'Table', 'Math', 'Spoiler', 'Checkbox', 'Footnote', 'DefinitionList', 'Markup'
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -285,7 +301,7 @@ class Markdown
|
|||
* @since 1.0.0
|
||||
*/
|
||||
private const COMPLETABLE = [
|
||||
'Math', 'Table', 'Checkbox', 'Footnote', 'Markup', 'Code', 'FencedCode', 'List'
|
||||
'Math', 'Spoiler', 'Table', 'Checkbox', 'Footnote', 'Markup', 'Code', 'FencedCode', 'List'
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -424,7 +440,7 @@ class Markdown
|
|||
|
||||
// Marks
|
||||
if ($this->options['mark'] ?? true) {
|
||||
$this->inlineTypes['='][] = 'mark';
|
||||
$this->inlineTypes['='][] = 'Mark';
|
||||
$this->inlineMarkerList .= '=';
|
||||
}
|
||||
|
||||
|
|
@ -434,6 +450,12 @@ class Markdown
|
|||
$this->inlineMarkerList .= '[';
|
||||
}
|
||||
|
||||
// Spoiler
|
||||
if ($this->options['spoiler'] ?? false) {
|
||||
$this->inlineTypes['>'][] = 'Spoiler';
|
||||
$this->inlineMarkerList .= '=';
|
||||
}
|
||||
|
||||
// Inline Math
|
||||
if ($this->options['math'] ?? false) {
|
||||
$this->inlineTypes['\\'][] = 'Math';
|
||||
|
|
@ -473,32 +495,19 @@ class Markdown
|
|||
$this->inlineMarkerList .= '?';
|
||||
}
|
||||
|
||||
// Smartypants
|
||||
if ($this->options['smarty'] ?? false) {
|
||||
$this->inlineTypes['<'][] = 'Smartypants';
|
||||
$this->inlineMarkerList .= '<';
|
||||
$this->inlineTypes['>'][] = 'Smartypants';
|
||||
$this->inlineMarkerList .= '>';
|
||||
$this->inlineTypes['-'][] = 'Smartypants';
|
||||
$this->inlineMarkerList .= '-';
|
||||
$this->inlineTypes['.'][] = 'Smartypants';
|
||||
$this->inlineMarkerList .= '.';
|
||||
$this->inlineTypes["'"][] = 'Smartypants';
|
||||
$this->inlineMarkerList .= "'";
|
||||
$this->inlineTypes['"'][] = 'Smartypants';
|
||||
$this->inlineMarkerList .= '"';
|
||||
$this->inlineTypes['`'][] = 'Smartypants';
|
||||
$this->inlineMarkerList .= '`';
|
||||
}
|
||||
|
||||
// Block Math
|
||||
if ($this->options['math'] ?? false) {
|
||||
$this->blockTypes['\\'][] = 'Math';
|
||||
$this->blockTypes['$'][] = 'Math';
|
||||
}
|
||||
|
||||
// Task
|
||||
if ($this->options['lists']['tasks'] ?? true) {
|
||||
// Block Spoiler
|
||||
if ($this->options['spoiler'] ?? false) {
|
||||
$this->blockTypes['?'][] = 'Spoiler';
|
||||
}
|
||||
|
||||
// Checkbox
|
||||
if ($this->options['lists']['checkbox'] ?? true) {
|
||||
$this->blockTypes['['][] = 'Checkbox';
|
||||
}
|
||||
|
||||
|
|
@ -507,6 +516,24 @@ class Markdown
|
|||
$this->inlineTypes['['][] = 'Embeding';
|
||||
$this->inlineMarkerList .= '[';
|
||||
}
|
||||
|
||||
// Map
|
||||
if ($this->options['map'] ?? false) {
|
||||
$this->inlineTypes['['][] = 'Map';
|
||||
$this->inlineMarkerList .= '[';
|
||||
}
|
||||
|
||||
// Address
|
||||
if ($this->options['address'] ?? false) {
|
||||
$this->inlineTypes['['][] = 'Address';
|
||||
$this->inlineMarkerList .= '[';
|
||||
}
|
||||
|
||||
// Contact
|
||||
if ($this->options['contact'] ?? false) {
|
||||
$this->inlineTypes['['][] = 'Contact';
|
||||
$this->inlineMarkerList .= '[';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -518,7 +545,7 @@ class Markdown
|
|||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function parse($text) : string
|
||||
public static function parse(string $text) : string
|
||||
{
|
||||
$parsedown = new self();
|
||||
|
||||
|
|
@ -1205,6 +1232,30 @@ class Markdown
|
|||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle marks
|
||||
*
|
||||
* @param array{text:string, context:string, before:string} $excerpt Inline data
|
||||
*
|
||||
* @return null|array{extent:int, element:array}
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function inlineSpoiler(array $excerpt) : ?array
|
||||
{
|
||||
if (\preg_match('/^>!(.*?)!</us', $excerpt['text'], $matches) !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'extent' => \strlen($matches[0]),
|
||||
'element' => [
|
||||
'name' => 'mark',
|
||||
'text' => $matches[2],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle keystrokes
|
||||
*
|
||||
|
|
@ -1322,6 +1373,232 @@ class Markdown
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle map
|
||||
*
|
||||
* @param array{text:string, context:string, before:string} $excerpt Inline data
|
||||
*
|
||||
* @return null|array{extent:int, element:array}
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function inlineMap(array $excerpt) : ?array
|
||||
{
|
||||
if (!($this->options['map'] ?? false)
|
||||
|| (\preg_match('/\[map(?:\s+(?:name="([^"]+)"|country="([^"]+)"|city="([^"]+)"|zip="([^"]+)"|address="([^"]+)"|lat="([^"]+)"|lon="([^"]+)")){0,3}\]/', $excerpt['text'], $matches) !== 1)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$name = $matches[1];
|
||||
$country = $matches[2];
|
||||
$city = $matches[3];
|
||||
$zip = $matches[4];
|
||||
$address = $matches[5];
|
||||
|
||||
$lat = empty($matches[6]) ? '' : (float) $matches[6];
|
||||
$lon = empty($matches[7]) ? '' : (float) $matches[7];
|
||||
|
||||
if ($lat === '' || $lon === '') {
|
||||
[$lat, $lon] = \phpOMS\Api\Geocoding\Nominatim::geocoding($country, $city, $address, $zip);
|
||||
}
|
||||
|
||||
return [
|
||||
'extent' => \strlen($matches[0]),
|
||||
'element' => [
|
||||
'name' => 'div',
|
||||
'text' => '',
|
||||
'attributes' => [
|
||||
'id' => '-' . \bin2hex(\random_bytes(4)),
|
||||
'class' => 'map',
|
||||
'data-lat' => $lat,
|
||||
'data-lon' => $lon,
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle address
|
||||
*
|
||||
* @param array{text:string, context:string, before:string} $excerpt Inline data
|
||||
*
|
||||
* @return null|array{extent:int, element:array}
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function inlineAddress(array $excerpt) : ?array
|
||||
{
|
||||
if (!($this->options['address'] ?? false)
|
||||
|| (\preg_match('/\[addr(?:\s+(?:name="([^"]+)"|country="([^"]+)"|city="([^"]+)"|zip="([^"]+)"|address="([^"]+)")){0,3}\]/', $excerpt['text'], $matches) !== 1)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$name = $matches[1];
|
||||
$country = $matches[2];
|
||||
$city = $matches[3];
|
||||
$zip = $matches[4];
|
||||
$address = $matches[5];
|
||||
|
||||
return [
|
||||
'extent' => \strlen($matches[0]),
|
||||
'element' => [
|
||||
'name' => 'div',
|
||||
//'text' => '',
|
||||
'attributes' => [
|
||||
'class' => 'addressWidget',
|
||||
],
|
||||
'elements' => [
|
||||
[
|
||||
'name' => 'span',
|
||||
'text' => $name,
|
||||
'attributes' => ['class' => 'addressWidget-name'],
|
||||
],
|
||||
[
|
||||
'name' => 'span',
|
||||
'text' => $address,
|
||||
'attributes' => ['class' => 'addressWidget-address'],
|
||||
],
|
||||
[
|
||||
'name' => 'span',
|
||||
'text' => $zip,
|
||||
'attributes' => ['class' => 'addressWidget-zip'],
|
||||
],
|
||||
[
|
||||
'name' => 'span',
|
||||
'text' => $city,
|
||||
'attributes' => ['class' => 'addressWidget-city'],
|
||||
],
|
||||
[
|
||||
'name' => 'span',
|
||||
'text' => $country,
|
||||
'attributes' => ['class' => 'addressWidget-country'],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle contact
|
||||
*
|
||||
* @param array{text:string, context:string, before:string} $excerpt Inline data
|
||||
*
|
||||
* @return null|array{extent:int, element:array}
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function inlineContact(array $excerpt) : ?array
|
||||
{
|
||||
if (!($this->options['contact'] ?? false)
|
||||
|| (\preg_match('/\[contact.*?([a-zA-Z]+)="([a-zA-Z0-9\-_]+)"\]/', $excerpt['text'], $matches) !== 1)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$src = '';
|
||||
switch ($matches[1]) {
|
||||
case 'email':
|
||||
$src = 'Resources/icons/company/email.svg';
|
||||
break;
|
||||
case 'phone':
|
||||
$src = 'Resources/icons/company/phone.svg';
|
||||
break;
|
||||
case 'twitter':
|
||||
$src = 'Resources/icons/company/twitter.svg';
|
||||
break;
|
||||
case 'instagram':
|
||||
$src = 'Resources/icons/company/instagram.svg';
|
||||
break;
|
||||
case 'discord':
|
||||
$src = 'Resources/icons/company/discord.svg';
|
||||
break;
|
||||
case 'slack':
|
||||
$src = 'Resources/icons/company/slack.svg';
|
||||
break;
|
||||
case 'teams':
|
||||
$src = 'Resources/icons/company/teams.svg';
|
||||
break;
|
||||
case 'facebook':
|
||||
$src = 'Resources/icons/company/facebook.svg';
|
||||
break;
|
||||
case 'youtube':
|
||||
$src = 'Resources/icons/company/youtube.svg';
|
||||
break;
|
||||
case 'paypal':
|
||||
$src = 'Resources/icons/company/paypal.svg';
|
||||
break;
|
||||
}
|
||||
|
||||
return [
|
||||
'extent' => \strlen($matches[0]),
|
||||
'element' => [
|
||||
'name' => 'a',
|
||||
//'text' => '',
|
||||
'attributes' => [
|
||||
'class' => 'contactWidget',
|
||||
'href' => '',
|
||||
],
|
||||
'elements' => [
|
||||
[
|
||||
'name' => 'img',
|
||||
'attributes' => [
|
||||
'class' => 'contactWidget-icon',
|
||||
'src' => $src
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'span',
|
||||
'text' => $matches[2],
|
||||
'attributes' => ['class' => 'contactWidget-contact'],
|
||||
]
|
||||
],
|
||||
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle progress
|
||||
*
|
||||
* @param array{text:string, context:string, before:string} $excerpt Inline data
|
||||
*
|
||||
* @return null|array{extent:int, element:array}
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function inlineProgress(array $excerpt) : ?array
|
||||
{
|
||||
if (!($this->options['progress'] ?? false)
|
||||
|| (\preg_match('/\[progress(?:\s+(?:type="([^"]+)"|percent="([^"]+)"|value="([^"]+)")){0,3}\]/', $excerpt['text'], $matches) !== 1)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// $type = $matches[1] ?? 'meter';
|
||||
$percent = $matches[2] ?? ($matches[3]);
|
||||
$value = $matches[3] ?? $matches[2];
|
||||
|
||||
if ($percent === ''
|
||||
|| $value === ''
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'extent' => \strlen($matches[0]),
|
||||
'element' => [
|
||||
'name' => 'progress',
|
||||
//'text' => '',
|
||||
'attributes' => [
|
||||
'value' => $value,
|
||||
'max' => '100',
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle super script
|
||||
*
|
||||
|
|
@ -1406,140 +1683,6 @@ class Markdown
|
|||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle smartypants
|
||||
*
|
||||
* @param array{text:string, context:string, before:string} $excerpt Inline data
|
||||
*
|
||||
* @return null|array{extent:int, element:array}
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function inlineSmartypants(array $excerpt) : ?array
|
||||
{
|
||||
if (\preg_match('/(``)(?!\s)([^"\'`]{1,})(\'\')|(\")(?!\s)([^\"]{1,})(\")|(\')(?!\s)([^\']{1,})(\')|(<{2})(?!\s)([^<>]{1,})(>{2})|(\.{3})|(-{3})|(-{2})/i', $excerpt['text'], $matches) !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Substitutions
|
||||
$backtickDoublequoteOpen = $this->options['smarty']['substitutions']['left-double-quote'] ?? '“';
|
||||
$backtickDoublequoteClose = $this->options['smarty']['substitutions']['right-double-quote'] ?? '”';
|
||||
|
||||
$smartDoublequoteOpen = $this->options['smarty']['substitutions']['left-double-quote'] ?? '“';
|
||||
$smartDoublequoteClose = $this->options['smarty']['substitutions']['right-double-quote'] ?? '”';
|
||||
$smartSinglequoteOpen = $this->options['smarty']['substitutions']['left-single-quote'] ?? '‘';
|
||||
$smartSinglequoteClose = $this->options['smarty']['substitutions']['right-single-quote'] ?? '’';
|
||||
|
||||
$leftAngleQuote = $this->options['smarty']['substitutions']['left-angle-quote'] ?? '«';
|
||||
$rightAngleQuote = $this->options['smarty']['substitutions']['right-angle-quote'] ?? '»';
|
||||
|
||||
$matches = \array_values(\array_filter($matches));
|
||||
|
||||
// Smart backticks
|
||||
$smartBackticks = $this->options['smarty']['smart_backticks'] ?? false;
|
||||
|
||||
if ($smartBackticks && $matches[1] === '``') {
|
||||
$length = \strlen(\trim($excerpt['before']));
|
||||
if ($length > 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'extent' => \strlen($matches[0]),
|
||||
'element' => [
|
||||
'text' => \html_entity_decode($backtickDoublequoteOpen).$matches[2].\html_entity_decode($backtickDoublequoteClose),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
// Smart quotes
|
||||
$smartQuotes = $this->options['smarty']['smart_quotes'] ?? true;
|
||||
|
||||
if ($smartQuotes) {
|
||||
if ($matches[1] === "'") {
|
||||
$length = \strlen(\trim($excerpt['before']));
|
||||
if ($length > 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'extent' => \strlen($matches[0]),
|
||||
'element' => [
|
||||
'text' => \html_entity_decode($smartSinglequoteOpen).$matches[2].\html_entity_decode($smartSinglequoteClose),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
if ($matches[1] === '"') {
|
||||
$length = \strlen(\trim($excerpt['before']));
|
||||
if ($length > 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'extent' => \strlen($matches[0]),
|
||||
'element' => [
|
||||
'text' => \html_entity_decode($smartDoublequoteOpen).$matches[2].\html_entity_decode($smartDoublequoteClose),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Smart angled quotes
|
||||
$smartAngledQuotes = $this->options['smarty']['smart_angled_quotes'] ?? true;
|
||||
|
||||
if ($smartAngledQuotes && $matches[1] === '<<') {
|
||||
$length = \strlen(\trim($excerpt['before']));
|
||||
if ($length > 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'extent' => \strlen($matches[0]),
|
||||
'element' => [
|
||||
'text' => \html_entity_decode($leftAngleQuote).$matches[2].\html_entity_decode($rightAngleQuote),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
// Smart dashes
|
||||
$smartDashes = $this->options['smarty']['smart_dashes'] ?? true;
|
||||
|
||||
if ($smartDashes) {
|
||||
if ($matches[1] === '---') {
|
||||
return [
|
||||
'extent' => \strlen($matches[0]),
|
||||
'element' => [
|
||||
'rawHtml' => $this->options['smarty']['substitutions']['mdash'] ?? '—',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
if ($matches[1] === '--') {
|
||||
return [
|
||||
'extent' => \strlen($matches[0]),
|
||||
'element' => [
|
||||
'rawHtml' => $this->options['smarty']['substitutions']['ndash'] ?? '–',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Smart ellipses
|
||||
$smartEllipses = $this->options['smarty']['smart_ellipses'] ?? true;
|
||||
|
||||
if ($smartEllipses && $matches[1] === '...') {
|
||||
return [
|
||||
'extent' => \strlen($matches[0]),
|
||||
'element' => [
|
||||
'rawHtml' => $this->options['smarty']['substitutions']['ellipses'] ?? '…',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle math
|
||||
*
|
||||
|
|
@ -1832,7 +1975,7 @@ class Markdown
|
|||
return null;
|
||||
}
|
||||
|
||||
list($name, $pattern) = $line['text'][0] <= '-' ? ['ul', '[*+-]'] : ['ol', '[0-9]{1,9}+[.\)]'];
|
||||
[$name, $pattern] = $line['text'][0] <= '-' ? ['ul', '[*+-]'] : ['ol', '[0-9]{1,9}+[.\)]'];
|
||||
|
||||
if (\preg_match('/^(' . $pattern . '([ ]++|$))(.*+)/', $line['text'], $matches) !== 1) {
|
||||
return null;
|
||||
|
|
@ -2320,6 +2463,7 @@ class Markdown
|
|||
|
||||
return $block;
|
||||
}
|
||||
|
||||
if (\preg_match('/^(?<!\\\\)(\$\$)$/', $line['text']) && $block['end'] === '$$') {
|
||||
$block['complete'] = true;
|
||||
$block['math'] = true;
|
||||
|
|
@ -2457,6 +2601,57 @@ class Markdown
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Continue block spoiler
|
||||
*
|
||||
* @param array{body:string, indent:int, text:string} $line Line data
|
||||
* @param null|array $_ Current block (unused parameter)
|
||||
*
|
||||
* @return null|array
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function blockSpoiler(array $line, array $_ = null) : ?array
|
||||
{
|
||||
if (!($this->options['code']['blocks'] ?? true)
|
||||
|| !($this->options['code'] ?? true)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$marker = $line['text'][0];
|
||||
$openerLength = \strspn($line['text'], $marker);
|
||||
|
||||
if ($openerLength < 3) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$summary = \trim(\preg_replace('/^\?{3}([^\s]+)(.+)?/s', '$1', $line['text']));
|
||||
|
||||
$infostring = \trim(\substr($line['text'], $openerLength), "\t ");
|
||||
if (\strpos($infostring, '?') !== false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'char' => $marker,
|
||||
'openerLength' => $openerLength,
|
||||
'element' => [
|
||||
'name' => 'details',
|
||||
'elements' => [
|
||||
[
|
||||
'name' => 'summary',
|
||||
'text' => $summary,
|
||||
],
|
||||
[
|
||||
'name' => 'span', // @todo: check if without span possible
|
||||
'text' => '',
|
||||
]
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete block table
|
||||
*
|
||||
|
|
@ -2639,9 +2834,13 @@ class Markdown
|
|||
*/
|
||||
protected function blockCheckboxComplete(array $block) : array
|
||||
{
|
||||
if ($this->markupEscaped || $this->safeMode) {
|
||||
$text = \htmlspecialchars($block['text'], \ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
$html = $block['handler'] === 'unchecked'
|
||||
? $this->checkboxUnchecked($block['text'])
|
||||
: $this->checkboxChecked($block['text']);
|
||||
? '<input type="checkbox" disabled /> ' . $this->formatOnce($text)
|
||||
: '<input type="checkbox" checked disabled /> ' . $this->formatOnce($text);
|
||||
|
||||
$block['element'] = [
|
||||
'rawHtml' => $html,
|
||||
|
|
@ -2651,42 +2850,6 @@ class Markdown
|
|||
return $block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate unchecked checkbox html
|
||||
*
|
||||
* @param string Checkbox text
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function checkboxUnchecked(string $text) : string
|
||||
{
|
||||
if ($this->markupEscaped || $this->safeMode) {
|
||||
$text = \htmlspecialchars($text, \ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
return '<input type="checkbox" disabled /> ' . $this->formatOnce($text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate checked checkbox html
|
||||
*
|
||||
* @param string Checkbox text
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function checkboxChecked(string $text) : string
|
||||
{
|
||||
if ($this->markupEscaped || $this->safeMode) {
|
||||
$text = \htmlspecialchars($text, \ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
return '<input type="checkbox" checked disabled /> ' . $this->formatOnce($text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats text without double escaping
|
||||
*
|
||||
|
|
@ -3259,24 +3422,21 @@ class Markdown
|
|||
|
||||
++$this->definitionData['Footnote'][$name]['count'];
|
||||
|
||||
if (!isset($this->definitionData['Footnote'][$name]['number']))
|
||||
{
|
||||
if (!isset($this->definitionData['Footnote'][$name]['number'])) {
|
||||
$this->definitionData['Footnote'][$name]['number'] = ++ $this->footnoteCount; // » &
|
||||
}
|
||||
|
||||
$element = [
|
||||
'name' => 'sup',
|
||||
'attributes' => ['id' => 'fnref' . $this->definitionData['Footnote'][$name]['count'] . ':' . $name],
|
||||
'element' => [
|
||||
'name' => 'a',
|
||||
'attributes' => ['href' => '#fn:' . $name, 'class' => 'footnote-ref'],
|
||||
'text' => $this->definitionData['Footnote'][$name]['number'],
|
||||
],
|
||||
];
|
||||
|
||||
return [
|
||||
'extent' => \strlen($matches[0]),
|
||||
'element' => $element,
|
||||
'element' => [
|
||||
'name' => 'sup',
|
||||
'attributes' => ['id' => 'fnref' . $this->definitionData['Footnote'][$name]['count'] . ':' . $name],
|
||||
'element' => [
|
||||
'name' => 'a',
|
||||
'attributes' => ['href' => '#fn:' . $name, 'class' => 'footnote-ref'],
|
||||
'text' => $this->definitionData['Footnote'][$name]['number'],
|
||||
],
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -3848,6 +4008,57 @@ class Markdown
|
|||
return $block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Continue block spoiler
|
||||
*
|
||||
* @param array{body:string, indent:int, text:string} $line Line data
|
||||
* @param array $block Current block
|
||||
*
|
||||
* @return null|array
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function blockSpoilerContinue(array $line, array $block) : ?array
|
||||
{
|
||||
if (isset($block['complete'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isset($block['interrupted'])) {
|
||||
$block['element']['element']['text'] .= \str_repeat("\n", $block['interrupted']);
|
||||
|
||||
unset($block['interrupted']);
|
||||
}
|
||||
|
||||
if (($len = \strspn($line['text'], $block['char'])) >= $block['openerLength']
|
||||
&& \rtrim(\substr($line['text'], $len), ' ') === ''
|
||||
) {
|
||||
$block['element']['element']['text'] = \substr($block['element']['element']['text'], 1);
|
||||
|
||||
$block['complete'] = true;
|
||||
|
||||
return $block;
|
||||
}
|
||||
|
||||
$block['element']['element']['text'] .= "\n" . $line['body'];
|
||||
|
||||
return $block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete block spoiler
|
||||
*
|
||||
* @param array $block Current block
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function blockSpoilerComplete(array $block) : array
|
||||
{
|
||||
return $block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Continue block list
|
||||
*
|
||||
|
|
@ -4130,10 +4341,10 @@ class Markdown
|
|||
return null;
|
||||
}
|
||||
|
||||
$Definition = $this->definitionData['Reference'][$definition];
|
||||
$definition = $this->definitionData['Reference'][$definition];
|
||||
|
||||
$element['attributes']['href'] = $Definition['url'];
|
||||
$element['attributes']['title'] = $Definition['title'];
|
||||
$element['attributes']['href'] = $definition['url'];
|
||||
$element['attributes']['title'] = $definition['title'];
|
||||
}
|
||||
|
||||
return [
|
||||
|
|
|
|||
|
|
@ -70,6 +70,95 @@ final class MarkdownTest extends \PHPUnit\Framework\TestCase
|
|||
);
|
||||
}
|
||||
|
||||
public function testMap() : void
|
||||
{
|
||||
$parser = new Markdown([
|
||||
'map' => true
|
||||
]);
|
||||
|
||||
self::assertEquals(
|
||||
\file_get_contents(__DIR__ . '/manualdata/map.html'),
|
||||
$parser->text(\file_get_contents(__DIR__ . '/manualdata/map.md'))
|
||||
);
|
||||
}
|
||||
|
||||
public function testContact() : void
|
||||
{
|
||||
$parser = new Markdown([
|
||||
'contact' => true
|
||||
]);
|
||||
|
||||
self::assertEquals(
|
||||
\file_get_contents(__DIR__ . '/manualdata/contact.html'),
|
||||
$parser->text(\file_get_contents(__DIR__ . '/manualdata/contact.md'))
|
||||
);
|
||||
}
|
||||
|
||||
public function testTypographer() : void
|
||||
{
|
||||
$parser = new Markdown([
|
||||
'typographer' => true
|
||||
]);
|
||||
|
||||
self::assertEquals(
|
||||
\file_get_contents(__DIR__ . '/manualdata/typographer.html'),
|
||||
$parser->text(\file_get_contents(__DIR__ . '/manualdata/typographer.md'))
|
||||
);
|
||||
}
|
||||
|
||||
public function testAddress() : void
|
||||
{
|
||||
$parser = new Markdown([
|
||||
'address' => true
|
||||
]);
|
||||
|
||||
self::assertEquals(
|
||||
\file_get_contents(__DIR__ . '/manualdata/address.html'),
|
||||
$parser->text(\file_get_contents(__DIR__ . '/manualdata/address.md'))
|
||||
);
|
||||
}
|
||||
|
||||
public function testProgress() : void
|
||||
{
|
||||
$parser = new Markdown([
|
||||
'progress' => true
|
||||
]);
|
||||
|
||||
self::assertEquals(
|
||||
\file_get_contents(__DIR__ . '/manualdata/progress.html'),
|
||||
$parser->text(\file_get_contents(__DIR__ . '/manualdata/progress.md'))
|
||||
);
|
||||
}
|
||||
|
||||
public function testSpoiler() : void
|
||||
{
|
||||
$parser = new Markdown([
|
||||
'spoiler' => true
|
||||
]);
|
||||
|
||||
self::assertEquals(
|
||||
\file_get_contents(__DIR__ . '/manualdata/spoiler.html'),
|
||||
$parser->text(\file_get_contents(__DIR__ . '/manualdata/spoiler.md'))
|
||||
);
|
||||
|
||||
self::assertEquals(
|
||||
\file_get_contents(__DIR__ . '/manualdata/spoiler_block.html'),
|
||||
$parser->text(\file_get_contents(__DIR__ . '/manualdata/spoiler_block.md'))
|
||||
);
|
||||
}
|
||||
|
||||
public function testEmbed() : void
|
||||
{
|
||||
$parser = new Markdown([
|
||||
'embeding' => true
|
||||
]);
|
||||
|
||||
self::assertEquals(
|
||||
\file_get_contents(__DIR__ . '/manualdata/embed.html'),
|
||||
$parser->text(\file_get_contents(__DIR__ . '/manualdata/embed.md'))
|
||||
);
|
||||
}
|
||||
|
||||
public function testMath() : void
|
||||
{
|
||||
$parser = new Markdown([
|
||||
|
|
|
|||
0
tests/Utils/Parser/Markdown/data/checkbox.htm
Normal file
0
tests/Utils/Parser/Markdown/data/checkbox.htm
Normal file
2
tests/Utils/Parser/Markdown/data/checkbox.md
Normal file
2
tests/Utils/Parser/Markdown/data/checkbox.md
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
- [ ] Unchecked
|
||||
- [x] Checked
|
||||
1
tests/Utils/Parser/Markdown/data/emoji.html
Normal file
1
tests/Utils/Parser/Markdown/data/emoji.html
Normal file
|
|
@ -0,0 +1 @@
|
|||
📹
|
||||
1
tests/Utils/Parser/Markdown/data/emoji.md
Normal file
1
tests/Utils/Parser/Markdown/data/emoji.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
:video_camera:
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
<p><em>underscore</em>, <em>asterisk</em>, <em>one two</em>, <em>three four</em>, <em>a</em>, <em>b</em></p>
|
||||
<p><u>underscore</u>, <em>asterisk</em>, <em>one two</em>, <em>three four</em>, <em>a</em>, <em>b</em></p>
|
||||
<p><strong>strong</strong> and <em>em</em> and <strong>strong</strong> and <em>em</em></p>
|
||||
<p><em>line
|
||||
line
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
_underscore_, *asterisk*, _one two_, *three four*, _a_, *b*
|
||||
__underscore__, *asterisk*, _one two_, *three four*, _a_, *b*
|
||||
|
||||
**strong** and *em* and **strong** and *em*
|
||||
|
||||
|
|
|
|||
1
tests/Utils/Parser/Markdown/data/keystroke.html
Normal file
1
tests/Utils/Parser/Markdown/data/keystroke.html
Normal file
|
|
@ -0,0 +1 @@
|
|||
<kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>A</kbd>
|
||||
1
tests/Utils/Parser/Markdown/data/keystroke.md
Normal file
1
tests/Utils/Parser/Markdown/data/keystroke.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
[[ctrl]] + [[shift]] + [[A]]
|
||||
1
tests/Utils/Parser/Markdown/data/mark.html
Normal file
1
tests/Utils/Parser/Markdown/data/mark.html
Normal file
|
|
@ -0,0 +1 @@
|
|||
<mark>Mark test</mark>
|
||||
1
tests/Utils/Parser/Markdown/data/mark.md
Normal file
1
tests/Utils/Parser/Markdown/data/mark.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
==Mark test==
|
||||
|
|
@ -1 +0,0 @@
|
|||
[video src="https://www.youtube.com/watch?v=dQw4w9WgXcQ&ab_channel=RickAstley"]
|
||||
1
tests/Utils/Parser/Markdown/manualdata/address.html
Normal file
1
tests/Utils/Parser/Markdown/manualdata/address.html
Normal file
|
|
@ -0,0 +1 @@
|
|||
[addr name="AddrName" address="Addr" city="AddrCity" country="AddrCoutry" zip="AddrZip"]
|
||||
1
tests/Utils/Parser/Markdown/manualdata/address.md
Normal file
1
tests/Utils/Parser/Markdown/manualdata/address.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
[addr name="AddrName" address="Addr" city="AddrCity" country="AddrCoutry" zip="AddrZip"]
|
||||
9
tests/Utils/Parser/Markdown/manualdata/contact.html
Normal file
9
tests/Utils/Parser/Markdown/manualdata/contact.html
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
[contact phone="+49 123 456 789"]
|
||||
[contact facebook="@jinggaApp"]
|
||||
[contact discord="jingga"]
|
||||
[contact email="test@email.com"]
|
||||
[contact twitter="@jinggaApp"]
|
||||
[contact youtube="@jinggaApp"]
|
||||
[contact instagram="jinggaApp"]
|
||||
[contact slack="jinggaApp"]
|
||||
[contact teams="jinggaApp"]
|
||||
9
tests/Utils/Parser/Markdown/manualdata/contact.md
Normal file
9
tests/Utils/Parser/Markdown/manualdata/contact.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
[contact phone="+49 123 456 789"]
|
||||
[contact facebook="@jinggaApp"]
|
||||
[contact discord="jingga"]
|
||||
[contact email="test@email.com"]
|
||||
[contact twitter="@jinggaApp"]
|
||||
[contact youtube="@jinggaApp"]
|
||||
[contact instagram="jinggaApp"]
|
||||
[contact slack="jinggaApp"]
|
||||
[contact teams="jinggaApp"]
|
||||
|
|
@ -1 +1,2 @@
|
|||
<p>[video src="<a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ&ab_channel=RickAstley">https://www.youtube.com/watch?v=dQw4w9WgXcQ&ab_channel=RickAstley</a>"]</p>
|
||||
<p>[video src="<a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ&ab_channel=RickAstley">https://www.youtube.com/watch?v=dQw4w9WgXcQ&ab_channel=RickAstley</a>"]</p>
|
||||
<video src="test.mp4"></video>
|
||||
4
tests/Utils/Parser/Markdown/manualdata/embed.md
Normal file
4
tests/Utils/Parser/Markdown/manualdata/embed.md
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
[video src="https://www.youtube.com/watch?v=dQw4w9WgXcQ&ab_channel=RickAstley"]
|
||||
[video src="https://vimeo.com/874474957"]
|
||||
[video src="https://www.dailymotion.com/video/x3w7rss"]
|
||||
[video src="test.mp4"]
|
||||
1
tests/Utils/Parser/Markdown/manualdata/map.html
Normal file
1
tests/Utils/Parser/Markdown/manualdata/map.html
Normal file
|
|
@ -0,0 +1 @@
|
|||
<div id="" class="map" data-lat="1.0" data-lon="1.0"></div>
|
||||
1
tests/Utils/Parser/Markdown/manualdata/map.md
Normal file
1
tests/Utils/Parser/Markdown/manualdata/map.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
[map lat="1.0" lon="1.0"]
|
||||
1
tests/Utils/Parser/Markdown/manualdata/progress.html
Normal file
1
tests/Utils/Parser/Markdown/manualdata/progress.html
Normal file
|
|
@ -0,0 +1 @@
|
|||
<progress value="33" max="100"></progress>
|
||||
1
tests/Utils/Parser/Markdown/manualdata/progress.md
Normal file
1
tests/Utils/Parser/Markdown/manualdata/progress.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
[progress value="33"]
|
||||
1
tests/Utils/Parser/Markdown/manualdata/spoiler.html
Normal file
1
tests/Utils/Parser/Markdown/manualdata/spoiler.html
Normal file
|
|
@ -0,0 +1 @@
|
|||
This is a >!test spoiler!< in text.
|
||||
1
tests/Utils/Parser/Markdown/manualdata/spoiler.md
Normal file
1
tests/Utils/Parser/Markdown/manualdata/spoiler.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
This is a >!test spoiler!< in text.
|
||||
|
|
@ -0,0 +1 @@
|
|||
<details><summary>Test summary</summary><span>This is a test spoiler.</span></details>
|
||||
3
tests/Utils/Parser/Markdown/manualdata/spoiler_block.md
Normal file
3
tests/Utils/Parser/Markdown/manualdata/spoiler_block.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
???Test summary
|
||||
This is a test spoiler.
|
||||
???
|
||||
1
tests/Utils/Parser/Markdown/manualdata/typographer.html
Normal file
1
tests/Utils/Parser/Markdown/manualdata/typographer.html
Normal file
|
|
@ -0,0 +1 @@
|
|||
©
|
||||
1
tests/Utils/Parser/Markdown/manualdata/typographer.md
Normal file
1
tests/Utils/Parser/Markdown/manualdata/typographer.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
(c)
|
||||
Loading…
Reference in New Issue
Block a user