ponepaste/includes/Parsedown/BeamParsedown.php
aftercase 5b2e3d5177 Initial commit
Initial commit
2021-07-10 19:18:17 +01:00

491 lines
No EOL
14 KiB
PHP

<?php
#
#
# Beam Parsedown
# https://github.com/ardissoebrata/beam-parsedown
#
# (c) Emanuil Rusev
# http://erusev.com
#
# (c) Ardi Soebrata
# https://mybeam.me
#
# For the full license information, view the LICENSE file that was distributed
# with this source code.
#
#
namespace ArdiSSoebrata\BeamParsedown;
use ParsedownExtra;
class BeamParsedown extends ParsedownExtra
{
const version = '0.0.1';
protected $isUrlRegex = "/(https?|ftp)\:\/\//i";
protected $regexAttribute = '(?:([#.][\w-]+\s*)|([\w-]+=[\w-]+\s*))+';
function __construct()
{
parent::__construct();
// @codeCoverageIgnoreStart
if (version_compare(parent::version, '0.8.1') < 0)
{
throw new Exception('BeamParsedown requires a later version of ParsedownExtra');
}
// @codeCoverageIgnoreEnd
$this->InlineTypes['['][] = 'Icon';
$this->InlineTypes['['][] = 'Audio';
// Identify our blocks before definition list.
array_unshift($this->BlockTypes[':'], 'Alert');
array_unshift($this->BlockTypes[':'], 'Mermaid');
array_unshift($this->BlockTypes[':'], 'Chart');
// Identify our blocks before Reference.
array_unshift($this->BlockTypes['['], 'Youtube');
array_unshift($this->BlockTypes['['], 'Drawio');
}
// Base path.
protected $basePath = '';
public function setBasePath($url)
{
$this->basePath = preg_replace('{/$}', '', $url) . '/';
return $this;
}
protected function inlineImage($excerpt)
{
$image = parent::inlineImage($excerpt);
if ( ! isset($image))
{
return null;
}
// Add basePath if src is relative.
$src = $image['element']['attributes']['src'];
if (!preg_match($this->isUrlRegex, $src, $urlmatch)) {
$image['element']['attributes']['src'] = $this->basePath . $src;
}
return $image;
}
// Heading id & attributes.
protected function blockHeader($Line)
{
$Block = parent::blockHeader($Line);
if (! isset($Block)) {
return null;
}
if (!isset($Block['element']['attributes']['id'])) {
$text = $Block['element']['text'];
$text = preg_replace('/(\[.+:.*\]\s)/', '', $text); // remove [tag: value]. Ex. [icon: fa fa-home].
$Block['element']['attributes']['id'] = $this->slugify($text);
}
return $Block;
}
protected function blockSetextHeader($Line, array $Block = null)
{
$Block = parent::blockSetextHeader($Line, $Block);
if (isset($Block['element']) && !isset($Block['element']['attributes']['id'])) {
$text = $Block['element']['text'];
$text = preg_replace('/(\[.+:.*\]\s)/', '', $text); // remove [tag: value]. Ex. [icon: fa fa-home].
$Block['element']['attributes']['id'] = $this->slugify($text);
}
return $Block;
}
protected function parseAttributeData($attributeString)
{
$Data = array();
$attributes = preg_split('/[ ]+/', $attributeString, - 1, PREG_SPLIT_NO_EMPTY);
foreach ($attributes as $attribute)
{
if ($attribute[0] === '#')
{
$Data['id'] = substr($attribute, 1);
}
elseif ($attribute[0] === '.')
{
$classes []= substr($attribute, 1);
}
elseif (preg_match('/([\w-]+)=([\w-]+)/', $attribute, $match))
{
$Data[$match[1]] = $match[2];
}
}
if (isset($classes))
{
$Data['class'] = implode(' ', $classes);
}
return $Data;
}
public static function slugify($text)
{
// replace non letter or digits by -
$text = preg_replace('~[^\pL\d]+~u', '-', $text);
// transliterate
$text = iconv('utf-8', 'us-ascii//TRANSLIT', $text);
// remove unwanted characters
$text = preg_replace('~[^-\w]+~', '', $text);
// trim
$text = trim($text, '-');
// remove duplicate -
$text = preg_replace('~-+~', '-', $text);
// lowercase
$text = strtolower($text);
if (empty($text)) {
return 'n-a';
}
return $text;
}
// Icon
protected function InlineIcon($excerpt)
{
if (preg_match('/\[icon:(.+?)\]/', $excerpt['text'], $matches))
{
return array(
// How many characters to advance the Parsedown's
// cursor after being done processing this tag.
'extent' => strlen($matches[0]),
'element' => array(
'name' => 'i',
'attributes' => array(
'class' => trim($matches[1]),
),
'rawHtml' => '',
),
);
}
}
// Audio
protected function InlineAudio($excerpt)
{
if (preg_match('/\[audio:(.+?)\]/', $excerpt['text'], $matches))
{
// Add basePath if src is relative.
$src = trim($matches[1]);
if (!preg_match($this->isUrlRegex, $src, $urlmatch)) {
$src = $this->basePath . $src;
}
return array(
// How many characters to advance the Parsedown's
// cursor after being done processing this tag.
'extent' => strlen($matches[0]),
'element' => array(
'name' => 'audio',
'attributes' => array(
'controls' => '',
'preload' => 'none',
),
'handler' => 'element',
'text' => array(
'name' => 'source',
'attributes' => array(
'src' => $src,
)
),
),
);
}
}
// Youtube
protected function BlockYoutube($excerpt)
{
if (preg_match('/\[youtube:\s*https\:\/\/youtu\.be\/(.+?)\]/', $excerpt['text'], $matches))
{
$video_id = trim($matches[1]);
return array(
// How many characters to advance the Parsedown's
// cursor after being done processing this tag.
'extent' => strlen($matches[0]),
'element' => array(
'name' => 'div',
'attributes' => array(
'class' => 'overflow-hidden relative h-0',
'style' => 'padding-bottom: 56.25%',
),
'handler' => 'element',
'text' => array(
'name' => 'iframe',
'attributes' => array(
'src' => 'https://www.youtube.com/embed/' . $video_id,
'frameborder' => '0',
'allow' => 'accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture',
'allowfullscreen' => '',
'class' => 'left-0 top-0 h-full w-full absolute',
),
'rawHtml' => '',
),
),
);
}
}
// Alerts
protected $alert_types = array(
'info' => array(
'container-class' => 'bg-indigo-100 rounded shadow-sm flex overflow-hidden',
'icon-bg-class' => 'bg-indigo-500 w-20 flex justify-center items-center',
'icon-class' => 'fa fa-info-circle fa-2x text-white',
),
'warning' => array(
'container-class' => 'bg-yellow-50 rounded shadow-sm flex overflow-hidden',
'icon-bg-class' => 'bg-yellow-300 w-20 flex justify-center items-center',
'icon-class' => 'fa fa-exclamation-triangle fa-2x',
)
);
protected function BlockAlert($line, $block)
{
$types = implode('|', array_keys($this->alert_types));
if (preg_match('/^:::(' . $types . ')/', $line['text'], $matches))
{
$type = trim($matches[1]);
return array(
'char' => $line['text'][0],
'element' => array(
'name' => 'div',
'attributes' => array(
'class' => $this->alert_types[$type]['container-class'],
'role' => 'alert',
),
'handler' => 'elements',
'text' => array(
array(
'name' => 'div',
'attributes' => array(
'class' => $this->alert_types[$type]['icon-bg-class']
),
'handler' => 'element',
'text' => array(
'name' => 'i',
'attributes' => array(
'class' => $this->alert_types[$type]['icon-class'],
),
'rawHtml' => ''
),
),
array(
'name' => 'div',
'attributes' => array(
'class' => 'flex-1 px-4',
),
'handler' => 'lines',
'text' => array(),
)
),
),
);
}
}
protected function BlockAlertContinue($line, $block)
{
if (isset($block['complete']))
{
return;
}
// A blank newline has occurred.
if (isset($block['interrupted']))
{
unset($block['interrupted']);
}
// Check for end of the block.
if (preg_match('/^:::/', $line['text']))
{
$block['complete'] = true;
return $block;
}
$block['element']['text'][1]['text'][] = $line['body'];
return $block;
}
protected function BlockAlertComplete($block)
{
return $block;
}
// draw.io
protected function BlockDrawio($excerpt)
{
if (preg_match('/\[drawio:\s*(.+?)\]/', $excerpt['text'], $matches))
{
$file = trim($matches[1]);
if (!preg_match($this->isUrlRegex, $file, $urlmatch)) {
$file = $this->basePath . $file;
}
return array(
// How many characters to advance the Parsedown's
// cursor after being done processing this tag.
'extent' => strlen($matches[0]),
'element' => array(
'name' => 'div',
'handler' => 'elements',
'text' => array(
array(
'name' => 'div',
'attributes' => array(
'class' => 'mxgraph w-full border',
'data-mxgraph' => json_encode(array(
'highlight' => '#0000ff',
'target' => 'blank',
'nav' => true,
'resize' => true,
'toolbar' => 'zoom layers lightbox',
'url' => $file,
))
),
'rawHtml' => '',
),
array(
'name' => 'script',
'attributes' => array(
'type' => 'text/javascript',
'src' => 'https://viewer.diagrams.net/js/viewer-static.min.js'
),
'rawHtml' => '',
),
),
),
);
}
}
// Mermaid
protected function BlockMermaid($line, $block)
{
if (preg_match('/^:::\s*mermaid/', $line['text'], $matches))
{
return array(
'char' => $line['text'][0],
'element' => array(
'name' => 'div',
'attributes' => array(
'class' => 'mermaid',
),
'rawHtml' => "\n",
),
);
}
}
protected function BlockMermaidContinue($line, $block)
{
if (isset($block['complete']))
{
return;
}
// A blank newline has occurred.
if (isset($block['interrupted']))
{
unset($block['interrupted']);
}
// Check for end of the block.
if (preg_match('/^:::/', $line['text']))
{
$block['complete'] = true;
return $block;
}
$block['element']['rawHtml'] .= $line['body'] . "\n";
return $block;
}
protected function BlockMermaidComplete($block)
{
return $block;
}
// Chart JS
protected function BlockChart($line, $block)
{
if (preg_match('/^:::\s*chart/', $line['text'], $matches))
{
return array(
'char' => $line['text'][0],
'element' => array(
'name' => 'canvas',
'attributes' => array(
'class' => 'chartjs',
),
'rawHtml' => "\n",
),
);
}
}
protected function BlockChartContinue($line, $block)
{
if (isset($block['complete']))
{
return;
}
// A blank newline has occurred.
if (isset($block['interrupted']))
{
unset($block['interrupted']);
}
// Check for end of the block.
if (preg_match('/^:::/', $line['text']))
{
$block['complete'] = true;
return $block;
}
$block['element']['rawHtml'] .= $line['body'] . "\n";
return $block;
}
protected function BlockChartComplete($block)
{
return $block;
}
}