]> BookStack Code Mirror - bookstack/commitdiff
Added markdown export endpoints to API
authorDan Brown <redacted>
Tue, 22 Jun 2021 20:32:55 +0000 (21:32 +0100)
committerDan Brown <redacted>
Tue, 22 Jun 2021 20:39:29 +0000 (21:39 +0100)
- Added tests to cover.
- Added slight extra spaces at content joins.

app/Entities/Tools/ExportFormatter.php
app/Entities/Tools/Markdown/HtmlToMarkdown.php
app/Http/Controllers/Api/BookExportApiController.php
app/Http/Controllers/Api/ChapterExportApiController.php
app/Http/Controllers/Api/PageExportApiController.php
routes/api.php
tests/Api/BooksApiTest.php
tests/Api/ChaptersApiTest.php
tests/Api/PagesApiTest.php

index bd0e1bfd07621ce268ec5f5e6e7943f9f940edb3..9317b04316b5b4bb4c3d4feac62977e42785b0c3 100644 (file)
@@ -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;
     }
index d3a2a3e7f6b497987f0b32e3d9a258af22ca38b6..c56119fe19cec8eb4437fdf2321236067980388f 100644 (file)
@@ -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 <a> 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 <br> into `\n` instead of `  \n`
+            'list_item_style' => '-', // Set the default character for each <li> in a <ul>. 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 <caption> content before or after table, null to suppress
+        ]);
 
         $environment->addConverter(new BlockquoteConverter());
         $environment->addConverter(new CodeConverter());
index 3d813c4d4225fabdaa686d9b15881871f85303ad..24cba9df374a83ba00f1aa9fbefb43594f8d7499 100644 (file)
@@ -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');
+    }
 }
index afdfe555dd56f1bee4bc0a40139476b8a2d80c4f..a4c349f4ef4aa06276adf98089531566a4c25617 100644 (file)
@@ -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');
+    }
 }
index 7563092cb36671c81ce826fae220b55fb5247c15..bf43016c2258d137d33fc6248d3b4021330e0c27 100644 (file)
@@ -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');
+    }
 }
index 44643d6d4cc7182a436a7f5ed0d6605d83df637f..5b724fab126dd57e6162886e6a868b0c5aeeacb6 100644 (file)
@@ -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');
index a36acdd0253bf961b0c3d44844cae14cd557f4ff..446ba28117a765a7663b6c95def30782939d9329 100644 (file)
@@ -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
index c7368eaee1835209d64df5ce90642f864bf3131b..e11bb0e1cf8d4f672d71791adabae4061604b378 100644 (file)
@@ -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
index e08e9b1b742b1424f4b6dba465ea994395d80908..9fab675e739644b11df8284a0355c6456627e6fb 100644 (file)
@@ -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
Morty Proxy This is a proxified and sanitized view of the page, visit original site.