From: Dan Brown Date: Tue, 22 Jun 2021 18:12:24 +0000 (+0100) Subject: Merge branch 'markdown-export' of https://github.com/nikhiljha/BookStack-1 into nikhi... X-Git-Tag: v21.08~1^2~38 X-Git-Url: http://source.bookstackapp.com/bookstack/commitdiff_plain/9af636bd48f5c7cec7f73746307800b9572d2644?ds=inline;hp=-c Merge branch 'markdown-export' of https://github.com/nikhiljha/BookStack-1 into nikhiljha-markdown-export --- 9af636bd48f5c7cec7f73746307800b9572d2644 diff --combined app/Entities/Tools/ExportFormatter.php index eb8f6862f,b0e88b18b..b462abec5 --- a/app/Entities/Tools/ExportFormatter.php +++ b/app/Entities/Tools/ExportFormatter.php @@@ -1,15 -1,16 +1,17 @@@ -/i", $htmlContent, $imageTagsOutput); + preg_match_all("/\/i", $htmlContent, $imageTagsOutput); // Replace image src with base64 encoded image strings if (isset($imageTagsOutput[0]) && count($imageTagsOutput[0]) > 0) { @@@ -204,7 -205,7 +206,7 @@@ { $text = $chapter->name . "\n\n"; $text .= $chapter->description . "\n\n"; - foreach ($chapter->pages as $page) { + foreach ($chapter->getVisiblePages() as $page) { $text .= $this->pageToPlainText($page); } return $text; @@@ -215,7 -216,7 +217,7 @@@ */ public function bookToPlainText(Book $book): string { - $bookTree = (new BookContents($book))->getTree(false, true); + $bookTree = (new BookContents($book))->getTree(false, false); $text = $book->name . "\n\n"; foreach ($bookTree as $bookChild) { if ($bookChild->isA('chapter')) { @@@ -226,4 -227,72 +228,72 @@@ } return $text; } + + /** + * Convert a page to a Markdown file. + * @throws Throwable + */ + public function pageToMarkdown(Page $page) + { + if (property_exists($page, 'markdown') && $page->markdown != '') { + return "# " . $page->name . "\n\n" . $page->markdown; + } else { + $converter = new HtmlConverter(); + return "# " . $page->name . "\n\n" . $converter->convert($page->html); + } + } + + /** + * Convert a chapter to a Markdown file. + * @throws Throwable + */ + public function chapterToMarkdown(Chapter $chapter) + { + $text = "# " . $chapter->name . "\n\n"; + $text .= $chapter->description . "\n\n"; + foreach ($chapter->pages as $page) { + $text .= $this->pageToMarkdown($page); + } + return $text; + } + + /** + * Convert a book into a plain text string. + */ + public function bookToMarkdown(Book $book): string + { + $bookTree = (new BookContents($book))->getTree(false, true); + $text = "# " . $book->name . "\n\n"; + foreach ($bookTree as $bookChild) { + if ($bookChild->isA('chapter')) { + $text .= $this->chapterToMarkdown($bookChild); + } else { + $text .= $this->pageToMarkdown($bookChild); + } + } + 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 --combined app/Http/Controllers/BookExportController.php index 1c1f12442,a92d94cc9..58868fa5c --- a/app/Http/Controllers/BookExportController.php +++ b/app/Http/Controllers/BookExportController.php @@@ -2,7 -2,8 +2,7 @@@ namespace BookStack\Http\Controllers; -use BookStack\Entities\Managers\BookContents; -use BookStack\Entities\ExportService; +use BookStack\Entities\Tools\ExportFormatter; use BookStack\Entities\Repos\BookRepo; use Throwable; @@@ -10,15 -11,16 +10,15 @@@ class BookExportController extends Cont { protected $bookRepo; - protected $exportService; + protected $exportFormatter; /** * BookExportController constructor. */ - public function __construct(BookRepo $bookRepo, ExportService $exportService) + public function __construct(BookRepo $bookRepo, ExportFormatter $exportFormatter) { $this->bookRepo = $bookRepo; - $this->exportService = $exportService; - parent::__construct(); + $this->exportFormatter = $exportFormatter; } /** @@@ -28,7 -30,7 +28,7 @@@ public function pdf(string $bookSlug) { $book = $this->bookRepo->getBySlug($bookSlug); - $pdfContent = $this->exportService->bookToPdf($book); + $pdfContent = $this->exportFormatter->bookToPdf($book); return $this->downloadResponse($pdfContent, $bookSlug . '.pdf'); } @@@ -39,7 -41,7 +39,7 @@@ public function html(string $bookSlug) { $book = $this->bookRepo->getBySlug($bookSlug); - $htmlContent = $this->exportService->bookToContainedHtml($book); + $htmlContent = $this->exportFormatter->bookToContainedHtml($book); return $this->downloadResponse($htmlContent, $bookSlug . '.html'); } @@@ -49,7 -51,27 +49,27 @@@ public function plainText(string $bookSlug) { $book = $this->bookRepo->getBySlug($bookSlug); - $textContent = $this->exportService->bookToPlainText($book); + $textContent = $this->exportFormatter->bookToPlainText($book); return $this->downloadResponse($textContent, $bookSlug . '.txt'); } + + /** + * Export a book as a markdown file. + */ + public function markdown(string $bookSlug) + { + $book = $this->bookRepo->getBySlug($bookSlug); + $textContent = $this->exportService->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 --combined app/Http/Controllers/ChapterExportController.php index 52d087442,c0fa9fad9..bc709771b --- a/app/Http/Controllers/ChapterExportController.php +++ b/app/Http/Controllers/ChapterExportController.php @@@ -1,6 -1,6 +1,6 @@@ chapterRepo = $chapterRepo; - $this->exportService = $exportService; - parent::__construct(); + $this->exportFormatter = $exportFormatter; } /** @@@ -28,7 -29,7 +28,7 @@@ public function pdf(string $bookSlug, string $chapterSlug) { $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug); - $pdfContent = $this->exportService->chapterToPdf($chapter); + $pdfContent = $this->exportFormatter->chapterToPdf($chapter); return $this->downloadResponse($pdfContent, $chapterSlug . '.pdf'); } @@@ -40,7 -41,7 +40,7 @@@ public function html(string $bookSlug, string $chapterSlug) { $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug); - $containedHtml = $this->exportService->chapterToContainedHtml($chapter); + $containedHtml = $this->exportFormatter->chapterToContainedHtml($chapter); return $this->downloadResponse($containedHtml, $chapterSlug . '.html'); } @@@ -51,7 -52,19 +51,19 @@@ public function plainText(string $bookSlug, string $chapterSlug) { $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug); - $chapterText = $this->exportService->chapterToPlainText($chapter); + $chapterText = $this->exportFormatter->chapterToPlainText($chapter); return $this->downloadResponse($chapterText, $chapterSlug . '.txt'); } + + /** + * Export a chapter to a simple markdown file. + * @throws NotFoundException + */ + public function markdown(string $bookSlug, string $chapterSlug) + { + // TODO: This should probably export to a zip file. + $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug); + $chapterText = $this->exportService->chapterToMarkdown($chapter); + return $this->downloadResponse($chapterText, $chapterSlug . '.md'); + } } diff --combined app/Http/Controllers/PageExportController.php index e5e027fe7,037f84e3b..d9cc5ba48 --- a/app/Http/Controllers/PageExportController.php +++ b/app/Http/Controllers/PageExportController.php @@@ -2,8 -2,8 +2,8 @@@ namespace BookStack\Http\Controllers; -use BookStack\Entities\ExportService; -use BookStack\Entities\Managers\PageContent; +use BookStack\Entities\Tools\ExportFormatter; +use BookStack\Entities\Tools\PageContent; use BookStack\Entities\Repos\PageRepo; use BookStack\Exceptions\NotFoundException; use Throwable; @@@ -12,15 -12,18 +12,15 @@@ class PageExportController extends Cont { protected $pageRepo; - protected $exportService; + protected $exportFormatter; /** * PageExportController constructor. - * @param PageRepo $pageRepo - * @param ExportService $exportService */ - public function __construct(PageRepo $pageRepo, ExportService $exportService) + public function __construct(PageRepo $pageRepo, ExportFormatter $exportFormatter) { $this->pageRepo = $pageRepo; - $this->exportService = $exportService; - parent::__construct(); + $this->exportFormatter = $exportFormatter; } /** @@@ -33,7 -36,7 +33,7 @@@ { $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); $page->html = (new PageContent($page))->render(); - $pdfContent = $this->exportService->pageToPdf($page); + $pdfContent = $this->exportFormatter->pageToPdf($page); return $this->downloadResponse($pdfContent, $pageSlug . '.pdf'); } @@@ -46,7 -49,7 +46,7 @@@ { $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); $page->html = (new PageContent($page))->render(); - $containedHtml = $this->exportService->pageToContainedHtml($page); + $containedHtml = $this->exportFormatter->pageToContainedHtml($page); return $this->downloadResponse($containedHtml, $pageSlug . '.html'); } @@@ -57,7 -60,18 +57,18 @@@ public function plainText(string $bookSlug, string $pageSlug) { $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); - $pageText = $this->exportService->pageToPlainText($page); + $pageText = $this->exportFormatter->pageToPlainText($page); return $this->downloadResponse($pageText, $pageSlug . '.txt'); } + + /** + * Export a page to a simple markdown .md file. + * @throws NotFoundException + */ + public function markdown(string $bookSlug, string $pageSlug) + { + $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); + $pageText = $this->exportService->pageToMarkdown($page); + return $this->downloadResponse($pageText, $pageSlug . '.md'); + } } diff --combined composer.json index 8450a2f92,68802e935..8124ccbca --- a/composer.json +++ b/composer.json @@@ -5,43 -5,45 +5,44 @@@ "license": "MIT", "type": "project", "require": { - "php": "^7.2", + "php": "^7.3|^8.0", "ext-curl": "*", "ext-dom": "*", + "ext-fileinfo": "*", "ext-gd": "*", "ext-json": "*", "ext-mbstring": "*", - "ext-tidy": "*", "ext-xml": "*", - "barryvdh/laravel-dompdf": "^0.8.5", - "barryvdh/laravel-snappy": "^0.4.5", - "doctrine/dbal": "^2.9", - "facade/ignition": "^1.4", - "fideloper/proxy": "^4.0", - "gathercontent/htmldiff": "^0.2.1", - "intervention/image": "^2.5", - "laravel/framework": "^6.12", - "laravel/socialite": "^4.3.2", - "league/commonmark": "^1.4", - "league/flysystem-aws-s3-v3": "^1.0", + "barryvdh/laravel-dompdf": "^0.9.0", + "barryvdh/laravel-snappy": "^0.4.8", + "doctrine/dbal": "^2.12.1", + "facade/ignition": "^1.16.4", + "fideloper/proxy": "^4.4.1", + "intervention/image": "^2.5.1", + "laravel/framework": "^6.20.16", + "laravel/socialite": "^5.1", + "league/commonmark": "^1.5", + "league/flysystem-aws-s3-v3": "^1.0.29", + "league/html-to-markdown": "^4.9", - "nunomaduro/collision": "^3.0", - "onelogin/php-saml": "^3.3", - "predis/predis": "^1.1", - "socialiteproviders/discord": "^2.0", - "socialiteproviders/gitlab": "^3.0", - "socialiteproviders/microsoft-azure": "^3.0", - "socialiteproviders/okta": "^1.0", - "socialiteproviders/slack": "^3.0", - "socialiteproviders/twitch": "^5.0" + "nunomaduro/collision": "^3.1", + "onelogin/php-saml": "^4.0", + "predis/predis": "^1.1.6", + "socialiteproviders/discord": "^4.1", + "socialiteproviders/gitlab": "^4.1", + "socialiteproviders/microsoft-azure": "^4.1", + "socialiteproviders/okta": "^4.1", + "socialiteproviders/slack": "^4.1", + "socialiteproviders/twitch": "^5.3", + "ssddanbrown/htmldiff": "^v1.0.1" }, "require-dev": { - "barryvdh/laravel-debugbar": "^3.2.8", - "barryvdh/laravel-ide-helper": "^2.6.4", - "fzaninotto/faker": "^1.4", - "laravel/browser-kit-testing": "^5.1", - "mockery/mockery": "^1.0", - "phpunit/phpunit": "^8.0", - "squizlabs/php_codesniffer": "^3.4", - "wnx/laravel-stats": "^2.0" + "barryvdh/laravel-debugbar": "^3.5.1", + "barryvdh/laravel-ide-helper": "^2.8.2", + "fakerphp/faker": "^1.13.0", + "laravel/browser-kit-testing": "^5.2", + "mockery/mockery": "^1.3.3", + "phpunit/phpunit": "^9.5.3", + "squizlabs/php_codesniffer": "^3.5.8" }, "autoload": { "classmap": [ @@@ -50,10 -52,7 +51,10 @@@ ], "psr-4": { "BookStack\\": "app/" - } + }, + "files": [ + "app/helpers.php" + ] }, "autoload-dev": { "psr-4": { @@@ -67,8 -66,13 +68,8 @@@ "post-create-project-cmd": [ "@php artisan key:generate --ansi" ], - "pre-update-cmd": [ - "@php -r \"!file_exists('bootstrap/cache/services.php') || @unlink('bootstrap/cache/services.php');\"", - "@php -r \"!file_exists('bootstrap/cache/compiled.php') || @unlink('bootstrap/cache/compiled.php');\"" - ], "pre-install-cmd": [ - "@php -r \"!file_exists('bootstrap/cache/services.php') || @unlink('bootstrap/cache/services.php');\"", - "@php -r \"!file_exists('bootstrap/cache/compiled.php') || @unlink('bootstrap/cache/compiled.php');\"" + "@php -r \"!file_exists('bootstrap/cache/services.php') || @unlink('bootstrap/cache/services.php');\"" ], "post-install-cmd": [ "@php artisan cache:clear", @@@ -88,7 -92,7 +89,7 @@@ "preferred-install": "dist", "sort-packages": true, "platform": { - "php": "7.2.0" + "php": "7.3.0" } }, "extra": { diff --combined dev/docker/Dockerfile index 895ad595a,be5af9ed9..178ea9a6c --- a/dev/docker/Dockerfile +++ b/dev/docker/Dockerfile @@@ -1,23 -1,16 +1,23 @@@ -FROM php:7.3-apache +FROM php:7.4-apache ENV APACHE_DOCUMENT_ROOT /app/public WORKDIR /app +# Install additional dependacnies and configure apache RUN apt-get update -y \ - && apt-get install -y git zip unzip libpng-dev libldap2-dev wait-for-it \ - && apt-get install -y git zip unzip libtidy-dev libpng-dev libldap2-dev libxml++2.6-dev wait-for-it libzip-dev \ ++ && apt-get install -y git zip unzip libpng-dev libldap2-dev libzip-dev wait-for-it \ && docker-php-ext-configure ldap --with-libdir=lib/x86_64-linux-gnu \ - && docker-php-ext-install pdo_mysql gd ldap \ - && docker-php-ext-install pdo pdo_mysql tidy dom xml mbstring gd ldap zip \ ++ && docker-php-ext-install pdo_mysql gd ldap zip \ && a2enmod rewrite \ && sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf \ - && sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf \ - && php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \ + && sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf + +# Install composer +RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \ && php composer-setup.php \ && mv composer.phar /usr/bin/composer \ && php -r "unlink('composer-setup.php');" + +# Use the default production configuration and update it as required +RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" \ - && sed -i 's/memory_limit = 128M/memory_limit = 512M/g' "$PHP_INI_DIR/php.ini" ++ && sed -i 's/memory_limit = 128M/memory_limit = 512M/g' "$PHP_INI_DIR/php.ini" diff --combined resources/lang/en/entities.php index 462402f33,b459c3d4b..1d4632bce --- a/resources/lang/en/entities.php +++ b/resources/lang/en/entities.php @@@ -22,13 -22,10 +22,13 @@@ return 'meta_created_name' => 'Created :timeLength by :user', 'meta_updated' => 'Updated :timeLength', 'meta_updated_name' => 'Updated :timeLength by :user', + 'meta_owned_name' => 'Owned by :user', 'entity_select' => 'Entity Select', 'images' => 'Images', 'my_recent_drafts' => 'My Recent Drafts', 'my_recently_viewed' => 'My Recently Viewed', + 'my_most_viewed_favourites' => 'My Most Viewed Favourites', + 'my_favourites' => 'My Favourites', 'no_pages_viewed' => 'You have not viewed any pages', 'no_pages_recently_created' => 'No pages have been recently created', 'no_pages_recently_updated' => 'No pages have been recently updated', @@@ -36,13 -33,13 +36,14 @@@ 'export_html' => 'Contained Web File', 'export_pdf' => 'PDF File', 'export_text' => 'Plain Text File', + 'export_md' => 'Markdown File', // Permissions and restrictions 'permissions' => 'Permissions', 'permissions_intro' => 'Once enabled, These permissions will take priority over any set role permissions.', 'permissions_enable' => 'Enable Custom Permissions', 'permissions_save' => 'Save Permissions', + 'permissions_owner' => 'Owner', // Search 'search_results' => 'Search Results', @@@ -51,8 -48,7 +52,8 @@@ 'search_no_pages' => 'No pages matched this search', 'search_for_term' => 'Search for :term', 'search_more' => 'More Results', - 'search_filters' => 'Search Filters', + 'search_advanced' => 'Advanced Search', + 'search_terms' => 'Search Terms', 'search_content_type' => 'Content Type', 'search_exact_matches' => 'Exact Matches', 'search_tags' => 'Tag Searches', @@@ -62,7 -58,6 +63,7 @@@ 'search_permissions_set' => 'Permissions set', 'search_created_by_me' => 'Created by me', 'search_updated_by_me' => 'Updated by me', + 'search_owned_by_me' => 'Owned by me', 'search_date_options' => 'Date Options', 'search_updated_before' => 'Updated before', 'search_updated_after' => 'Updated after', @@@ -151,7 -146,7 +152,7 @@@ 'chapters_create' => 'Create New Chapter', 'chapters_delete' => 'Delete Chapter', 'chapters_delete_named' => 'Delete Chapter :chapterName', - 'chapters_delete_explain' => 'This will delete the chapter with the name \':chapterName\'. All pages will be removed and added directly to the parent book.', + 'chapters_delete_explain' => 'This will delete the chapter with the name \':chapterName\'. All pages that exist within this chapter will also be deleted.', 'chapters_delete_confirm' => 'Are you sure you want to delete this chapter?', 'chapters_edit' => 'Edit Chapter', 'chapters_edit_named' => 'Edit Chapter :chapterName', @@@ -213,7 -208,6 +214,7 @@@ 'pages_revisions' => 'Page Revisions', 'pages_revisions_named' => 'Page Revisions for :pageName', 'pages_revision_named' => 'Page Revision for :pageName', + 'pages_revision_restored_from' => 'Restored from #:id; :summary', 'pages_revisions_created_by' => 'Created By', 'pages_revisions_date' => 'Revision Date', 'pages_revisions_number' => '#', @@@ -262,7 -256,7 +263,7 @@@ 'attachments_upload' => 'Upload File', 'attachments_link' => 'Attach Link', 'attachments_set_link' => 'Set Link', - 'attachments_delete_confirm' => 'Click delete again to confirm you want to delete this attachment.', + 'attachments_delete' => 'Are you sure you want to delete this attachment?', 'attachments_dropzone' => 'Drop files or click here to attach a file', 'attachments_no_files' => 'No files have been uploaded', 'attachments_explain_link' => 'You can attach a link if you\'d prefer not to upload a file. This can be a link to another page or a link to a file in the cloud.', @@@ -271,7 -265,6 +272,7 @@@ 'attachments_link_url' => 'Link to file', 'attachments_link_url_hint' => 'Url of site or file', 'attach' => 'Attach', + 'attachments_insert_link' => 'Add Attachment Link to Page', 'attachments_edit_file' => 'Edit File', 'attachments_edit_file_name' => 'File Name', 'attachments_edit_drop_upload' => 'Drop files or click here to upload and overwrite', @@@ -319,4 -312,4 +320,4 @@@ 'revision_restore_confirm' => 'Are you sure you want to restore this revision? The current page contents will be replaced.', 'revision_delete_success' => 'Revision deleted', 'revision_cannot_delete_latest' => 'Cannot delete the latest revision.' -]; +]; diff --combined resources/views/partials/entity-export-menu.blade.php index 6d23af07c,42c2eb79a..2b0f5c19d --- a/resources/views/partials/entity-export-menu.blade.php +++ b/resources/views/partials/entity-export-menu.blade.php @@@ -1,12 -1,13 +1,13 @@@ - diff --combined routes/web.php index 72d089078,4d00b5ff6..2bba3e2cf --- a/routes/web.php +++ b/routes/web.php @@@ -1,6 -1,5 +1,6 @@@ 'auth'], // Shelves Route::get('/create-shelf', 'BookshelfController@create'); - Route::group(['prefix' => 'shelves'], function() { + Route::group(['prefix' => 'shelves'], function () { Route::get('/', 'BookshelfController@index'); Route::post('/', 'BookshelfController@store'); Route::get('/{slug}/edit', 'BookshelfController@edit'); @@@ -48,6 -47,8 +48,8 @@@ Route::put('/{bookSlug}/sort', 'BookSortController@update'); Route::get('/{bookSlug}/export/html', 'BookExportController@html'); Route::get('/{bookSlug}/export/pdf', 'BookExportController@pdf'); + Route::get('/{bookSlug}/export/markdown', 'BookExportController@markdown'); + Route::get('/{bookSlug}/export/zip', 'BookExportController@zip'); Route::get('/{bookSlug}/export/plaintext', 'BookExportController@plainText'); // Pages @@@ -58,6 -59,7 +60,7 @@@ Route::get('/{bookSlug}/page/{pageSlug}', 'PageController@show'); Route::get('/{bookSlug}/page/{pageSlug}/export/pdf', 'PageExportController@pdf'); Route::get('/{bookSlug}/page/{pageSlug}/export/html', 'PageExportController@html'); + Route::get('/{bookSlug}/page/{pageSlug}/export/markdown', 'PageExportController@markdown'); Route::get('/{bookSlug}/page/{pageSlug}/export/plaintext', 'PageExportController@plainText'); Route::get('/{bookSlug}/page/{pageSlug}/edit', 'PageController@edit'); Route::get('/{bookSlug}/page/{pageSlug}/move', 'PageController@showMove'); @@@ -92,6 -94,7 +95,7 @@@ Route::get('/{bookSlug}/chapter/{chapterSlug}/permissions', 'ChapterController@showPermissions'); Route::get('/{bookSlug}/chapter/{chapterSlug}/export/pdf', 'ChapterExportController@pdf'); Route::get('/{bookSlug}/chapter/{chapterSlug}/export/html', 'ChapterExportController@html'); + Route::get('/{bookSlug}/chapter/{chapterSlug}/export/markdown', 'ChapterExportController@markdown'); Route::get('/{bookSlug}/chapter/{chapterSlug}/export/plaintext', 'ChapterExportController@plainText'); Route::put('/{bookSlug}/chapter/{chapterSlug}/permissions', 'ChapterController@permissions'); Route::get('/{bookSlug}/chapter/{chapterSlug}/delete', 'ChapterController@showDelete'); @@@ -99,17 -102,25 +103,17 @@@ }); // User Profile routes - Route::get('/user/{userId}', 'UserController@showProfilePage'); + Route::get('/user/{slug}', 'UserProfileController@show'); // Image routes - Route::group(['prefix' => 'images'], function () { - - // Gallery - Route::get('/gallery', 'Images\GalleryImageController@list'); - Route::post('/gallery', 'Images\GalleryImageController@create'); - - // Drawio - Route::get('/drawio', 'Images\DrawioImageController@list'); - Route::get('/drawio/base64/{id}', 'Images\DrawioImageController@getAsBase64'); - Route::post('/drawio', 'Images\DrawioImageController@create'); - - // Shared gallery & draw.io endpoint - Route::get('/usage/{id}', 'Images\ImageController@usage'); - Route::put('/{id}', 'Images\ImageController@update'); - Route::delete('/{id}', 'Images\ImageController@destroy'); - }); + Route::get('/images/gallery', 'Images\GalleryImageController@list'); + Route::post('/images/gallery', 'Images\GalleryImageController@create'); + Route::get('/images/drawio', 'Images\DrawioImageController@list'); + Route::get('/images/drawio/base64/{id}', 'Images\DrawioImageController@getAsBase64'); + Route::post('/images/drawio', 'Images\DrawioImageController@create'); + Route::get('/images/edit/{id}', 'Images\ImageController@edit'); + Route::put('/images/{id}', 'Images\ImageController@update'); + Route::delete('/images/{id}', 'Images\ImageController@destroy'); // Attachments routes Route::get('/attachments/{id}', 'AttachmentController@get'); @@@ -117,7 -128,6 +121,7 @@@ Route::post('/attachments/upload/{id}', 'AttachmentController@uploadUpdate'); Route::post('/attachments/link', 'AttachmentController@attachLink'); Route::put('/attachments/{id}', 'AttachmentController@update'); + Route::get('/attachments/edit/{id}', 'AttachmentController@getUpdateForm'); Route::get('/attachments/get/page/{pageId}', 'AttachmentController@listForPage'); Route::put('/attachments/sort/page/{pageId}', 'AttachmentController@sortForPage'); Route::delete('/attachments/{id}', 'AttachmentController@delete'); @@@ -128,7 -138,8 +132,7 @@@ Route::delete('/ajax/page/{id}', 'PageController@ajaxDestroy'); // Tag routes (AJAX) - Route::group(['prefix' => 'ajax/tags'], function() { - Route::get('/get/{entityType}/{entityId}', 'TagController@getForEntity'); + Route::group(['prefix' => 'ajax/tags'], function () { Route::get('/suggest/names', 'TagController@getNameSuggestions'); Route::get('/suggest/values', 'TagController@getValueSuggestions'); }); @@@ -136,9 -147,9 +140,9 @@@ Route::get('/ajax/search/entities', 'SearchController@searchEntitiesAjax'); // Comments - Route::post('/ajax/page/{pageId}/comment', 'CommentController@savePageComment'); - Route::put('/ajax/comment/{id}', 'CommentController@update'); - Route::delete('/ajax/comment/{id}', 'CommentController@destroy'); + Route::post('/comment/{pageId}', 'CommentController@savePageComment'); + Route::put('/comment/{id}', 'CommentController@update'); + Route::delete('/comment/{id}', 'CommentController@destroy'); // Links Route::get('/link/{id}', 'PageController@redirectFromLink'); @@@ -149,18 -160,9 +153,18 @@@ Route::get('/search/chapter/{bookId}', 'SearchController@searchChapter'); Route::get('/search/entity/siblings', 'SearchController@searchSiblings'); + // User Search + Route::get('/search/users/select', 'UserSearchController@forSelect'); + + // Template System Route::get('/templates', 'PageTemplateController@list'); Route::get('/templates/{templateId}', 'PageTemplateController@get'); + // Favourites + Route::get('/favourites', 'FavouriteController@index'); + Route::post('/favourites/add', 'FavouriteController@add'); + Route::post('/favourites/remove', 'FavouriteController@remove'); + // Other Pages Route::get('/', 'HomeController@index'); Route::get('/home', 'HomeController@index'); @@@ -172,20 -174,9 +176,20 @@@ Route::post('/', 'SettingController@update'); // Maintenance - Route::get('/maintenance', 'SettingController@showMaintenance'); - Route::delete('/maintenance/cleanup-images', 'SettingController@cleanupImages'); - Route::post('/maintenance/send-test-email', 'SettingController@sendTestEmail'); + Route::get('/maintenance', 'MaintenanceController@index'); + Route::delete('/maintenance/cleanup-images', 'MaintenanceController@cleanupImages'); + Route::post('/maintenance/send-test-email', 'MaintenanceController@sendTestEmail'); + + // Recycle Bin + Route::get('/recycle-bin', 'RecycleBinController@index'); + Route::post('/recycle-bin/empty', 'RecycleBinController@empty'); + Route::get('/recycle-bin/{id}/destroy', 'RecycleBinController@showDestroy'); + Route::delete('/recycle-bin/{id}', 'RecycleBinController@destroy'); + Route::get('/recycle-bin/{id}/restore', 'RecycleBinController@showRestore'); + Route::post('/recycle-bin/{id}/restore', 'RecycleBinController@restore'); + + // Audit Log + Route::get('/audit', 'AuditLogController@index'); // Users Route::get('/users', 'UserController@index'); @@@ -211,24 -202,24 +215,24 @@@ Route::delete('/users/{userId}/api-tokens/{tokenId}', 'UserApiTokenController@destroy'); // Roles - Route::get('/roles', 'PermissionController@listRoles'); - Route::get('/roles/new', 'PermissionController@createRole'); - Route::post('/roles/new', 'PermissionController@storeRole'); - Route::get('/roles/delete/{id}', 'PermissionController@showDeleteRole'); - Route::delete('/roles/delete/{id}', 'PermissionController@deleteRole'); - Route::get('/roles/{id}', 'PermissionController@editRole'); - Route::put('/roles/{id}', 'PermissionController@updateRole'); + Route::get('/roles', 'RoleController@list'); + Route::get('/roles/new', 'RoleController@create'); + Route::post('/roles/new', 'RoleController@store'); + Route::get('/roles/delete/{id}', 'RoleController@showDelete'); + Route::delete('/roles/delete/{id}', 'RoleController@delete'); + Route::get('/roles/{id}', 'RoleController@edit'); + Route::put('/roles/{id}', 'RoleController@update'); }); }); // Social auth routes -Route::get('/login/service/{socialDriver}', 'Auth\SocialController@getSocialLogin'); -Route::get('/login/service/{socialDriver}/callback', 'Auth\SocialController@socialCallback'); +Route::get('/login/service/{socialDriver}', 'Auth\SocialController@login'); +Route::get('/login/service/{socialDriver}/callback', 'Auth\SocialController@callback'); Route::group(['middleware' => 'auth'], function () { - Route::get('/login/service/{socialDriver}/detach', 'Auth\SocialController@detachSocialAccount'); + Route::post('/login/service/{socialDriver}/detach', 'Auth\SocialController@detach'); }); -Route::get('/register/service/{socialDriver}', 'Auth\SocialController@socialRegister'); +Route::get('/register/service/{socialDriver}', 'Auth\SocialController@register'); // Login/Logout routes Route::get('/login', 'Auth\LoginController@getLogin'); @@@ -260,4 -251,4 +264,4 @@@ Route::post('/password/email', 'Auth\Fo Route::get('/password/reset/{token}', 'Auth\ResetPasswordController@showResetForm'); Route::post('/password/reset', 'Auth\ResetPasswordController@reset'); -Route::fallback('HomeController@getNotFound'); +Route::fallback('HomeController@getNotFound')->name('fallback');