From 992f03a3c01759653594fc383917b9aa3affb5a2 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 22 Jun 2021 21:32:55 +0100 Subject: [PATCH] Added markdown export endpoints to API - Added tests to cover. - Added slight extra spaces at content joins. --- app/Entities/Tools/ExportFormatter.php | 2 +- app/Entities/Tools/Markdown/HtmlToMarkdown.php | 16 +++++++++++++++- .../Controllers/Api/BookExportApiController.php | 10 ++++++++++ .../Api/ChapterExportApiController.php | 11 ++++++++++- .../Controllers/Api/PageExportApiController.php | 10 ++++++++++ routes/api.php | 3 +++ tests/Api/BooksApiTest.php | 13 +++++++++++++ tests/Api/ChaptersApiTest.php | 12 ++++++++++++ tests/Api/PagesApiTest.php | 11 +++++++++++ 9 files changed, 85 insertions(+), 3 deletions(-) diff --git a/app/Entities/Tools/ExportFormatter.php b/app/Entities/Tools/ExportFormatter.php index bd0e1bfd0..9317b0431 100644 --- a/app/Entities/Tools/ExportFormatter.php +++ b/app/Entities/Tools/ExportFormatter.php @@ -248,7 +248,7 @@ class ExportFormatter $text = "# " . $chapter->name . "\n\n"; $text .= $chapter->description . "\n\n"; foreach ($chapter->pages as $page) { - $text .= $this->pageToMarkdown($page); + $text .= $this->pageToMarkdown($page) . "\n\n"; } return $text; } diff --git a/app/Entities/Tools/Markdown/HtmlToMarkdown.php b/app/Entities/Tools/Markdown/HtmlToMarkdown.php index d3a2a3e7f..c56119fe1 100644 --- a/app/Entities/Tools/Markdown/HtmlToMarkdown.php +++ b/app/Entities/Tools/Markdown/HtmlToMarkdown.php @@ -53,7 +53,21 @@ class HtmlToMarkdown */ protected function getConverterEnvironment(): Environment { - $environment = new Environment(['header_style' => 'atx']); + $environment = new Environment([ + 'header_style' => 'atx', // Set to 'atx' to output H1 and H2 headers as # Header1 and ## Header2 + 'suppress_errors' => true, // Set to false to show warnings when loading malformed HTML + 'strip_tags' => false, // Set to true to strip tags that don't have markdown equivalents. N.B. Strips tags, not their content. Useful to clean MS Word HTML output. + 'strip_placeholder_links' => false, // Set to true to remove that doesn't have href. + 'bold_style' => '**', // DEPRECATED: Set to '__' if you prefer the underlined style + 'italic_style' => '*', // DEPRECATED: Set to '_' if you prefer the underlined style + 'remove_nodes' => '', // space-separated list of dom nodes that should be removed. example: 'meta style script' + 'hard_break' => false, // Set to true to turn
into `\n` instead of ` \n` + 'list_item_style' => '-', // Set the default character for each
  • in a
      . Can be '-', '*', or '+' + 'preserve_comments' => false, // Set to true to preserve comments, or set to an array of strings to preserve specific comments + 'use_autolinks' => false, // Set to true to use simple link syntax if possible. Will always use []() if set to false + 'table_pipe_escape' => '\|', // Replacement string for pipe characters inside markdown table cells + 'table_caption_side' => 'top', // Set to 'top' or 'bottom' to show content before or after table, null to suppress + ]); $environment->addConverter(new BlockquoteConverter()); $environment->addConverter(new CodeConverter()); diff --git a/app/Http/Controllers/Api/BookExportApiController.php b/app/Http/Controllers/Api/BookExportApiController.php index 3d813c4d4..24cba9df3 100644 --- a/app/Http/Controllers/Api/BookExportApiController.php +++ b/app/Http/Controllers/Api/BookExportApiController.php @@ -44,4 +44,14 @@ class BookExportApiController extends ApiController $textContent = $this->exportFormatter->bookToPlainText($book); return $this->downloadResponse($textContent, $book->slug . '.txt'); } + + /** + * Export a book as a markdown file. + */ + public function exportMarkdown(int $id) + { + $book = Book::visible()->findOrFail($id); + $markdown = $this->exportFormatter->bookToMarkdown($book); + return $this->downloadResponse($markdown, $book->slug . '.md'); + } } diff --git a/app/Http/Controllers/Api/ChapterExportApiController.php b/app/Http/Controllers/Api/ChapterExportApiController.php index afdfe555d..a4c349f4e 100644 --- a/app/Http/Controllers/Api/ChapterExportApiController.php +++ b/app/Http/Controllers/Api/ChapterExportApiController.php @@ -2,7 +2,6 @@ use BookStack\Entities\Models\Chapter; use BookStack\Entities\Tools\ExportFormatter; -use BookStack\Entities\Repos\BookRepo; use Throwable; class ChapterExportApiController extends ApiController @@ -48,4 +47,14 @@ class ChapterExportApiController extends ApiController $textContent = $this->exportFormatter->chapterToPlainText($chapter); return $this->downloadResponse($textContent, $chapter->slug . '.txt'); } + + /** + * Export a chapter as a markdown file. + */ + public function exportMarkdown(int $id) + { + $chapter = Chapter::visible()->findOrFail($id); + $markdown = $this->exportFormatter->chapterToMarkdown($chapter); + return $this->downloadResponse($markdown, $chapter->slug . '.md'); + } } diff --git a/app/Http/Controllers/Api/PageExportApiController.php b/app/Http/Controllers/Api/PageExportApiController.php index 7563092cb..bf43016c2 100644 --- a/app/Http/Controllers/Api/PageExportApiController.php +++ b/app/Http/Controllers/Api/PageExportApiController.php @@ -44,4 +44,14 @@ class PageExportApiController extends ApiController $textContent = $this->exportFormatter->pageToPlainText($page); return $this->downloadResponse($textContent, $page->slug . '.txt'); } + + /** + * Export a page as a markdown file. + */ + public function exportMarkdown(int $id) + { + $page = Page::visible()->findOrFail($id); + $markdown = $this->exportFormatter->pageToMarkdown($page); + return $this->downloadResponse($markdown, $page->slug . '.md'); + } } diff --git a/routes/api.php b/routes/api.php index 44643d6d4..5b724fab1 100644 --- a/routes/api.php +++ b/routes/api.php @@ -18,6 +18,7 @@ Route::delete('books/{id}', 'BookApiController@delete'); Route::get('books/{id}/export/html', 'BookExportApiController@exportHtml'); Route::get('books/{id}/export/pdf', 'BookExportApiController@exportPdf'); Route::get('books/{id}/export/plaintext', 'BookExportApiController@exportPlainText'); +Route::get('books/{id}/export/markdown', 'BookExportApiController@exportMarkdown'); Route::get('chapters', 'ChapterApiController@list'); Route::post('chapters', 'ChapterApiController@create'); @@ -28,6 +29,7 @@ Route::delete('chapters/{id}', 'ChapterApiController@delete'); Route::get('chapters/{id}/export/html', 'ChapterExportApiController@exportHtml'); Route::get('chapters/{id}/export/pdf', 'ChapterExportApiController@exportPdf'); Route::get('chapters/{id}/export/plaintext', 'ChapterExportApiController@exportPlainText'); +Route::get('chapters/{id}/export/markdown', 'ChapterExportApiController@exportMarkdown'); Route::get('pages', 'PageApiController@list'); Route::post('pages', 'PageApiController@create'); @@ -38,6 +40,7 @@ Route::delete('pages/{id}', 'PageApiController@delete'); Route::get('pages/{id}/export/html', 'PageExportApiController@exportHtml'); Route::get('pages/{id}/export/pdf', 'PageExportApiController@exportPdf'); Route::get('pages/{id}/export/plaintext', 'PageExportApiController@exportPlainText'); +Route::get('pages/{id}/export/markdown', 'PageExportApiController@exportMarkDown'); Route::get('shelves', 'BookshelfApiController@list'); Route::post('shelves', 'BookshelfApiController@create'); diff --git a/tests/Api/BooksApiTest.php b/tests/Api/BooksApiTest.php index a36acdd02..446ba2811 100644 --- a/tests/Api/BooksApiTest.php +++ b/tests/Api/BooksApiTest.php @@ -140,4 +140,17 @@ class BooksApiTest extends TestCase $resp->assertStatus(200); $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $book->slug . '.pdf"'); } + + public function test_export_markdown_endpoint() + { + $this->actingAsApiEditor(); + $book = Book::visible()->has('pages')->has('chapters')->first(); + + $resp = $this->get($this->baseEndpoint . "/{$book->id}/export/markdown"); + $resp->assertStatus(200); + $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $book->slug . '.md"'); + $resp->assertSee('# ' . $book->name); + $resp->assertSee('# ' . $book->pages()->first()->name); + $resp->assertSee('# ' . $book->chapters()->first()->name); + } } \ No newline at end of file diff --git a/tests/Api/ChaptersApiTest.php b/tests/Api/ChaptersApiTest.php index c7368eaee..e11bb0e1c 100644 --- a/tests/Api/ChaptersApiTest.php +++ b/tests/Api/ChaptersApiTest.php @@ -186,4 +186,16 @@ class ChaptersApiTest extends TestCase $resp->assertStatus(200); $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.pdf"'); } + + public function test_export_markdown_endpoint() + { + $this->actingAsApiEditor(); + $chapter = Chapter::visible()->has('pages')->first(); + + $resp = $this->get($this->baseEndpoint . "/{$chapter->id}/export/markdown"); + $resp->assertStatus(200); + $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.md"'); + $resp->assertSee('# ' . $chapter->name); + $resp->assertSee('# ' . $chapter->pages()->first()->name); + } } \ No newline at end of file diff --git a/tests/Api/PagesApiTest.php b/tests/Api/PagesApiTest.php index e08e9b1b7..9fab675e7 100644 --- a/tests/Api/PagesApiTest.php +++ b/tests/Api/PagesApiTest.php @@ -258,4 +258,15 @@ class PagesApiTest extends TestCase $resp->assertStatus(200); $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $page->slug . '.pdf"'); } + + public function test_export_markdown_endpoint() + { + $this->actingAsApiEditor(); + $page = Page::visible()->first(); + + $resp = $this->get($this->baseEndpoint . "/{$page->id}/export/markdown"); + $resp->assertStatus(200); + $resp->assertSee('# ' . $page->name); + $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $page->slug . '.md"'); + } } \ No newline at end of file