.*?<\/pre>)/s'; + // patterns + const CODE_BLOCK_PATTERN = '/(```[\s\S]*?```|~~~[\s\S]*?~~~|[\s\S]*?<\/pre>)/'; const VIDEO_TAG_PATTERN = '/\[video.*src="([^"]*)".*\]/'; const COLOR_TAG_PATTERN = '/\[color=([^\]]+)\](.*?)\[\/color\]/s'; const RTL_TAG_PATTERN = '/\[rtl\](.*?)\[\/rtl\]/s'; @@ -15,16 +16,12 @@ class ParsedownPlus extends ParsedownFilter const MONO_TAG_PATTERN = '/\[mono\](.*?)\[\/mono\]/s'; const COLLAPSIBLE_SECTION_PATTERN = '/\+\+\+(.*?)\n(.*?)\n\+\+\+/s'; - function __construct(array $params = null) + public function __construct(array $params = null) { parent::__construct($params); - - // Ensure the parent class version is compatible if (version_compare(parent::version, '0.8.0-beta-1') < 0) { throw new Exception('ParsedownPlus requires a later version of Parsedown'); } - - // Load predefined colors and fonts from config.php if it exists $this->loadConfig(); } @@ -50,19 +47,11 @@ public function text($text) $text = $this->addCss($text); $this->cssAdded = true; } - - // Process custom tags outside code blocks, except for color tags + $text = $this->processSpecialQuotesOutsideCode($text); $text = $this->processCustomTagsOutsideCode($text, false); - - // Parse the Markdown $text = parent::text($text); - - // Process collapsible sections $text = $this->processCollapsibleSections($text); - - // Now process the color tags $text = $this->processColorTags($text); - return $text; } @@ -118,6 +107,48 @@ protected function addCss($text) border-bottom: 1px solid #aaa; margin-bottom: 0.5em; } + blockquote.special-quote p { + margin: 0 0 10px; + } + .special-quote-header { + font-weight: bold; + color: inherit; + display: flex; + align-items: center; + } + .special-quote-header i { + margin-right: 5px; + } + blockquote.special-quote.caution { + border-left-color: #dc3545; + } + blockquote.special-quote.caution .special-quote-header { + color: #dc3545; + } + blockquote.special-quote.important { + border-left-color: #007bff; + } + blockquote.special-quote.important .special-quote-header { + color: #007bff; + } + blockquote.special-quote.warning { + border-left-color: #ffc107; + } + blockquote.special-quote.warning .special-quote-header { + color: #ffc107; + } + blockquote.special-quote.tip { + border-left-color: #28a745; + } + blockquote.special-quote.tip .special-quote-header { + color: #28a745; + } + blockquote.special-quote.question { + border-left-color: #17a2b8; + } + blockquote.special-quote.question .special-quote-header { + color: #17a2b8; + } \n"; return $css . $text; } @@ -125,13 +156,11 @@ protected function addCss($text) protected function processCustomTagsOutsideCode($text, $includeColor = true) { $parts = preg_split(self::CODE_BLOCK_PATTERN, $text, -1, PREG_SPLIT_DELIM_CAPTURE); - foreach ($parts as &$part) { if (!preg_match(self::CODE_BLOCK_PATTERN, $part)) { $part = $this->processCustomTags($part, $includeColor); } } - return implode('', $parts); } @@ -154,23 +183,21 @@ protected function processVideoTags($text) function ($matches) { $url = $matches[1]; $type = ''; - - $needles = array('youtube', 'vimeo'); + $needles = ['youtube', 'vimeo']; foreach ($needles as $needle) { if (strpos($url, $needle) !== false) { $type = $needle; } } - switch ($type) { case 'youtube': $src = preg_replace('/.*\?v=([^\&\]]*).*/', 'https://www.youtube.com/embed/$1', $url); - return ''; + return ''; case 'vimeo': $src = preg_replace('/(?:https?:\/\/(?:[\w]{3}\.|player\.)*vimeo\.com(?:[\/\w:]*(?:\/videos)?)?\/([0-9]+)[^\s]*)/', 'https://player.vimeo.com/video/$1', $url); - return ''; + return ''; default: - return $matches[0]; // Return the original if no match + return $matches[0]; // return the original if no match } }, $text @@ -180,7 +207,6 @@ function ($matches) { protected function processColorTags($text) { $parts = preg_split(self::CODE_BLOCK_PATTERN, $text, -1, PREG_SPLIT_DELIM_CAPTURE); - foreach ($parts as &$part) { if (!preg_match(self::CODE_BLOCK_PATTERN, $part)) { $part = preg_replace_callback( @@ -193,7 +219,7 @@ function ($matches) { $color = htmlspecialchars($color); } $content = $matches[2]; - // Check if the content contains block-level elements + // check if the content contains block-level elements if (preg_match('/<(?:p|div|h[1-6]|ul|ol|li|blockquote|pre|table|dl|address)/i', $content)) { return "$content"; } else { @@ -204,7 +230,6 @@ function ($matches) { ); } } - return implode('', $parts); } @@ -243,10 +268,10 @@ function ($matches) { $text ); } + protected function processCollapsibleSections($text) { $parts = preg_split(self::CODE_BLOCK_PATTERN, $text, -1, PREG_SPLIT_DELIM_CAPTURE); - foreach ($parts as &$part) { if (!preg_match(self::CODE_BLOCK_PATTERN, $part)) { $part = preg_replace_callback( @@ -254,20 +279,91 @@ protected function processCollapsibleSections($text) function ($matches) { $summary = trim($matches[1]); $content = $this->text(trim($matches[2])); - if (empty($summary)) { $summary = "Click to expand"; } else { $summary = trim($summary, '"'); } - return ""; }, $part ); } } + return implode('', $parts); + } + protected function getSpecialQuoteIconClass($tag) + { + $icons = [ + 'caution' => 'fa-exclamation-triangle', + 'important' => 'fa-info-circle', + 'warning' => 'fa-exclamation-circle', + 'tip' => 'fa-sticky-note', + 'question' => 'fa-question-circle', + ]; + return $icons[$tag] ?? 'fa-info-circle'; // default to info-circle if tag not found + } + + protected function processSpecialQuotesOutsideCode($text) + { + $parts = preg_split(self::CODE_BLOCK_PATTERN, $text, -1, PREG_SPLIT_DELIM_CAPTURE); + foreach ($parts as &$part) { + if (!preg_match(self::CODE_BLOCK_PATTERN, $part)) { + $part = $this->processSpecialQuotes($part); + } + } return implode('', $parts); } + + protected function processSpecialQuotes($text) + { + $lines = explode("\n", $text); + $outputLines = []; + $inSpecialQuote = false; + $specialQuoteTag = ''; + $specialQuoteHeader = ''; + $specialQuoteContent = []; + foreach ($lines as $line) { + if (preg_match('/^> \[!(CAUTION|IMPORTANT|WARNING|TIP|QUESTION)\](.*)/i', $line, $matches)) { + // start of a special blockquote + if ($inSpecialQuote) { + // close any previously opened blockquote + $outputLines[] = $this->generateSpecialBlockquote($specialQuoteTag, $specialQuoteHeader, $specialQuoteContent); + $specialQuoteContent = []; // reset content + } + $inSpecialQuote = true; + $specialQuoteTag = strtolower($matches[1]); + $specialQuoteHeader = trim($matches[2]); + } elseif ($inSpecialQuote && preg_match('/^> ?(.*)/', $line, $matches)) { + // continuation of a special blockquote + $specialQuoteContent[] = trim($matches[1]); + } else { + // end of special blockquote + if ($inSpecialQuote) { + $outputLines[] = $this->generateSpecialBlockquote($specialQuoteTag, $specialQuoteHeader, $specialQuoteContent); + $inSpecialQuote = false; + $specialQuoteTag = ''; + $specialQuoteHeader = ''; + $specialQuoteContent = []; + } + // output non-blockquote lines as they are + $outputLines[] = $line; + } + } + // handle unclosed blockquote at the end + if ($inSpecialQuote) { + $outputLines[] = $this->generateSpecialBlockquote($specialQuoteTag, $specialQuoteHeader, $specialQuoteContent); + } + return implode("\n", $outputLines); + } + + protected function generateSpecialBlockquote($tag, $header, $content) + { + $iconClass = $this->getSpecialQuoteIconClass($tag); + $parsedHeader = parent::line($header); + // join the content, let the parent markdown parser process it properly (so lists are parsed as lists) + $parsedContent = parent::text(implode("\n", $content)); + return "{$summary}
{$content}"; + } } diff --git a/libraries/parsedown-plus-0.0.5/README.md b/libraries/parsedown-plus-0.0.6/README.md similarity index 85% rename from libraries/parsedown-plus-0.0.5/README.md rename to libraries/parsedown-plus-0.0.6/README.md index 304633a..be4cf0b 100644 --- a/libraries/parsedown-plus-0.0.5/README.md +++ b/libraries/parsedown-plus-0.0.6/README.md @@ -1,114 +1,133 @@ -# ParsedownPlus - -ParsedownPlus is an extension of Parsedown that adds support for custom tags such as colored text, forced RTL/LTR direction, monospace fonts, and video embedding for YouTube and Vimeo. These custom tags are not standard Markdown. - -**[GitHub Repository](https://github.com/leomoon-studios/ParsedownPlus)** - -## Installation - -1. Include the `ParsedownPlus.php` class in your project. -2. (Optional) Create a `config.php` file in the same directory as `ParsedownPlus.php` to define custom colors and fonts. You can use the provided `config-sample.php` as a template. - -```php - true, - 'toc' => true, - 'sup' => true, - 'sub' => true -]); - -$markdown = "YouTube video: [video src=\"https://www.youtube.com/watch?v=Ta_wxUvvO4c\"] - -Vimeo video: [video src=\"https://vimeo.com/423640994\"] - -[color=#ff0000]Red[/color], [color=#00FF00]Green[/color], [color=#0000FF]Blue[/color] - -[rtl]Forced RTL text.[/rtl] - -[mono]Forced monospaced text.[/mono] - -; -$html = $PARSER->text($markdown); -echo $html; -``` - -## Configuration (optional) - -### config-sample.php - -```php - [ - 'monospace' => 'Courier New, monospace', - ], - // Add predefined colors - 'colors' => [ - 'customred' => '#8D021F', - // Add more colors as needed - ] -]; -``` - -## Examples - -### YouTube Embedding - -```markdown -[video src="https://www.youtube.com/watch?v=Ta_wxUvvO4c"] -``` - -### Vimeo Embedding - -```markdown -[video src="https://vimeo.com/423640994"] -``` - -### Colored Text - -```markdown -[color=#ff0000]Red[/color], [color=#00FF00]Green[/color], [color=#0000FF]Blue[/color] -``` - -Predefined colors can be used like this: - -```markdown -Predefined colors: [color=customred]predefined red[/color]. -``` - -### Forced RTL/LTR - -```markdown -[rtl]Forced RTL example.[/rtl] -``` - -### Monospace Font - -```markdown -[mono]example[/mono] -``` - -### Collapsible section -```markdown -+++ Collapsible title (optional) -This is the content for the collapsible section. If title is not defined, it will default to: `Click to expand` -+++ -``` - -## License - -ParsedownPlus is licensed under the GPLv3 License. See the `LICENSE` file for more details. - -## Contributions -Contributions are welcome! If you have suggestions, bug reports, or feature requests, please open an issue or submit a pull request on the repository. Make sure to follow the project's code of conduct and contribution guidelines. Thank you for helping improve ParsedownPlus! +# ParsedownPlus + +ParsedownPlus is an extension of Parsedown that adds support for custom tags such as colored text, forced RTL/LTR direction, monospace fonts, and video embedding for YouTube and Vimeo. These custom tags are not standard Markdown. + +**[GitHub Repository](https://github.com/leomoon-studios/ParsedownPlus)** + +## Installation + +1. Include the `ParsedownPlus.php` class in your project. +2. (Optional) Create a `config.php` file in the same directory as `ParsedownPlus.php` to define custom colors and fonts. You can use the provided `config-sample.php` as a template. + +```php + true, + 'toc' => true, + 'sup' => true, + 'sub' => true +]); + +$markdown = "YouTube video: [video src=\"https://www.youtube.com/watch?v=Ta_wxUvvO4c\"] + +Vimeo video: [video src=\"https://vimeo.com/423640994\"] + +[color=#ff0000]Red[/color], [color=#00FF00]Green[/color], [color=#0000FF]Blue[/color] + +[rtl]Forced RTL text.[/rtl] + +[mono]Forced monospaced text.[/mono] + +; +$html = $PARSER->text($markdown); +echo $html; +``` + +## Configuration (optional) + +### config-sample.php + +```php + [ + 'monospace' => 'Courier New, monospace', + ], + // Add predefined colors + 'colors' => [ + 'customred' => '#8D021F', + // Add more colors as needed + ] +]; +``` + +## Examples + +### YouTube Embedding + +```markdown +[video src="https://www.youtube.com/watch?v=Ta_wxUvvO4c"] +``` + +### Vimeo Embedding + +```markdown +[video src="https://vimeo.com/423640994"] +``` + +### Caution Important Warning Tip Question Quotes + +``` +> [!CAUTION] Caution header +> Caution text... + +> [!IMPORTANT] Important header +> Important text... + +> [!WARNING] Warning header +> Warning text... + +> [!TIP] Tip header +> Tip text... + +> [!QUESTION] Question header +> Question text... +``` + +### Colored Text + +```markdown +[color=#ff0000]Red[/color], [color=#00FF00]Green[/color], [color=#0000FF]Blue[/color] +``` + +Predefined colors can be used like this: + +```markdown +Predefined colors: [color=customred]predefined red[/color]. +``` + +### Forced RTL/LTR + +```markdown +[rtl]Forced RTL example.[/rtl] +``` + +### Monospace Font + +```markdown +[mono]example[/mono] +``` + +### Collapsible section +```markdown ++++ Collapsible title (optional) +This is the content for the collapsible section. If title is not defined, it will default to: `Click to expand` ++++ +``` + +## License + +ParsedownPlus is licensed under the GPLv3 License. See the `LICENSE` file for more details. + +## Contributions +Contributions are welcome! If you have suggestions, bug reports, or feature requests, please open an issue or submit a pull request on the repository. Make sure to follow the project's code of conduct and contribution guidelines. Thank you for helping improve ParsedownPlus! diff --git a/libraries/parsedown-plus-0.0.5/config-sample.php b/libraries/parsedown-plus-0.0.6/config-sample.php similarity index 95% rename from libraries/parsedown-plus-0.0.5/config-sample.php rename to libraries/parsedown-plus-0.0.6/config-sample.php index ffce7f9..f16ce8d 100644 --- a/libraries/parsedown-plus-0.0.5/config-sample.php +++ b/libraries/parsedown-plus-0.0.6/config-sample.php @@ -1,13 +1,13 @@ - [ - 'monospace' => 'Courier New, monospace', - ], - // add predefined colors - 'colors' => [ - 'customred' => '#8D021F', - // Add more colors as needed - ] -]; + [ + 'monospace' => 'Courier New, monospace', + ], + // add predefined colors + 'colors' => [ + 'customred' => '#8D021F', + // Add more colors as needed + ] +];{$parsedHeader}
{$parsedContent}