diff --git a/app/Entities/Models/Chapter.php b/app/Entities/Models/Chapter.php index 257b19e37..181068533 100644 --- a/app/Entities/Models/Chapter.php +++ b/app/Entities/Models/Chapter.php @@ -5,6 +5,7 @@ use Illuminate\Support\Collection; /** * Class Chapter * @property Collection<Page> $pages + * @property mixed description */ class Chapter extends BookChild { diff --git a/app/Entities/Tools/ExportFormatter.php b/app/Entities/Tools/ExportFormatter.php index b462abec5..bd0e1bfd0 100644 --- a/app/Entities/Tools/ExportFormatter.php +++ b/app/Entities/Tools/ExportFormatter.php @@ -3,13 +3,12 @@ use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Page; +use BookStack\Entities\Tools\Markdown\HtmlToMarkdown; use BookStack\Uploads\ImageService; use DomPDF; use Exception; use SnappyPDF; -use League\HTMLToMarkdown\HtmlConverter; use Throwable; -use ZipArchive; class ExportFormatter { @@ -231,23 +230,20 @@ class ExportFormatter /** * Convert a page to a Markdown file. - * @throws Throwable */ - public function pageToMarkdown(Page $page) + public function pageToMarkdown(Page $page): string { - if (property_exists($page, 'markdown') && $page->markdown != '') { + if ($page->markdown) { return "# " . $page->name . "\n\n" . $page->markdown; - } else { - $converter = new HtmlConverter(); - return "# " . $page->name . "\n\n" . $converter->convert($page->html); } + + return "# " . $page->name . "\n\n" . (new HtmlToMarkdown($page->html))->convert(); } /** * Convert a chapter to a Markdown file. - * @throws Throwable */ - public function chapterToMarkdown(Chapter $chapter) + public function chapterToMarkdown(Chapter $chapter): string { $text = "# " . $chapter->name . "\n\n"; $text .= $chapter->description . "\n\n"; @@ -265,7 +261,7 @@ class ExportFormatter $bookTree = (new BookContents($book))->getTree(false, true); $text = "# " . $book->name . "\n\n"; foreach ($bookTree as $bookChild) { - if ($bookChild->isA('chapter')) { + if ($bookChild instanceof Chapter) { $text .= $this->chapterToMarkdown($bookChild); } else { $text .= $this->pageToMarkdown($bookChild); @@ -273,27 +269,4 @@ class ExportFormatter } return $text; } - - /** - * Convert a book into a zip file. - */ - public function bookToZip(Book $book): string - { - // TODO: Is not unlinking the file a security risk? - $z = new ZipArchive(); - $z->open("book.zip", \ZipArchive::CREATE | \ZipArchive::OVERWRITE); - $bookTree = (new BookContents($book))->getTree(false, true); - foreach ($bookTree as $bookChild) { - if ($bookChild->isA('chapter')) { - $z->addEmptyDir($bookChild->name); - foreach ($bookChild->pages as $page) { - $filename = $bookChild->name . "/" . $page->name . ".md"; - $z->addFromString($filename, $this->pageToMarkdown($page)); - } - } else { - $z->addFromString($bookChild->name . ".md", $this->pageToMarkdown($bookChild)); - } - } - return "book.zip"; - } } diff --git a/app/Entities/Tools/Markdown/CustomParagraphConverter.php b/app/Entities/Tools/Markdown/CustomParagraphConverter.php new file mode 100644 index 000000000..1af770261 --- /dev/null +++ b/app/Entities/Tools/Markdown/CustomParagraphConverter.php @@ -0,0 +1,17 @@ +<?php namespace BookStack\Entities\Tools\Markdown; + +use League\HTMLToMarkdown\Converter\ParagraphConverter; +use League\HTMLToMarkdown\ElementInterface; + +class CustomParagraphConverter extends ParagraphConverter +{ + public function convert(ElementInterface $element): string + { + $class = $element->getAttribute('class'); + if (strpos($class, 'callout') !== false) { + return "<{$element->getTagName()} class=\"{$class}\">{$element->getValue()}</{$element->getTagName()}>\n\n"; + } + + return parent::convert($element); + } +} diff --git a/app/Entities/Tools/Markdown/HtmlToMarkdown.php b/app/Entities/Tools/Markdown/HtmlToMarkdown.php new file mode 100644 index 000000000..d3a2a3e7f --- /dev/null +++ b/app/Entities/Tools/Markdown/HtmlToMarkdown.php @@ -0,0 +1,76 @@ +<?php namespace BookStack\Entities\Tools\Markdown; + +use League\HTMLToMarkdown\Converter\BlockquoteConverter; +use League\HTMLToMarkdown\Converter\CodeConverter; +use League\HTMLToMarkdown\Converter\CommentConverter; +use League\HTMLToMarkdown\Converter\DivConverter; +use League\HTMLToMarkdown\Converter\EmphasisConverter; +use League\HTMLToMarkdown\Converter\HardBreakConverter; +use League\HTMLToMarkdown\Converter\HeaderConverter; +use League\HTMLToMarkdown\Converter\HorizontalRuleConverter; +use League\HTMLToMarkdown\Converter\ImageConverter; +use League\HTMLToMarkdown\Converter\LinkConverter; +use League\HTMLToMarkdown\Converter\ListBlockConverter; +use League\HTMLToMarkdown\Converter\ListItemConverter; +use League\HTMLToMarkdown\Converter\PreformattedConverter; +use League\HTMLToMarkdown\Converter\TextConverter; +use League\HTMLToMarkdown\Environment; +use League\HTMLToMarkdown\HtmlConverter; + +class HtmlToMarkdown +{ + protected $html; + + public function __construct(string $html) + { + $this->html = $html; + } + + /** + * Run the conversion + */ + public function convert(): string + { + $converter = new HtmlConverter($this->getConverterEnvironment()); + $html = $this->prepareHtml($this->html); + return $converter->convert($html); + } + + /** + * Run any pre-processing to the HTML to clean it up manually before conversion. + */ + protected function prepareHtml(string $html): string + { + // Carriage returns can cause whitespace issues in output + $html = str_replace("\r\n", "\n", $html); + // Attributes on the pre tag can cause issues with conversion + return preg_replace('/<pre .*?>/', '<pre>', $html); + } + + /** + * Get the HTML to Markdown customized environment. + * Extends the default provided environment with some BookStack specific tweaks. + */ + protected function getConverterEnvironment(): Environment + { + $environment = new Environment(['header_style' => 'atx']); + + $environment->addConverter(new BlockquoteConverter()); + $environment->addConverter(new CodeConverter()); + $environment->addConverter(new CommentConverter()); + $environment->addConverter(new DivConverter()); + $environment->addConverter(new EmphasisConverter()); + $environment->addConverter(new HardBreakConverter()); + $environment->addConverter(new HeaderConverter()); + $environment->addConverter(new HorizontalRuleConverter()); + $environment->addConverter(new ImageConverter()); + $environment->addConverter(new LinkConverter()); + $environment->addConverter(new ListBlockConverter()); + $environment->addConverter(new ListItemConverter()); + $environment->addConverter(new CustomParagraphConverter()); + $environment->addConverter(new PreformattedConverter()); + $environment->addConverter(new TextConverter()); + + return $environment; + } +} diff --git a/app/Http/Controllers/BookExportController.php b/app/Http/Controllers/BookExportController.php index 58868fa5c..6d334ca73 100644 --- a/app/Http/Controllers/BookExportController.php +++ b/app/Http/Controllers/BookExportController.php @@ -59,17 +59,7 @@ class BookExportController extends Controller public function markdown(string $bookSlug) { $book = $this->bookRepo->getBySlug($bookSlug); - $textContent = $this->exportService->bookToMarkdown($book); + $textContent = $this->exportFormatter->bookToMarkdown($book); return $this->downloadResponse($textContent, $bookSlug . '.md'); } - - /** - * Export a book as a zip file, made of markdown files. - */ - public function zip(string $bookSlug) - { - $book = $this->bookRepo->getBySlug($bookSlug); - $filename = $this->exportService->bookToZip($book); - return response()->download($filename); - } } diff --git a/app/Http/Controllers/ChapterExportController.php b/app/Http/Controllers/ChapterExportController.php index bc709771b..b934eefce 100644 --- a/app/Http/Controllers/ChapterExportController.php +++ b/app/Http/Controllers/ChapterExportController.php @@ -63,7 +63,7 @@ class ChapterExportController extends Controller { // TODO: This should probably export to a zip file. $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug); - $chapterText = $this->exportService->chapterToMarkdown($chapter); + $chapterText = $this->exportFormatter->chapterToMarkdown($chapter); return $this->downloadResponse($chapterText, $chapterSlug . '.md'); } } diff --git a/app/Http/Controllers/PageExportController.php b/app/Http/Controllers/PageExportController.php index d9cc5ba48..f5dcf7117 100644 --- a/app/Http/Controllers/PageExportController.php +++ b/app/Http/Controllers/PageExportController.php @@ -68,7 +68,7 @@ class PageExportController extends Controller public function markdown(string $bookSlug, string $pageSlug) { $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); - $pageText = $this->exportService->pageToMarkdown($page); + $pageText = $this->exportFormatter->pageToMarkdown($page); return $this->downloadResponse($pageText, $pageSlug . '.md'); } } diff --git a/composer.json b/composer.json index 8124ccbca..4125ae7fe 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "laravel/socialite": "^5.1", "league/commonmark": "^1.5", "league/flysystem-aws-s3-v3": "^1.0.29", - "league/html-to-markdown": "^4.9", + "league/html-to-markdown": "^5.0.0", "nunomaduro/collision": "^3.1", "onelogin/php-saml": "^4.0", "predis/predis": "^1.1.6", diff --git a/composer.lock b/composer.lock index d0d75a32c..a7868b42a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "eb3108f1a3a757df9b9a3a4f82b5db3b", + "content-hash": "70a290f0e2112361af4a0abe15148adc", "packages": [ { "name": "aws/aws-sdk-php", - "version": "3.184.2", + "version": "3.184.7", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "78fe691ab466fecf195209672f6c00c5d4ed219a" + "reference": "d5d5fe5fdfca6c7a56f2f8364d09c3100d5c2149" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/78fe691ab466fecf195209672f6c00c5d4ed219a", - "reference": "78fe691ab466fecf195209672f6c00c5d4ed219a", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/d5d5fe5fdfca6c7a56f2f8364d09c3100d5c2149", + "reference": "d5d5fe5fdfca6c7a56f2f8364d09c3100d5c2149", "shasum": "" }, "require": { @@ -92,9 +92,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.184.2" + "source": "https://github.com/aws/aws-sdk-php/tree/3.184.7" }, - "time": "2021-06-11T18:20:15+00:00" + "time": "2021-06-21T18:37:16+00:00" }, { "name": "barryvdh/laravel-dompdf", @@ -229,16 +229,16 @@ }, { "name": "doctrine/cache", - "version": "1.11.3", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/doctrine/cache.git", - "reference": "3bb5588cec00a0268829cc4a518490df6741af9d" + "reference": "c9622c6820d3ede1e2315a6a377ea1076e421d88" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/3bb5588cec00a0268829cc4a518490df6741af9d", - "reference": "3bb5588cec00a0268829cc4a518490df6741af9d", + "url": "https://api.github.com/repos/doctrine/cache/zipball/c9622c6820d3ede1e2315a6a377ea1076e421d88", + "reference": "c9622c6820d3ede1e2315a6a377ea1076e421d88", "shasum": "" }, "require": { @@ -308,7 +308,7 @@ ], "support": { "issues": "https://github.com/doctrine/cache/issues", - "source": "https://github.com/doctrine/cache/tree/1.11.3" + "source": "https://github.com/doctrine/cache/tree/2.0.3" }, "funding": [ { @@ -324,35 +324,36 @@ "type": "tidelift" } ], - "time": "2021-05-25T09:01:55+00:00" + "time": "2021-05-25T09:43:04+00:00" }, { "name": "doctrine/dbal", - "version": "2.13.1", + "version": "2.13.2", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "c800380457948e65bbd30ba92cc17cda108bf8c9" + "reference": "8dd39d2ead4409ce652fd4f02621060f009ea5e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/c800380457948e65bbd30ba92cc17cda108bf8c9", - "reference": "c800380457948e65bbd30ba92cc17cda108bf8c9", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/8dd39d2ead4409ce652fd4f02621060f009ea5e4", + "reference": "8dd39d2ead4409ce652fd4f02621060f009ea5e4", "shasum": "" }, "require": { - "doctrine/cache": "^1.0", + "doctrine/cache": "^1.0|^2.0", "doctrine/deprecations": "^0.5.3", "doctrine/event-manager": "^1.0", "ext-pdo": "*", "php": "^7.1 || ^8" }, "require-dev": { - "doctrine/coding-standard": "8.2.0", + "doctrine/coding-standard": "9.0.0", "jetbrains/phpstorm-stubs": "2020.2", "phpstan/phpstan": "0.12.81", - "phpunit/phpunit": "^7.5.20|^8.5|9.5.0", + "phpunit/phpunit": "^7.5.20|^8.5|9.5.5", "squizlabs/php_codesniffer": "3.6.0", + "symfony/cache": "^4.4", "symfony/console": "^2.0.5|^3.0|^4.0|^5.0", "vimeo/psalm": "4.6.4" }, @@ -415,7 +416,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/2.13.1" + "source": "https://github.com/doctrine/dbal/tree/2.13.2" }, "funding": [ { @@ -431,7 +432,7 @@ "type": "tidelift" } ], - "time": "2021-04-17T17:30:19+00:00" + "time": "2021-06-18T21:48:39+00:00" }, { "name": "doctrine/deprecations", @@ -1651,16 +1652,16 @@ }, { "name": "laravel/framework", - "version": "v6.20.27", + "version": "v6.20.29", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "92c0417e60efc39bc556ba5dfc9b20a56f7848fb" + "reference": "00fa9c04aed10d68481f5757b89da0e6798f53b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/92c0417e60efc39bc556ba5dfc9b20a56f7848fb", - "reference": "92c0417e60efc39bc556ba5dfc9b20a56f7848fb", + "url": "https://api.github.com/repos/laravel/framework/zipball/00fa9c04aed10d68481f5757b89da0e6798f53b3", + "reference": "00fa9c04aed10d68481f5757b89da0e6798f53b3", "shasum": "" }, "require": { @@ -1800,7 +1801,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2021-05-11T14:00:28+00:00" + "time": "2021-06-22T13:41:06+00:00" }, { "name": "laravel/socialite", @@ -1873,16 +1874,16 @@ }, { "name": "league/commonmark", - "version": "1.6.2", + "version": "1.6.4", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "7d70d2f19c84bcc16275ea47edabee24747352eb" + "reference": "c3c8b7217c52572fb42aaf84211abccf75a151b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/7d70d2f19c84bcc16275ea47edabee24747352eb", - "reference": "7d70d2f19c84bcc16275ea47edabee24747352eb", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/c3c8b7217c52572fb42aaf84211abccf75a151b2", + "reference": "c3c8b7217c52572fb42aaf84211abccf75a151b2", "shasum": "" }, "require": { @@ -1900,7 +1901,7 @@ "github/gfm": "0.29.0", "michelf/php-markdown": "~1.4", "mikehaertl/php-shellcommand": "^1.4", - "phpstan/phpstan": "^0.12", + "phpstan/phpstan": "^0.12.90", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.2", "scrutinizer/ocular": "^1.5", "symfony/finder": "^4.2" @@ -1970,7 +1971,7 @@ "type": "tidelift" } ], - "time": "2021-05-12T11:39:41+00:00" + "time": "2021-06-19T20:08:14+00:00" }, { "name": "league/flysystem", @@ -2118,6 +2119,95 @@ }, "time": "2020-10-08T18:58:37+00:00" }, + { + "name": "league/html-to-markdown", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/html-to-markdown.git", + "reference": "c4dbebbebe0fe454b6b38e6c683a977615bd7dc2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/html-to-markdown/zipball/c4dbebbebe0fe454b6b38e6c683a977615bd7dc2", + "reference": "c4dbebbebe0fe454b6b38e6c683a977615bd7dc2", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xml": "*", + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "mikehaertl/php-shellcommand": "^1.1.0", + "phpstan/phpstan": "^0.12.82", + "phpunit/phpunit": "^8.5 || ^9.2", + "scrutinizer/ocular": "^1.6", + "unleashedtech/php-coding-standard": "^2.7", + "vimeo/psalm": "^4.6" + }, + "bin": [ + "bin/html-to-markdown" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "psr-4": { + "League\\HTMLToMarkdown\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + }, + { + "name": "Nick Cernis", + "email": "nick@cern.is", + "homepage": "http://modernnerd.net", + "role": "Original Author" + } + ], + "description": "An HTML-to-markdown conversion helper for PHP", + "homepage": "https://github.com/thephpleague/html-to-markdown", + "keywords": [ + "html", + "markdown" + ], + "support": { + "issues": "https://github.com/thephpleague/html-to-markdown/issues", + "source": "https://github.com/thephpleague/html-to-markdown/tree/5.0.0" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + }, + { + "url": "https://www.patreon.com/colinodell", + "type": "patreon" + } + ], + "time": "2021-03-29T01:29:08+00:00" + }, { "name": "league/mime-type-detection", "version": "1.7.0", @@ -2347,16 +2437,16 @@ }, { "name": "mtdowling/jmespath.php", - "version": "2.6.0", + "version": "2.6.1", "source": { "type": "git", "url": "https://github.com/jmespath/jmespath.php.git", - "reference": "42dae2cbd13154083ca6d70099692fef8ca84bfb" + "reference": "9b87907a81b87bc76d19a7fb2d61e61486ee9edb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/42dae2cbd13154083ca6d70099692fef8ca84bfb", - "reference": "42dae2cbd13154083ca6d70099692fef8ca84bfb", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/9b87907a81b87bc76d19a7fb2d61e61486ee9edb", + "reference": "9b87907a81b87bc76d19a7fb2d61e61486ee9edb", "shasum": "" }, "require": { @@ -2364,7 +2454,7 @@ "symfony/polyfill-mbstring": "^1.17" }, "require-dev": { - "composer/xdebug-handler": "^1.4", + "composer/xdebug-handler": "^1.4 || ^2.0", "phpunit/phpunit": "^4.8.36 || ^7.5.15" }, "bin": [ @@ -2402,9 +2492,9 @@ ], "support": { "issues": "https://github.com/jmespath/jmespath.php/issues", - "source": "https://github.com/jmespath/jmespath.php/tree/2.6.0" + "source": "https://github.com/jmespath/jmespath.php/tree/2.6.1" }, - "time": "2020-07-31T21:01:56+00:00" + "time": "2021-06-14T00:11:39+00:00" }, { "name": "nesbot/carbon", @@ -3781,16 +3871,16 @@ }, { "name": "socialiteproviders/microsoft-azure", - "version": "4.2.0", + "version": "4.2.1", "source": { "type": "git", "url": "https://github.com/SocialiteProviders/Microsoft-Azure.git", - "reference": "7808764f777a01df88be9ca6b14d683e50aaf88a" + "reference": "64779ec21db0bee3111039a67c0fa0ab550a3462" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/SocialiteProviders/Microsoft-Azure/zipball/7808764f777a01df88be9ca6b14d683e50aaf88a", - "reference": "7808764f777a01df88be9ca6b14d683e50aaf88a", + "url": "https://api.github.com/repos/SocialiteProviders/Microsoft-Azure/zipball/64779ec21db0bee3111039a67c0fa0ab550a3462", + "reference": "64779ec21db0bee3111039a67c0fa0ab550a3462", "shasum": "" }, "require": { @@ -3816,9 +3906,9 @@ ], "description": "Microsoft Azure OAuth2 Provider for Laravel Socialite", "support": { - "source": "https://github.com/SocialiteProviders/Microsoft-Azure/tree/4.2.0" + "source": "https://github.com/SocialiteProviders/Microsoft-Azure/tree/4.2.1" }, - "time": "2020-12-01T23:10:59+00:00" + "time": "2021-06-14T22:51:38+00:00" }, { "name": "socialiteproviders/okta", @@ -4904,16 +4994,16 @@ }, { "name": "symfony/mime", - "version": "v5.3.0", + "version": "v5.3.2", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "ed710d297b181f6a7194d8172c9c2423d58e4852" + "reference": "47dd7912152b82d0d4c8d9040dbc93d6232d472a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/ed710d297b181f6a7194d8172c9c2423d58e4852", - "reference": "ed710d297b181f6a7194d8172c9c2423d58e4852", + "url": "https://api.github.com/repos/symfony/mime/zipball/47dd7912152b82d0d4c8d9040dbc93d6232d472a", + "reference": "47dd7912152b82d0d4c8d9040dbc93d6232d472a", "shasum": "" }, "require": { @@ -4967,7 +5057,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v5.3.0" + "source": "https://github.com/symfony/mime/tree/v5.3.2" }, "funding": [ { @@ -4983,7 +5073,7 @@ "type": "tidelift" } ], - "time": "2021-05-26T17:43:10+00:00" + "time": "2021-06-09T10:58:01+00:00" }, { "name": "symfony/polyfill-ctype", @@ -6250,16 +6340,16 @@ "packages-dev": [ { "name": "barryvdh/laravel-debugbar", - "version": "v3.6.1", + "version": "v3.6.2", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "f6f0f895a33cac801286a74355d146bb5384a5da" + "reference": "70b89754913fd89fef16d0170a91dbc2a5cd633a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/f6f0f895a33cac801286a74355d146bb5384a5da", - "reference": "f6f0f895a33cac801286a74355d146bb5384a5da", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/70b89754913fd89fef16d0170a91dbc2a5cd633a", + "reference": "70b89754913fd89fef16d0170a91dbc2a5cd633a", "shasum": "" }, "require": { @@ -6319,15 +6409,19 @@ ], "support": { "issues": "https://github.com/barryvdh/laravel-debugbar/issues", - "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.6.1" + "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.6.2" }, "funding": [ + { + "url": "https://fruitcake.nl", + "type": "custom" + }, { "url": "https://github.com/barryvdh", "type": "github" } ], - "time": "2021-06-02T06:42:22+00:00" + "time": "2021-06-14T14:29:26+00:00" }, { "name": "barryvdh/laravel-ide-helper", @@ -9169,16 +9263,16 @@ }, { "name": "sebastian/type", - "version": "2.3.2", + "version": "2.3.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "0d1c587401514d17e8f9258a27e23527cb1b06c1" + "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/0d1c587401514d17e8f9258a27e23527cb1b06c1", - "reference": "0d1c587401514d17e8f9258a27e23527cb1b06c1", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b8cd8a1c753c90bc1a0f5372170e3e489136f914", + "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914", "shasum": "" }, "require": { @@ -9213,7 +9307,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/2.3.2" + "source": "https://github.com/sebastianbergmann/type/tree/2.3.4" }, "funding": [ { @@ -9221,7 +9315,7 @@ "type": "github" } ], - "time": "2021-06-04T13:02:07+00:00" + "time": "2021-06-15T12:49:02+00:00" }, { "name": "sebastian/version", diff --git a/tests/Entity/ExportTest.php b/tests/Entity/ExportTest.php index f9ba3d90e..f437904e8 100644 --- a/tests/Entity/ExportTest.php +++ b/tests/Entity/ExportTest.php @@ -259,4 +259,85 @@ class ExportTest extends TestCase $resp->assertDontSee('ExportWizardTheFifth'); } + public function test_page_markdown_export() + { + $page = Page::query()->first(); + + $resp = $this->asEditor()->get($page->getUrl('/export/markdown')); + $resp->assertStatus(200); + $resp->assertSee($page->name); + $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $page->slug . '.md"'); + } + + public function test_page_markdown_export_uses_existing_markdown_if_apparent() + { + $page = Page::query()->first()->forceFill([ + 'markdown' => '# A header', + 'html' => '<h1>Dogcat</h1>', + ]); + $page->save(); + + $resp = $this->asEditor()->get($page->getUrl('/export/markdown')); + $resp->assertSee('A header'); + $resp->assertDontSee('Dogcat'); + } + + public function test_page_markdown_export_converts_html_where_no_markdown() + { + $page = Page::query()->first()->forceFill([ + 'markdown' => '', + 'html' => "<h1>Dogcat</h1><p>Some <strong>bold</strong> text</p>", + ]); + $page->save(); + + $resp = $this->asEditor()->get($page->getUrl('/export/markdown')); + $resp->assertSee("# Dogcat\n\nSome **bold** text"); + } + + public function test_page_markdown_export_does_not_convert_callouts() + { + $page = Page::query()->first()->forceFill([ + 'markdown' => '', + 'html' => "<h1>Dogcat</h1><p class=\"callout info\">Some callout text</p><p>Another line</p>", + ]); + $page->save(); + + $resp = $this->asEditor()->get($page->getUrl('/export/markdown')); + $resp->assertSee("# Dogcat\n\n<p class=\"callout info\">Some callout text</p>\n\nAnother line"); + } + + public function test_page_markdown_export_handles_bookstacks_wysiwyg_codeblock_format() + { + $page = Page::query()->first()->forceFill([ + 'markdown' => '', + 'html' => '<h1>Dogcat</h1>'."\r\n".'<pre id="bkmrk-var-a-%3D-%27cat%27%3B"><code class="language-JavaScript">var a = \'cat\';</code></pre><p>Another line</p>', + ]); + $page->save(); + + $resp = $this->asEditor()->get($page->getUrl('/export/markdown')); + $resp->assertSee("# Dogcat\n\n```JavaScript\nvar a = 'cat';\n```\n\nAnother line"); + } + + public function test_chapter_markdown_export() + { + $chapter = Chapter::query()->first(); + $page = $chapter->pages()->first(); + $resp = $this->asEditor()->get($chapter->getUrl('/export/markdown')); + + $resp->assertSee('# ' . $chapter->name); + $resp->assertSee('# ' . $page->name); + } + + public function test_book_markdown_export() + { + $book = Book::query()->whereHas('pages')->whereHas('chapters')->first(); + $chapter = $book->chapters()->first(); + $page = $chapter->pages()->first(); + $resp = $this->asEditor()->get($book->getUrl('/export/markdown')); + + $resp->assertSee('# ' . $book->name); + $resp->assertSee('# ' . $chapter->name); + $resp->assertSee('# ' . $page->name); + } + }