]> BookStack Code Mirror - bookstack/commitdiff
Merge branch 'login-auto-redirect' into development
authorDan Brown <redacted>
Tue, 21 Jun 2022 14:38:01 +0000 (15:38 +0100)
committerDan Brown <redacted>
Tue, 21 Jun 2022 14:38:01 +0000 (15:38 +0100)
183 files changed:
.github/FUNDING.yml
.github/ISSUE_TEMPLATE/config.yml
.github/ISSUE_TEMPLATE/language_request.yml
.github/translators.txt
app/Actions/ActivityType.php
app/Actions/TagRepo.php
app/Auth/Access/GroupSyncService.php
app/Config/session.php
app/Entities/Repos/BookRepo.php
app/Entities/Repos/BookshelfRepo.php
app/Entities/Repos/PageRepo.php
app/Entities/Tools/Cloner.php
app/Entities/Tools/ExportFormatter.php
app/Entities/Tools/HierarchyTransformer.php [new file with mode: 0644]
app/Entities/Tools/SearchIndex.php
app/Entities/Tools/SearchRunner.php
app/Entities/Tools/TrashCan.php
app/Exceptions/Handler.php
app/Exceptions/JsonDebugException.php
app/Http/Controllers/Api/BookApiController.php
app/Http/Controllers/Api/BookExportApiController.php
app/Http/Controllers/Api/BookshelfApiController.php
app/Http/Controllers/Api/ChapterExportApiController.php
app/Http/Controllers/Api/PageExportApiController.php
app/Http/Controllers/AttachmentController.php
app/Http/Controllers/BookController.php
app/Http/Controllers/BookExportController.php
app/Http/Controllers/BookshelfController.php
app/Http/Controllers/ChapterController.php
app/Http/Controllers/ChapterExportController.php
app/Http/Controllers/Controller.php
app/Http/Controllers/PageExportController.php
app/Http/Controllers/SettingController.php
app/Http/Middleware/Localization.php
app/Http/Request.php
app/Http/Responses/DownloadResponseFactory.php [new file with mode: 0644]
app/Uploads/AttachmentService.php
app/Util/WebSafeMimeSniffer.php
composer.lock
database/factories/Auth/RoleFactory.php
dev/api/responses/books-list.json
dev/api/responses/books-update.json
dev/api/responses/shelves-list.json
dev/api/responses/shelves-update.json
package-lock.json
package.json
resources/icons/download.svg [new file with mode: 0644]
resources/js/code.mjs
resources/js/components/chapter-contents.js [new file with mode: 0644]
resources/js/components/chapter-toggle.js [deleted file]
resources/js/components/code-editor.js
resources/js/components/code-textarea.js [new file with mode: 0644]
resources/js/components/dropdown-search.js
resources/js/components/dropdown.js
resources/js/components/index.js
resources/js/components/markdown-editor.js
resources/js/services/animations.js
resources/js/services/vdom.js [new file with mode: 0644]
resources/js/wysiwyg/drop-paste-handling.js
resources/js/wysiwyg/plugin-codeeditor.js
resources/lang/ar/activities.php
resources/lang/ar/settings.php
resources/lang/bg/settings.php
resources/lang/bs/settings.php
resources/lang/ca/settings.php
resources/lang/cs/settings.php
resources/lang/da/settings.php
resources/lang/de/settings.php
resources/lang/de_informal/settings.php
resources/lang/en/activities.php
resources/lang/en/common.php
resources/lang/en/entities.php
resources/lang/en/settings.php
resources/lang/es/settings.php
resources/lang/es_AR/settings.php
resources/lang/et/settings.php
resources/lang/eu/settings.php
resources/lang/fa/activities.php
resources/lang/fa/common.php
resources/lang/fa/entities.php
resources/lang/fa/settings.php
resources/lang/fr/settings.php
resources/lang/he/settings.php
resources/lang/hr/settings.php
resources/lang/hu/settings.php
resources/lang/id/settings.php
resources/lang/it/entities.php
resources/lang/it/settings.php
resources/lang/ja/settings.php
resources/lang/ko/settings.php
resources/lang/lt/settings.php
resources/lang/lv/editor.php
resources/lang/lv/entities.php
resources/lang/lv/settings.php
resources/lang/nb/settings.php
resources/lang/nl/editor.php
resources/lang/nl/entities.php
resources/lang/nl/settings.php
resources/lang/pl/entities.php
resources/lang/pl/settings.php
resources/lang/pt/settings.php
resources/lang/pt_BR/settings.php
resources/lang/ru/settings.php
resources/lang/sk/settings.php
resources/lang/sl/settings.php
resources/lang/sv/settings.php
resources/lang/tr/settings.php
resources/lang/uk/editor.php
resources/lang/uk/entities.php
resources/lang/uk/settings.php
resources/lang/uz/settings.php
resources/lang/vi/settings.php
resources/lang/zh_CN/entities.php
resources/lang/zh_CN/errors.php
resources/lang/zh_CN/settings.php
resources/lang/zh_TW/settings.php
resources/sass/_blocks.scss
resources/sass/_components.scss
resources/sass/_forms.scss
resources/sass/_header.scss
resources/sass/_layout.scss
resources/sass/_lists.scss
resources/sass/_pages.scss
resources/sass/export-styles.scss
resources/sass/styles.scss
resources/views/api-docs/index.blade.php
resources/views/api-docs/parts/getting-started.blade.php
resources/views/attachments/list.blade.php
resources/views/books/edit.blade.php
resources/views/books/export.blade.php [deleted file]
resources/views/books/parts/convert-to-shelf.blade.php [new file with mode: 0644]
resources/views/books/show.blade.php
resources/views/chapters/edit.blade.php
resources/views/chapters/export.blade.php [deleted file]
resources/views/chapters/parts/child-menu.blade.php
resources/views/chapters/parts/convert-to-book.blade.php [new file with mode: 0644]
resources/views/chapters/parts/list-item.blade.php
resources/views/chapters/show.blade.php
resources/views/common/confirm-dialog.blade.php
resources/views/common/export-styles.blade.php [deleted file]
resources/views/common/header.blade.php
resources/views/entities/breadcrumb-listing.blade.php
resources/views/entities/export-menu.blade.php
resources/views/entities/meta.blade.php
resources/views/entities/selector-popup.blade.php
resources/views/exports/book.blade.php [new file with mode: 0644]
resources/views/exports/chapter.blade.php [new file with mode: 0644]
resources/views/exports/page.blade.php [moved from resources/views/pages/export.blade.php with 71% similarity]
resources/views/exports/parts/book-contents-menu.blade.php [new file with mode: 0644]
resources/views/exports/parts/chapter-contents-menu.blade.php [new file with mode: 0644]
resources/views/exports/parts/chapter-item.blade.php [new file with mode: 0644]
resources/views/exports/parts/custom-head.blade.php [moved from resources/views/common/export-custom-head.blade.php with 100% similarity]
resources/views/exports/parts/meta.blade.php [moved from resources/views/entities/export-meta.blade.php with 100% similarity]
resources/views/exports/parts/page-item.blade.php [new file with mode: 0644]
resources/views/exports/parts/styles.blade.php [new file with mode: 0644]
resources/views/form/entity-permissions.blade.php
resources/views/form/user-select.blade.php
resources/views/home/default.blade.php
resources/views/layouts/export.blade.php
resources/views/layouts/tri.blade.php
resources/views/pages/parts/code-editor.blade.php
resources/views/pages/parts/editor-toolbar.blade.php
resources/views/pages/parts/image-manager.blade.php
resources/views/pages/show.blade.php
resources/views/settings/audit.blade.php
resources/views/settings/customization.blade.php
resources/views/shelves/show.blade.php
resources/views/users/delete.blade.php
routes/web.php
tests/Api/BooksApiTest.php
tests/Api/ShelvesApiTest.php
tests/Auth/GroupSyncServiceTest.php [new file with mode: 0644]
tests/Auth/LdapTest.php
tests/Auth/MfaVerificationTest.php
tests/Entity/BookTest.php
tests/Entity/ConvertTest.php [new file with mode: 0644]
tests/Entity/EntitySearchTest.php
tests/Entity/ExportTest.php
tests/OpenGraphTest.php
tests/SharedTestHelpers.php
tests/TestResponse.php
tests/UrlTest.php
version

index 01b847186cf3b710a2209b35fb4a9e52029998c5..f3f51c7943cc7cdbca62f5bfcceff4942d534220 100644 (file)
@@ -1,3 +1,4 @@
 # These are supported funding model platforms
 
 github: [ssddanbrown]
+ko_fi: ssddanbrown
\ No newline at end of file
index d0ac0c6ab5d1c99c4b9c7bc849384a666996871d..019667388eb0f1f920fe58358d0e5197b84b4888 100644 (file)
@@ -1,9 +1,13 @@
 blank_issues_enabled: false
 contact_links:
-  - name: Discord chat support
+  - name: Discord Chat Support
     url: https://discord.gg/ztkBqR2
-    about: Realtime support / chat with the community and the team.
+    about: Realtime support & chat with the BookStack community and the team.
 
   - name: Debugging & Common Issues
     url: https://www.bookstackapp.com/docs/admin/debugging/
-    about: Find details on how to debug issues and view common issues with thier resolutions.
+    about: Find details on how to debug issues and view common issues with their resolutions.
+
+  - name: Official Support Plans
+    url: https://www.bookstackapp.com/support/
+    about: View our official support plans that offer assured support for business.
\ No newline at end of file
index 6f768cd74799f23d608e755110502cbe4971c64f..fad9ef1e83fb0e4bdf256ea57653bb9f89026dcb 100644 (file)
@@ -1,5 +1,5 @@
 name: Language Request
-description: Request a new language to be added to CrowdIn for you to translate
+description: Request a new language to be added to Crowdin for you to translate
 labels: [":earth_africa: Translations"]
 assignees:
   - ssddanbrown
@@ -23,7 +23,7 @@ body:
         This issue template is to request a new language be added to our [Crowdin translation management project](https://crowdin.com/project/bookstack).
         Please don't use this template to request a new language that you are not prepared to provide translations for.
       options:
-        - label: I confirm I'm offering to help translate for this new language via CrowdIn.
+        - label: I confirm I'm offering to help translate for this new language via Crowdin.
           required: true
   - type: markdown
     attributes:
index 4dc671454d573092fe9594a8b08b5bda01a3bf28..7ba2ec112f4aacf8206c2c9358bee301f6e3414f 100644 (file)
@@ -242,3 +242,4 @@ sgenc :: Turkish
 Shukrullo (vodiylik) :: Uzbek
 William W. (Nevnt) :: Chinese Traditional
 eamaro :: Portuguese
+Ypsilon-dev :: Arabic
index 8b5213a8b2af809f5e668f603a49d36f27dc1ecb..0ad25a5ab7210127442960ad2897b52ed7740bad 100644 (file)
@@ -16,11 +16,13 @@ class ActivityType
     const CHAPTER_MOVE = 'chapter_move';
 
     const BOOK_CREATE = 'book_create';
+    const BOOK_CREATE_FROM_CHAPTER = 'book_create_from_chapter';
     const BOOK_UPDATE = 'book_update';
     const BOOK_DELETE = 'book_delete';
     const BOOK_SORT = 'book_sort';
 
     const BOOKSHELF_CREATE = 'bookshelf_create';
+    const BOOKSHELF_CREATE_FROM_BOOK = 'bookshelf_create_from_book';
     const BOOKSHELF_UPDATE = 'bookshelf_update';
     const BOOKSHELF_DELETE = 'bookshelf_delete';
 
index 0acee7486b92a4dec062834799b99a406e6bba39..8cf1076019e6318785a3d5ed13fc8d4490b16517 100644 (file)
@@ -28,10 +28,10 @@ class TagRepo
                 'name',
                 ($searchTerm || $nameFilter) ? 'value' : DB::raw('COUNT(distinct value) as `values`'),
                 DB::raw('COUNT(id) as usages'),
-                DB::raw('SUM(IF(entity_type = \'BookStack\\\\Page\', 1, 0)) as page_count'),
-                DB::raw('SUM(IF(entity_type = \'BookStack\\\\Chapter\', 1, 0)) as chapter_count'),
-                DB::raw('SUM(IF(entity_type = \'BookStack\\\\Book\', 1, 0)) as book_count'),
-                DB::raw('SUM(IF(entity_type = \'BookStack\\\\BookShelf\', 1, 0)) as shelf_count'),
+                DB::raw('SUM(IF(entity_type = \'page\', 1, 0)) as page_count'),
+                DB::raw('SUM(IF(entity_type = \'chapter\', 1, 0)) as chapter_count'),
+                DB::raw('SUM(IF(entity_type = \'book\', 1, 0)) as book_count'),
+                DB::raw('SUM(IF(entity_type = \'bookshelf\', 1, 0)) as shelf_count'),
             ])
             ->orderBy($nameFilter ? 'value' : 'name');
 
index db19b007ac32b31f5a2bb6112514b6aa7b271826..74f0539d82ffcce72d8261be3e02ca6dcd2dddef 100644 (file)
@@ -28,10 +28,8 @@ class GroupSyncService
      */
     protected function externalIdMatchesGroupNames(string $externalId, array $groupNames): bool
     {
-        $externalAuthIds = explode(',', strtolower($externalId));
-
-        foreach ($externalAuthIds as $externalAuthId) {
-            if (in_array(trim($externalAuthId), $groupNames)) {
+        foreach ($this->parseRoleExternalAuthId($externalId) as $externalAuthId) {
+            if (in_array($externalAuthId, $groupNames)) {
                 return true;
             }
         }
@@ -39,6 +37,18 @@ class GroupSyncService
         return false;
     }
 
+    protected function parseRoleExternalAuthId(string $externalId): array
+    {
+        $inputIds = preg_split('/(?<!\\\),/', $externalId);
+        $cleanIds = [];
+
+        foreach ($inputIds as $inputId) {
+            $cleanIds[] = str_replace('\,', ',', trim($inputId));
+        }
+
+        return $cleanIds;
+    }
+
     /**
      * Match an array of group names to BookStack system roles.
      * Formats group names to be lower-case and hyphenated.
index 4bbb789010ff341a0cf6d6b505201412a5b76819..a00d758071201903b137d12d27a2581f7e481867 100644 (file)
@@ -72,7 +72,7 @@ return [
     // to the server if the browser has a HTTPS connection. This will keep
     // the cookie from being sent to you if it can not be done securely.
     'secure' => env('SESSION_SECURE_COOKIE', null)
-        ?? Str::startsWith(env('APP_URL'), 'https:'),
+        ?? Str::startsWith(env('APP_URL', ''), 'https:'),
 
     // HTTP Access Only
     // Setting this value to true will prevent JavaScript from accessing the
index 7c4b280a8ca2b6a2bd690f7adca54add1382f2a2..b5944fd460b2f23737e9c3ddcf22be005e72df4e 100644 (file)
@@ -91,6 +91,7 @@ class BookRepo
     {
         $book = new Book();
         $this->baseRepo->create($book, $input);
+        $this->baseRepo->updateCoverImage($book, $input['image'] ?? null);
         Activity::add(ActivityType::BOOK_CREATE, $book);
 
         return $book;
@@ -102,6 +103,11 @@ class BookRepo
     public function update(Book $book, array $input): Book
     {
         $this->baseRepo->update($book, $input);
+
+        if (array_key_exists('image', $input)) {
+            $this->baseRepo->updateCoverImage($book, $input['image'], $input['image'] === null);
+        }
+
         Activity::add(ActivityType::BOOK_UPDATE, $book);
 
         return $book;
index ceabba59af456e277b1f5fc49dedf90b11c02a1e..b85289b97d24d73ef7c3c17a5c9ab179c2114c62 100644 (file)
@@ -6,12 +6,10 @@ use BookStack\Actions\ActivityType;
 use BookStack\Entities\Models\Book;
 use BookStack\Entities\Models\Bookshelf;
 use BookStack\Entities\Tools\TrashCan;
-use BookStack\Exceptions\ImageUploadException;
 use BookStack\Exceptions\NotFoundException;
 use BookStack\Facades\Activity;
 use Exception;
 use Illuminate\Contracts\Pagination\LengthAwarePaginator;
-use Illuminate\Http\UploadedFile;
 use Illuminate\Support\Collection;
 
 class BookshelfRepo
@@ -89,6 +87,7 @@ class BookshelfRepo
     {
         $shelf = new Bookshelf();
         $this->baseRepo->create($shelf, $input);
+        $this->baseRepo->updateCoverImage($shelf, $input['image'] ?? null);
         $this->updateBooks($shelf, $bookIds);
         Activity::add(ActivityType::BOOKSHELF_CREATE, $shelf);
 
@@ -106,14 +105,17 @@ class BookshelfRepo
             $this->updateBooks($shelf, $bookIds);
         }
 
+        if (array_key_exists('image', $input)) {
+            $this->baseRepo->updateCoverImage($shelf, $input['image'], $input['image'] === null);
+        }
+
         Activity::add(ActivityType::BOOKSHELF_UPDATE, $shelf);
 
         return $shelf;
     }
 
     /**
-     * Update which books are assigned to this shelf by
-     * syncing the given book ids.
+     * Update which books are assigned to this shelf by syncing the given book ids.
      * Function ensures the books are visible to the current user and existing.
      */
     protected function updateBooks(Bookshelf $shelf, array $bookIds)
@@ -132,17 +134,6 @@ class BookshelfRepo
         $shelf->books()->sync($syncData);
     }
 
-    /**
-     * Update the given shelf cover image, or clear it.
-     *
-     * @throws ImageUploadException
-     * @throws Exception
-     */
-    public function updateCoverImage(Bookshelf $shelf, ?UploadedFile $coverImage, bool $removeImage = false)
-    {
-        $this->baseRepo->updateCoverImage($shelf, $coverImage, $removeImage);
-    }
-
     /**
      * Copy down the permissions of the given shelf to all child books.
      */
index c106d2fd30679d6f663cdd845ddbaf1b0aee4fe5..e3c6bd17af95b7ea869ffaa9c70ad059d248ee10 100644 (file)
@@ -392,23 +392,6 @@ class PageRepo
         return $parentClass::visible()->where('id', '=', $entityId)->first();
     }
 
-    /**
-     * Change the page's parent to the given entity.
-     */
-    protected function changeParent(Page $page, Entity $parent)
-    {
-        $book = ($parent instanceof Chapter) ? $parent->book : $parent;
-        $page->chapter_id = ($parent instanceof Chapter) ? $parent->id : 0;
-        $page->save();
-
-        if ($page->book->id !== $book->id) {
-            $page->changeBook($book->id);
-        }
-
-        $page->load('book');
-        $book->rebuildPermissions();
-    }
-
     /**
      * Get a page revision to update for the given page.
      * Checks for an existing revisions before providing a fresh one.
index b4923b90aac1f0048c044b2cc374a8cb25b8f7b9..92b62a7547a7422785d09b7c4317d31cec9ec19a 100644 (file)
@@ -16,25 +16,10 @@ use Illuminate\Http\UploadedFile;
 
 class Cloner
 {
-    /**
-     * @var PageRepo
-     */
-    protected $pageRepo;
-
-    /**
-     * @var ChapterRepo
-     */
-    protected $chapterRepo;
-
-    /**
-     * @var BookRepo
-     */
-    protected $bookRepo;
-
-    /**
-     * @var ImageService
-     */
-    protected $imageService;
+    protected PageRepo $pageRepo;
+    protected ChapterRepo $chapterRepo;
+    protected BookRepo $bookRepo;
+    protected ImageService $imageService;
 
     public function __construct(PageRepo $pageRepo, ChapterRepo $chapterRepo, BookRepo $bookRepo, ImageService $imageService)
     {
@@ -50,11 +35,8 @@ class Cloner
     public function clonePage(Page $original, Entity $parent, string $newName): Page
     {
         $copyPage = $this->pageRepo->getNewDraftPage($parent);
-        $pageData = $original->getAttributes();
-
-        // Update name & tags
+        $pageData = $this->entityToInputData($original);
         $pageData['name'] = $newName;
-        $pageData['tags'] = $this->entityTagsToInputArray($original);
 
         return $this->pageRepo->publishDraft($copyPage, $pageData);
     }
@@ -65,9 +47,8 @@ class Cloner
      */
     public function cloneChapter(Chapter $original, Book $parent, string $newName): Chapter
     {
-        $chapterDetails = $original->getAttributes();
+        $chapterDetails = $this->entityToInputData($original);
         $chapterDetails['name'] = $newName;
-        $chapterDetails['tags'] = $this->entityTagsToInputArray($original);
 
         $copyChapter = $this->chapterRepo->create($chapterDetails, $parent);
 
@@ -87,9 +68,8 @@ class Cloner
      */
     public function cloneBook(Book $original, string $newName): Book
     {
-        $bookDetails = $original->getAttributes();
+        $bookDetails = $this->entityToInputData($original);
         $bookDetails['name'] = $newName;
-        $bookDetails['tags'] = $this->entityTagsToInputArray($original);
 
         $copyBook = $this->bookRepo->create($bookDetails);
 
@@ -104,26 +84,48 @@ class Cloner
             }
         }
 
-        if ($original->cover) {
-            try {
-                $tmpImgFile = tmpfile();
-                $uploadedFile = $this->imageToUploadedFile($original->cover, $tmpImgFile);
-                $this->bookRepo->updateCoverImage($copyBook, $uploadedFile, false);
-            } catch (\Exception $exception) {
-            }
+        return $copyBook;
+    }
+
+    /**
+     * Convert an entity to a raw data array of input data.
+     *
+     * @return array<string, mixed>
+     */
+    public function entityToInputData(Entity $entity): array
+    {
+        $inputData = $entity->getAttributes();
+        $inputData['tags'] = $this->entityTagsToInputArray($entity);
+
+        // Add a cover to the data if existing on the original entity
+        if ($entity->cover instanceof Image) {
+            $uploadedFile = $this->imageToUploadedFile($entity->cover);
+            $inputData['image'] = $uploadedFile;
         }
 
-        return $copyBook;
+        return $inputData;
+    }
+
+    /**
+     * Copy the permission settings from the source entity to the target entity.
+     */
+    public function copyEntityPermissions(Entity $sourceEntity, Entity $targetEntity): void
+    {
+        $targetEntity->restricted = $sourceEntity->restricted;
+        $permissions = $sourceEntity->permissions()->get(['role_id', 'action'])->toArray();
+        $targetEntity->permissions()->delete();
+        $targetEntity->permissions()->createMany($permissions);
+        $targetEntity->rebuildPermissions();
     }
 
     /**
      * Convert an image instance to an UploadedFile instance to mimic
      * a file being uploaded.
      */
-    protected function imageToUploadedFile(Image $image, &$tmpFile): ?UploadedFile
+    protected function imageToUploadedFile(Image $image): ?UploadedFile
     {
         $imgData = $this->imageService->getImageData($image);
-        $tmpImgFilePath = stream_get_meta_data($tmpFile)['uri'];
+        $tmpImgFilePath = tempnam(sys_get_temp_dir(), 'bs_cover_clone_');
         file_put_contents($tmpImgFilePath, $imgData);
 
         return new UploadedFile($tmpImgFilePath, basename($image->path));
index 99aa4536f52bef1ec985c2b46aace5ed5c0f2669..97902db8219c12d5183a97b0e5a3565d4c158378 100644 (file)
@@ -39,7 +39,7 @@ class ExportFormatter
     public function pageToContainedHtml(Page $page)
     {
         $page->html = (new PageContent($page))->render();
-        $pageHtml = view('pages.export', [
+        $pageHtml = view('exports.page', [
             'page'       => $page,
             'format'     => 'html',
             'cspContent' => $this->cspService->getCspMetaTagValue(),
@@ -59,7 +59,7 @@ class ExportFormatter
         $pages->each(function ($page) {
             $page->html = (new PageContent($page))->render();
         });
-        $html = view('chapters.export', [
+        $html = view('exports.chapter', [
             'chapter'    => $chapter,
             'pages'      => $pages,
             'format'     => 'html',
@@ -77,7 +77,7 @@ class ExportFormatter
     public function bookToContainedHtml(Book $book)
     {
         $bookTree = (new BookContents($book))->getTree(false, true);
-        $html = view('books.export', [
+        $html = view('exports.book', [
             'book'         => $book,
             'bookChildren' => $bookTree,
             'format'       => 'html',
@@ -95,7 +95,7 @@ class ExportFormatter
     public function pageToPdf(Page $page)
     {
         $page->html = (new PageContent($page))->render();
-        $html = view('pages.export', [
+        $html = view('exports.page', [
             'page'   => $page,
             'format' => 'pdf',
             'engine' => $this->pdfGenerator->getActiveEngine(),
@@ -116,7 +116,7 @@ class ExportFormatter
             $page->html = (new PageContent($page))->render();
         });
 
-        $html = view('chapters.export', [
+        $html = view('exports.chapter', [
             'chapter' => $chapter,
             'pages'   => $pages,
             'format'  => 'pdf',
@@ -134,7 +134,7 @@ class ExportFormatter
     public function bookToPdf(Book $book)
     {
         $bookTree = (new BookContents($book))->getTree(false, true);
-        $html = view('books.export', [
+        $html = view('exports.book', [
             'book'         => $book,
             'bookChildren' => $bookTree,
             'format'       => 'pdf',
diff --git a/app/Entities/Tools/HierarchyTransformer.php b/app/Entities/Tools/HierarchyTransformer.php
new file mode 100644 (file)
index 0000000..50d9e2e
--- /dev/null
@@ -0,0 +1,87 @@
+<?php
+
+namespace BookStack\Entities\Tools;
+
+use BookStack\Actions\ActivityType;
+use BookStack\Entities\Models\Book;
+use BookStack\Entities\Models\Bookshelf;
+use BookStack\Entities\Models\Chapter;
+use BookStack\Entities\Models\Page;
+use BookStack\Entities\Repos\BookRepo;
+use BookStack\Entities\Repos\BookshelfRepo;
+use BookStack\Facades\Activity;
+
+class HierarchyTransformer
+{
+    protected BookRepo $bookRepo;
+    protected BookshelfRepo $shelfRepo;
+    protected Cloner $cloner;
+    protected TrashCan $trashCan;
+
+    public function __construct(BookRepo $bookRepo, BookshelfRepo $shelfRepo, Cloner $cloner, TrashCan $trashCan)
+    {
+        $this->bookRepo = $bookRepo;
+        $this->shelfRepo = $shelfRepo;
+        $this->cloner = $cloner;
+        $this->trashCan = $trashCan;
+    }
+
+    /**
+     * Transform a chapter into a book.
+     * Does not check permissions, check before calling.
+     */
+    public function transformChapterToBook(Chapter $chapter): Book
+    {
+        $inputData = $this->cloner->entityToInputData($chapter);
+        $book = $this->bookRepo->create($inputData);
+        $this->cloner->copyEntityPermissions($chapter, $book);
+
+        /** @var Page $page */
+        foreach ($chapter->pages as $page) {
+            $page->chapter_id = 0;
+            $page->changeBook($book->id);
+        }
+
+        $this->trashCan->destroyEntity($chapter);
+
+        Activity::add(ActivityType::BOOK_CREATE_FROM_CHAPTER, $book);
+
+        return $book;
+    }
+
+    /**
+     * Transform a book into a shelf.
+     * Does not check permissions, check before calling.
+     */
+    public function transformBookToShelf(Book $book): Bookshelf
+    {
+        $inputData = $this->cloner->entityToInputData($book);
+        $shelf = $this->shelfRepo->create($inputData, []);
+        $this->cloner->copyEntityPermissions($book, $shelf);
+
+        $shelfBookSyncData = [];
+
+        /** @var Chapter $chapter */
+        foreach ($book->chapters as $index => $chapter) {
+            $newBook = $this->transformChapterToBook($chapter);
+            $shelfBookSyncData[$newBook->id] = ['order' => $index];
+            if (!$newBook->restricted) {
+                $this->cloner->copyEntityPermissions($shelf, $newBook);
+            }
+        }
+
+        if ($book->directPages->count() > 0) {
+            $book->name .= ' ' . trans('entities.pages');
+            $shelfBookSyncData[$book->id] = ['order' => count($shelfBookSyncData) + 1];
+            $book->save();
+        } else {
+            $this->trashCan->destroyEntity($book);
+        }
+
+        $shelf->books()->sync($shelfBookSyncData);
+
+        Activity::add(ActivityType::BOOKSHELF_CREATE_FROM_BOOK, $shelf);
+
+        return $shelf;
+    }
+}
index d43d982079d88262280dbe9c70b40d11632d0577..db44daadfb931122e2a449c9ab3078bf1974877d 100644 (file)
@@ -147,6 +147,8 @@ class SearchIndex
         ];
 
         $html = '<body>' . $html . '</body>';
+        $html = str_ireplace(['<br>', '<br />', '<br/>'], "\n", $html);
+
         libxml_use_internal_errors(true);
         $doc = new DOMDocument();
         $doc->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
index a0a44f3a553b0bf1791afc08645da769e59e70d8..a591914f3c1c85b20603ae9df2e5ba1634205a5d 100644 (file)
@@ -360,7 +360,7 @@ class SearchRunner
                     /** @var Connection $connection */
                     $connection = $query->getConnection();
                     $tagValue = (float) trim($connection->getPdo()->quote($tagValue), "'");
-                    $query->whereRaw("value ${tagOperator} ${tagValue}");
+                    $query->whereRaw("value {$tagOperator} {$tagValue}");
                 } else {
                     $query->where('value', $tagOperator, $tagValue);
                 }
index 1e130c9e17956cc8ecaf8db05a4d3d9807d5c67b..abec2e2d57a0fb1a67f3fa08a97e7ea58c050d8a 100644 (file)
@@ -344,7 +344,7 @@ class TrashCan
      *
      * @throws Exception
      */
-    protected function destroyEntity(Entity $entity): int
+    public function destroyEntity(Entity $entity): int
     {
         if ($entity instanceof Page) {
             return $this->destroyPage($entity);
index 317b011d87de92f3369d7b1b3a993efacdd1c864..187664f9ef7cf779c9f7e85321975e176f789d42 100644 (file)
@@ -21,6 +21,7 @@ class Handler extends ExceptionHandler
      */
     protected $dontReport = [
         NotFoundException::class,
+        StoppedAuthenticationException::class,
     ];
 
     /**
index e8d61305e1232eb748b007d8c506e85ae39a0385..8acc19778e5dad7a0106edf7f9c4fe28659be52b 100644 (file)
@@ -19,10 +19,13 @@ class JsonDebugException extends Exception
     }
 
     /**
-     * Covert this exception into a response.
+     * Convert this exception into a response.
+     * We add a manual data conversion to UTF8 to ensure any binary data is presentable as a JSON string.
      */
     public function render(): JsonResponse
     {
-        return response()->json($this->data);
+        $cleaned = mb_convert_encoding($this->data, 'UTF-8');
+
+        return response()->json($cleaned);
     }
 }
index b28e3eefa3af93802c894bfd8436d41176bf0702..15565c361f2ed6f3076a6dae6da92650c27f9901 100644 (file)
@@ -11,19 +11,6 @@ class BookApiController extends ApiController
 {
     protected $bookRepo;
 
-    protected $rules = [
-        'create' => [
-            'name'        => ['required', 'string', 'max:255'],
-            'description' => ['string', 'max:1000'],
-            'tags'        => ['array'],
-        ],
-        'update' => [
-            'name'        => ['string', 'min:1', 'max:255'],
-            'description' => ['string', 'max:1000'],
-            'tags'        => ['array'],
-        ],
-    ];
-
     public function __construct(BookRepo $bookRepo)
     {
         $this->bookRepo = $bookRepo;
@@ -37,19 +24,21 @@ class BookApiController extends ApiController
         $books = Book::visible();
 
         return $this->apiListingResponse($books, [
-            'id', 'name', 'slug', 'description', 'created_at', 'updated_at', 'created_by', 'updated_by', 'owned_by', 'image_id',
+            'id', 'name', 'slug', 'description', 'created_at', 'updated_at', 'created_by', 'updated_by', 'owned_by',
         ]);
     }
 
     /**
      * Create a new book in the system.
+     * The cover image of a book can be set by sending a file via an 'image' property within a 'multipart/form-data' request.
+     * If the 'image' property is null then the book cover image will be removed.
      *
      * @throws ValidationException
      */
     public function create(Request $request)
     {
         $this->checkPermission('book-create-all');
-        $requestData = $this->validate($request, $this->rules['create']);
+        $requestData = $this->validate($request, $this->rules()['create']);
 
         $book = $this->bookRepo->create($requestData);
 
@@ -68,6 +57,8 @@ class BookApiController extends ApiController
 
     /**
      * Update the details of a single book.
+     * The cover image of a book can be set by sending a file via an 'image' property within a 'multipart/form-data' request.
+     * If the 'image' property is null then the book cover image will be removed.
      *
      * @throws ValidationException
      */
@@ -76,7 +67,7 @@ class BookApiController extends ApiController
         $book = Book::visible()->findOrFail($id);
         $this->checkOwnablePermission('book-update', $book);
 
-        $requestData = $this->validate($request, $this->rules['update']);
+        $requestData = $this->validate($request, $this->rules()['update']);
         $book = $this->bookRepo->update($book, $requestData);
 
         return response()->json($book);
@@ -97,4 +88,22 @@ class BookApiController extends ApiController
 
         return response('', 204);
     }
+
+    protected function rules(): array
+    {
+        return [
+            'create' => [
+                'name'        => ['required', 'string', 'max:255'],
+                'description' => ['string', 'max:1000'],
+                'tags'        => ['array'],
+                'image'       => array_merge(['nullable'], $this->getImageValidationRules()),
+            ],
+            'update' => [
+                'name'        => ['string', 'min:1', 'max:255'],
+                'description' => ['string', 'max:1000'],
+                'tags'        => ['array'],
+                'image'       => array_merge(['nullable'], $this->getImageValidationRules()),
+            ],
+        ];
+    }
 }
index 028bc3a817ebf726b358ca0f7b8d47f393695bf8..84090befba53249284e267bf77d767d88888cecb 100644 (file)
@@ -26,7 +26,7 @@ class BookExportApiController extends ApiController
         $book = Book::visible()->findOrFail($id);
         $pdfContent = $this->exportFormatter->bookToPdf($book);
 
-        return $this->downloadResponse($pdfContent, $book->slug . '.pdf');
+        return $this->download()->directly($pdfContent, $book->slug . '.pdf');
     }
 
     /**
@@ -39,7 +39,7 @@ class BookExportApiController extends ApiController
         $book = Book::visible()->findOrFail($id);
         $htmlContent = $this->exportFormatter->bookToContainedHtml($book);
 
-        return $this->downloadResponse($htmlContent, $book->slug . '.html');
+        return $this->download()->directly($htmlContent, $book->slug . '.html');
     }
 
     /**
@@ -50,7 +50,7 @@ class BookExportApiController extends ApiController
         $book = Book::visible()->findOrFail($id);
         $textContent = $this->exportFormatter->bookToPlainText($book);
 
-        return $this->downloadResponse($textContent, $book->slug . '.txt');
+        return $this->download()->directly($textContent, $book->slug . '.txt');
     }
 
     /**
@@ -61,6 +61,6 @@ class BookExportApiController extends ApiController
         $book = Book::visible()->findOrFail($id);
         $markdown = $this->exportFormatter->bookToMarkdown($book);
 
-        return $this->downloadResponse($markdown, $book->slug . '.md');
+        return $this->download()->directly($markdown, $book->slug . '.md');
     }
 }
index 63275a72a66191fb9880c425629cbc99126ddf65..620df1638f903c9d1f39e41648866c570f5fb069 100644 (file)
@@ -13,21 +13,6 @@ class BookshelfApiController extends ApiController
 {
     protected BookshelfRepo $bookshelfRepo;
 
-    protected $rules = [
-        'create' => [
-            'name'        => ['required', 'string', 'max:255'],
-            'description' => ['string', 'max:1000'],
-            'books'       => ['array'],
-            'tags'        => ['array'],
-        ],
-        'update' => [
-            'name'        => ['string', 'min:1', 'max:255'],
-            'description' => ['string', 'max:1000'],
-            'books'       => ['array'],
-            'tags'        => ['array'],
-        ],
-    ];
-
     /**
      * BookshelfApiController constructor.
      */
@@ -44,7 +29,7 @@ class BookshelfApiController extends ApiController
         $shelves = Bookshelf::visible();
 
         return $this->apiListingResponse($shelves, [
-            'id', 'name', 'slug', 'description', 'created_at', 'updated_at', 'created_by', 'updated_by', 'owned_by', 'image_id',
+            'id', 'name', 'slug', 'description', 'created_at', 'updated_at', 'created_by', 'updated_by', 'owned_by',
         ]);
     }
 
@@ -52,13 +37,15 @@ class BookshelfApiController extends ApiController
      * Create a new shelf in the system.
      * An array of books IDs can be provided in the request. These
      * will be added to the shelf in the same order as provided.
+     * The cover image of a shelf can be set by sending a file via an 'image' property within a 'multipart/form-data' request.
+     * If the 'image' property is null then the shelf cover image will be removed.
      *
      * @throws ValidationException
      */
     public function create(Request $request)
     {
         $this->checkPermission('bookshelf-create-all');
-        $requestData = $this->validate($request, $this->rules['create']);
+        $requestData = $this->validate($request, $this->rules()['create']);
 
         $bookIds = $request->get('books', []);
         $shelf = $this->bookshelfRepo->create($requestData, $bookIds);
@@ -86,6 +73,8 @@ class BookshelfApiController extends ApiController
      * An array of books IDs can be provided in the request. These
      * will be added to the shelf in the same order as provided and overwrite
      * any existing book assignments.
+     * The cover image of a shelf can be set by sending a file via an 'image' property within a 'multipart/form-data' request.
+     * If the 'image' property is null then the shelf cover image will be removed.
      *
      * @throws ValidationException
      */
@@ -94,7 +83,7 @@ class BookshelfApiController extends ApiController
         $shelf = Bookshelf::visible()->findOrFail($id);
         $this->checkOwnablePermission('bookshelf-update', $shelf);
 
-        $requestData = $this->validate($request, $this->rules['update']);
+        $requestData = $this->validate($request, $this->rules()['update']);
         $bookIds = $request->get('books', null);
 
         $shelf = $this->bookshelfRepo->update($shelf, $requestData, $bookIds);
@@ -117,4 +106,24 @@ class BookshelfApiController extends ApiController
 
         return response('', 204);
     }
+
+    protected function rules(): array
+    {
+        return [
+            'create' => [
+                'name'        => ['required', 'string', 'max:255'],
+                'description' => ['string', 'max:1000'],
+                'books'       => ['array'],
+                'tags'        => ['array'],
+                'image'       => array_merge(['nullable'], $this->getImageValidationRules()),
+            ],
+            'update' => [
+                'name'        => ['string', 'min:1', 'max:255'],
+                'description' => ['string', 'max:1000'],
+                'books'       => ['array'],
+                'tags'        => ['array'],
+                'image'       => array_merge(['nullable'], $this->getImageValidationRules()),
+            ],
+        ];
+    }
 }
index 5715ab2e37c6c9f7e682ad69b407f27153e3934e..faf5d812e207c667170fd8c361f4a976ee3d7f11 100644 (file)
@@ -29,7 +29,7 @@ class ChapterExportApiController extends ApiController
         $chapter = Chapter::visible()->findOrFail($id);
         $pdfContent = $this->exportFormatter->chapterToPdf($chapter);
 
-        return $this->downloadResponse($pdfContent, $chapter->slug . '.pdf');
+        return $this->download()->directly($pdfContent, $chapter->slug . '.pdf');
     }
 
     /**
@@ -42,7 +42,7 @@ class ChapterExportApiController extends ApiController
         $chapter = Chapter::visible()->findOrFail($id);
         $htmlContent = $this->exportFormatter->chapterToContainedHtml($chapter);
 
-        return $this->downloadResponse($htmlContent, $chapter->slug . '.html');
+        return $this->download()->directly($htmlContent, $chapter->slug . '.html');
     }
 
     /**
@@ -53,7 +53,7 @@ class ChapterExportApiController extends ApiController
         $chapter = Chapter::visible()->findOrFail($id);
         $textContent = $this->exportFormatter->chapterToPlainText($chapter);
 
-        return $this->downloadResponse($textContent, $chapter->slug . '.txt');
+        return $this->download()->directly($textContent, $chapter->slug . '.txt');
     }
 
     /**
@@ -64,6 +64,6 @@ class ChapterExportApiController extends ApiController
         $chapter = Chapter::visible()->findOrFail($id);
         $markdown = $this->exportFormatter->chapterToMarkdown($chapter);
 
-        return $this->downloadResponse($markdown, $chapter->slug . '.md');
+        return $this->download()->directly($markdown, $chapter->slug . '.md');
     }
 }
index ce5700c79b9add01a9b2eb8d418f5b3a785344e8..f2edffba3d8b8934175705ada9a4327d2d0724b4 100644 (file)
@@ -26,7 +26,7 @@ class PageExportApiController extends ApiController
         $page = Page::visible()->findOrFail($id);
         $pdfContent = $this->exportFormatter->pageToPdf($page);
 
-        return $this->downloadResponse($pdfContent, $page->slug . '.pdf');
+        return $this->download()->directly($pdfContent, $page->slug . '.pdf');
     }
 
     /**
@@ -39,7 +39,7 @@ class PageExportApiController extends ApiController
         $page = Page::visible()->findOrFail($id);
         $htmlContent = $this->exportFormatter->pageToContainedHtml($page);
 
-        return $this->downloadResponse($htmlContent, $page->slug . '.html');
+        return $this->download()->directly($htmlContent, $page->slug . '.html');
     }
 
     /**
@@ -50,7 +50,7 @@ class PageExportApiController extends ApiController
         $page = Page::visible()->findOrFail($id);
         $textContent = $this->exportFormatter->pageToPlainText($page);
 
-        return $this->downloadResponse($textContent, $page->slug . '.txt');
+        return $this->download()->directly($textContent, $page->slug . '.txt');
     }
 
     /**
@@ -61,6 +61,6 @@ class PageExportApiController extends ApiController
         $page = Page::visible()->findOrFail($id);
         $markdown = $this->exportFormatter->pageToMarkdown($page);
 
-        return $this->downloadResponse($markdown, $page->slug . '.md');
+        return $this->download()->directly($markdown, $page->slug . '.md');
     }
 }
index 0a092b63ae11385e4d67dae6218109b2e2f659fa..03e362f4a78f273573195d120ae806a9d65931ae 100644 (file)
@@ -233,10 +233,10 @@ class AttachmentController extends Controller
         $attachmentStream = $this->attachmentService->streamAttachmentFromStorage($attachment);
 
         if ($request->get('open') === 'true') {
-            return $this->streamedInlineDownloadResponse($attachmentStream, $fileName);
+            return $this->download()->streamedInline($attachmentStream, $fileName);
         }
 
-        return $this->streamedDownloadResponse($attachmentStream, $fileName);
+        return $this->download()->streamedDirectly($attachmentStream, $fileName);
     }
 
     /**
index bc403c6d04495eaf1adf28d4895b9b7f9c6bfeab..681ed96bb7cfd4bfb2afcd520e6f005956c7f59d 100644 (file)
@@ -9,6 +9,7 @@ use BookStack\Entities\Models\Bookshelf;
 use BookStack\Entities\Repos\BookRepo;
 use BookStack\Entities\Tools\BookContents;
 use BookStack\Entities\Tools\Cloner;
+use BookStack\Entities\Tools\HierarchyTransformer;
 use BookStack\Entities\Tools\PermissionsUpdater;
 use BookStack\Entities\Tools\ShelfContext;
 use BookStack\Exceptions\ImageUploadException;
@@ -100,7 +101,6 @@ class BookController extends Controller
         }
 
         $book = $this->bookRepo->create($request->all());
-        $this->bookRepo->updateCoverImage($book, $request->file('image', null));
 
         if ($bookshelf) {
             $bookshelf->appendBook($book);
@@ -158,15 +158,20 @@ class BookController extends Controller
     {
         $book = $this->bookRepo->getBySlug($slug);
         $this->checkOwnablePermission('book-update', $book);
-        $this->validate($request, [
+
+        $validated = $this->validate($request, [
             'name'        => ['required', 'string', 'max:255'],
             'description' => ['string', 'max:1000'],
             'image'       => array_merge(['nullable'], $this->getImageValidationRules()),
         ]);
 
-        $book = $this->bookRepo->update($book, $request->all());
-        $resetCover = $request->has('image_reset');
-        $this->bookRepo->updateCoverImage($book, $request->file('image', null), $resetCover);
+        if ($request->has('image_reset')) {
+            $validated['image'] = null;
+        } elseif (array_key_exists('image', $validated) && is_null($validated['image'])) {
+            unset($validated['image']);
+        }
+
+        $book = $this->bookRepo->update($book, $validated);
 
         return redirect($book->getUrl());
     }
@@ -262,4 +267,20 @@ class BookController extends Controller
 
         return redirect($bookCopy->getUrl());
     }
+
+    /**
+     * Convert the chapter to a book.
+     */
+    public function convertToShelf(HierarchyTransformer $transformer, string $bookSlug)
+    {
+        $book = $this->bookRepo->getBySlug($bookSlug);
+        $this->checkOwnablePermission('book-update', $book);
+        $this->checkOwnablePermission('book-delete', $book);
+        $this->checkPermission('bookshelf-create-all');
+        $this->checkPermission('book-create-all');
+
+        $shelf = $transformer->transformBookToShelf($book);
+
+        return redirect($shelf->getUrl());
+    }
 }
index 7f6dd801752b5e3901cb6188e3dc200888f32984..cc8d48a35511e5c6504c69b33512c9164fd22793 100644 (file)
@@ -31,7 +31,7 @@ class BookExportController extends Controller
         $book = $this->bookRepo->getBySlug($bookSlug);
         $pdfContent = $this->exportFormatter->bookToPdf($book);
 
-        return $this->downloadResponse($pdfContent, $bookSlug . '.pdf');
+        return $this->download()->directly($pdfContent, $bookSlug . '.pdf');
     }
 
     /**
@@ -44,7 +44,7 @@ class BookExportController extends Controller
         $book = $this->bookRepo->getBySlug($bookSlug);
         $htmlContent = $this->exportFormatter->bookToContainedHtml($book);
 
-        return $this->downloadResponse($htmlContent, $bookSlug . '.html');
+        return $this->download()->directly($htmlContent, $bookSlug . '.html');
     }
 
     /**
@@ -55,7 +55,7 @@ class BookExportController extends Controller
         $book = $this->bookRepo->getBySlug($bookSlug);
         $textContent = $this->exportFormatter->bookToPlainText($book);
 
-        return $this->downloadResponse($textContent, $bookSlug . '.txt');
+        return $this->download()->directly($textContent, $bookSlug . '.txt');
     }
 
     /**
@@ -66,6 +66,6 @@ class BookExportController extends Controller
         $book = $this->bookRepo->getBySlug($bookSlug);
         $textContent = $this->exportFormatter->bookToMarkdown($book);
 
-        return $this->downloadResponse($textContent, $bookSlug . '.md');
+        return $this->download()->directly($textContent, $bookSlug . '.md');
     }
 }
index 9a7f78a85a0417fc70b3ea707ea495397577e1eb..a294bf7318c2a35a44d42ff3772b15f900a5123d 100644 (file)
@@ -83,15 +83,15 @@ class BookshelfController extends Controller
     public function store(Request $request)
     {
         $this->checkPermission('bookshelf-create-all');
-        $this->validate($request, [
+        $validated = $this->validate($request, [
             'name'        => ['required', 'string', 'max:255'],
             'description' => ['string', 'max:1000'],
             'image'       => array_merge(['nullable'], $this->getImageValidationRules()),
+            'tags'        => ['array'],
         ]);
 
         $bookIds = explode(',', $request->get('books', ''));
-        $shelf = $this->bookshelfRepo->create($request->all(), $bookIds);
-        $this->bookshelfRepo->updateCoverImage($shelf, $request->file('image', null));
+        $shelf = $this->bookshelfRepo->create($validated, $bookIds);
 
         return redirect($shelf->getUrl());
     }
@@ -160,16 +160,21 @@ class BookshelfController extends Controller
     {
         $shelf = $this->bookshelfRepo->getBySlug($slug);
         $this->checkOwnablePermission('bookshelf-update', $shelf);
-        $this->validate($request, [
+        $validated = $this->validate($request, [
             'name'        => ['required', 'string', 'max:255'],
             'description' => ['string', 'max:1000'],
             'image'       => array_merge(['nullable'], $this->getImageValidationRules()),
+            'tags'        => ['array'],
         ]);
 
+        if ($request->has('image_reset')) {
+            $validated['image'] = null;
+        } elseif (array_key_exists('image', $validated) && is_null($validated['image'])) {
+            unset($validated['image']);
+        }
+
         $bookIds = explode(',', $request->get('books', ''));
-        $shelf = $this->bookshelfRepo->update($shelf, $request->all(), $bookIds);
-        $resetCover = $request->has('image_reset');
-        $this->bookshelfRepo->updateCoverImage($shelf, $request->file('image', null), $resetCover);
+        $shelf = $this->bookshelfRepo->update($shelf, $validated, $bookIds);
 
         return redirect($shelf->getUrl());
     }
index 83b9bb692da6c3173305c538ab79a6a8a836da86..60eb523800fc369edb694db27e0bcdb065825e15 100644 (file)
@@ -7,6 +7,7 @@ use BookStack\Entities\Models\Book;
 use BookStack\Entities\Repos\ChapterRepo;
 use BookStack\Entities\Tools\BookContents;
 use BookStack\Entities\Tools\Cloner;
+use BookStack\Entities\Tools\HierarchyTransformer;
 use BookStack\Entities\Tools\NextPreviousContentLocator;
 use BookStack\Entities\Tools\PermissionsUpdater;
 use BookStack\Exceptions\MoveOperationException;
@@ -272,4 +273,19 @@ class ChapterController extends Controller
 
         return redirect($chapter->getUrl());
     }
+
+    /**
+     * Convert the chapter to a book.
+     */
+    public function convertToBook(HierarchyTransformer $transformer, string $bookSlug, string $chapterSlug)
+    {
+        $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
+        $this->checkOwnablePermission('chapter-update', $chapter);
+        $this->checkOwnablePermission('chapter-delete', $chapter);
+        $this->checkPermission('book-create-all');
+
+        $book = $transformer->transformChapterToBook($chapter);
+
+        return redirect($book->getUrl());
+    }
 }
index 480280c99ef6dc83a169ba1762d5e4fc0edd4d36..fd56d91b3359218b6f1bf0218761132200bb803d 100644 (file)
@@ -33,7 +33,7 @@ class ChapterExportController extends Controller
         $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
         $pdfContent = $this->exportFormatter->chapterToPdf($chapter);
 
-        return $this->downloadResponse($pdfContent, $chapterSlug . '.pdf');
+        return $this->download()->directly($pdfContent, $chapterSlug . '.pdf');
     }
 
     /**
@@ -47,7 +47,7 @@ class ChapterExportController extends Controller
         $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
         $containedHtml = $this->exportFormatter->chapterToContainedHtml($chapter);
 
-        return $this->downloadResponse($containedHtml, $chapterSlug . '.html');
+        return $this->download()->directly($containedHtml, $chapterSlug . '.html');
     }
 
     /**
@@ -60,7 +60,7 @@ class ChapterExportController extends Controller
         $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
         $chapterText = $this->exportFormatter->chapterToPlainText($chapter);
 
-        return $this->downloadResponse($chapterText, $chapterSlug . '.txt');
+        return $this->download()->directly($chapterText, $chapterSlug . '.txt');
     }
 
     /**
@@ -70,10 +70,9 @@ class ChapterExportController extends Controller
      */
     public function markdown(string $bookSlug, string $chapterSlug)
     {
-        // TODO: This should probably export to a zip file.
         $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
         $chapterText = $this->exportFormatter->chapterToMarkdown($chapter);
 
-        return $this->downloadResponse($chapterText, $chapterSlug . '.md');
+        return $this->download()->directly($chapterText, $chapterSlug . '.md');
     }
 }
index c00ac938bdae2b56586d4d97bc74820cd5b7bbd5..f6dc1dbca43cb9b46ce60ef9d48105f16a04c575 100644 (file)
@@ -4,15 +4,13 @@ namespace BookStack\Http\Controllers;
 
 use BookStack\Exceptions\NotifyException;
 use BookStack\Facades\Activity;
+use BookStack\Http\Responses\DownloadResponseFactory;
 use BookStack\Interfaces\Loggable;
 use BookStack\Model;
-use BookStack\Util\WebSafeMimeSniffer;
 use Illuminate\Foundation\Bus\DispatchesJobs;
 use Illuminate\Foundation\Validation\ValidatesRequests;
 use Illuminate\Http\JsonResponse;
-use Illuminate\Http\Response;
 use Illuminate\Routing\Controller as BaseController;
-use Symfony\Component\HttpFoundation\StreamedResponse;
 
 abstract class Controller extends BaseController
 {
@@ -110,72 +108,11 @@ abstract class Controller extends BaseController
     }
 
     /**
-     * Create a response that forces a download in the browser.
+     * Create and return a new download response factory using the current request.
      */
-    protected function downloadResponse(string $content, string $fileName): Response
+    protected function download(): DownloadResponseFactory
     {
-        return response()->make($content, 200, [
-            'Content-Type'           => 'application/octet-stream',
-            'Content-Disposition'    => 'attachment; filename="' . str_replace('"', '', $fileName) . '"',
-            'X-Content-Type-Options' => 'nosniff',
-        ]);
-    }
-
-    /**
-     * Create a response that forces a download, from a given stream of content.
-     */
-    protected function streamedDownloadResponse($stream, string $fileName): StreamedResponse
-    {
-        return response()->stream(function () use ($stream) {
-            // End & flush the output buffer otherwise we still seem to use memory.
-            // Ignore in testing since output buffers are used to gather a response.
-            if (!app()->runningUnitTests()) {
-                ob_end_clean();
-            }
-
-            fpassthru($stream);
-            fclose($stream);
-        }, 200, [
-            'Content-Type'           => 'application/octet-stream',
-            'Content-Disposition'    => 'attachment; filename="' . str_replace('"', '', $fileName) . '"',
-            'X-Content-Type-Options' => 'nosniff',
-        ]);
-    }
-
-    /**
-     * Create a file download response that provides the file with a content-type
-     * correct for the file, in a way so the browser can show the content in browser.
-     */
-    protected function inlineDownloadResponse(string $content, string $fileName): Response
-    {
-        $mime = (new WebSafeMimeSniffer())->sniff($content);
-
-        return response()->make($content, 200, [
-            'Content-Type'           => $mime,
-            'Content-Disposition'    => 'inline; filename="' . str_replace('"', '', $fileName) . '"',
-            'X-Content-Type-Options' => 'nosniff',
-        ]);
-    }
-
-    /**
-     * Create a file download response that provides the file with a content-type
-     * correct for the file, in a way so the browser can show the content in browser,
-     * for a given content stream.
-     */
-    protected function streamedInlineDownloadResponse($stream, string $fileName): StreamedResponse
-    {
-        $sniffContent = fread($stream, 1000);
-        $mime = (new WebSafeMimeSniffer())->sniff($sniffContent);
-
-        return response()->stream(function () use ($sniffContent, $stream) {
-            echo $sniffContent;
-            fpassthru($stream);
-            fclose($stream);
-        }, 200, [
-            'Content-Type'           => $mime,
-            'Content-Disposition'    => 'inline; filename="' . str_replace('"', '', $fileName) . '"',
-            'X-Content-Type-Options' => 'nosniff',
-        ]);
+        return new DownloadResponseFactory(request());
     }
 
     /**
index 0287916de28f40008eeb2d16e948d6274eb77a3a..62101d3390fccca880259f76d824cde7deb2396c 100644 (file)
@@ -36,7 +36,7 @@ class PageExportController extends Controller
         $page->html = (new PageContent($page))->render();
         $pdfContent = $this->exportFormatter->pageToPdf($page);
 
-        return $this->downloadResponse($pdfContent, $pageSlug . '.pdf');
+        return $this->download()->directly($pdfContent, $pageSlug . '.pdf');
     }
 
     /**
@@ -51,7 +51,7 @@ class PageExportController extends Controller
         $page->html = (new PageContent($page))->render();
         $containedHtml = $this->exportFormatter->pageToContainedHtml($page);
 
-        return $this->downloadResponse($containedHtml, $pageSlug . '.html');
+        return $this->download()->directly($containedHtml, $pageSlug . '.html');
     }
 
     /**
@@ -64,7 +64,7 @@ class PageExportController extends Controller
         $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
         $pageText = $this->exportFormatter->pageToPlainText($page);
 
-        return $this->downloadResponse($pageText, $pageSlug . '.txt');
+        return $this->download()->directly($pageText, $pageSlug . '.txt');
     }
 
     /**
@@ -77,6 +77,6 @@ class PageExportController extends Controller
         $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
         $pageText = $this->exportFormatter->pageToMarkdown($page);
 
-        return $this->downloadResponse($pageText, $pageSlug . '.md');
+        return $this->download()->directly($pageText, $pageSlug . '.md');
     }
 }
index 9e9e6c6909ec0613fbfccee497d93a009317abf9..f5e48ca4cc5413ae6c6afcbf5fbb091391e6ce6a 100644 (file)
@@ -83,7 +83,7 @@ class SettingController extends Controller
         $this->logActivity(ActivityType::SETTINGS_UPDATE, $category);
         $this->showSuccessNotification(trans('settings.settings_save_success'));
 
-        return redirect("/settings/${category}");
+        return redirect("/settings/{$category}");
     }
 
     protected function ensureCategoryExists(string $category): void
index 83de55eb3ca914acce4a7ba0fb44005a1ed7334d..16aea8ec9241bc26a61feac436b2d4ce35dd1c04 100644 (file)
@@ -11,7 +11,7 @@ class Localization
     /**
      * Array of right-to-left locales.
      */
-    protected $rtlLocales = ['ar', 'he'];
+    protected $rtlLocales = ['ar', 'fa', 'he'];
 
     /**
      * Map of BookStack locale names to best-estimate system locale names.
@@ -30,6 +30,7 @@ class Localization
         'es_AR'       => 'es_AR',
         'et'          => 'et_EE',
         'eu'          => 'eu_ES',
+        'fa'          => 'fa_IR',
         'fr'          => 'fr_FR',
         'he'          => 'he_IL',
         'hr'          => 'hr_HR',
index 13892603db8768df2f1bcfa012d88bc827e75959..4cbdf34bae6ac266463776d9e825be5128abdc9c 100644 (file)
@@ -35,7 +35,9 @@ class Request extends LaravelRequest
         $appUrl = config('app.url', null);
 
         if ($appUrl) {
-            return '/' . rtrim(implode('/', array_slice(explode('/', $appUrl), 3)), '/');
+            $parsedBaseUrl = rtrim(implode('/', array_slice(explode('/', $appUrl), 3)), '/');
+
+            return empty($parsedBaseUrl) ? '' : ('/' . $parsedBaseUrl);
         }
 
         return parent::getBaseUrl();
diff --git a/app/Http/Responses/DownloadResponseFactory.php b/app/Http/Responses/DownloadResponseFactory.php
new file mode 100644 (file)
index 0000000..ec742fc
--- /dev/null
@@ -0,0 +1,77 @@
+<?php
+
+namespace BookStack\Http\Responses;
+
+use BookStack\Util\WebSafeMimeSniffer;
+use Illuminate\Http\Request;
+use Illuminate\Http\Response;
+use Symfony\Component\HttpFoundation\StreamedResponse;
+
+class DownloadResponseFactory
+{
+    protected Request $request;
+
+    public function __construct(Request $request)
+    {
+        $this->request = $request;
+    }
+
+    /**
+     * Create a response that directly forces a download in the browser.
+     */
+    public function directly(string $content, string $fileName): Response
+    {
+        return response()->make($content, 200, $this->getHeaders($fileName));
+    }
+
+    /**
+     * Create a response that forces a download, from a given stream of content.
+     */
+    public function streamedDirectly($stream, string $fileName): StreamedResponse
+    {
+        return response()->stream(function () use ($stream) {
+
+            // End & flush the output buffer, if we're in one, otherwise we still use memory.
+            // Output buffer may or may not exist depending on PHP `output_buffering` setting.
+            // Ignore in testing since output buffers are used to gather a response.
+            if (!empty(ob_get_status()) && !app()->runningUnitTests()) {
+                ob_end_clean();
+            }
+
+            fpassthru($stream);
+            fclose($stream);
+        }, 200, $this->getHeaders($fileName));
+    }
+
+    /**
+     * Create a file download response that provides the file with a content-type
+     * correct for the file, in a way so the browser can show the content in browser,
+     * for a given content stream.
+     */
+    public function streamedInline($stream, string $fileName): StreamedResponse
+    {
+        $sniffContent = fread($stream, 2000);
+        $mime = (new WebSafeMimeSniffer())->sniff($sniffContent);
+
+        return response()->stream(function () use ($sniffContent, $stream) {
+            echo $sniffContent;
+            fpassthru($stream);
+            fclose($stream);
+        }, 200, $this->getHeaders($fileName, $mime));
+    }
+
+    /**
+     * Get the common headers to provide for a download response.
+     */
+    protected function getHeaders(string $fileName, string $mime = 'application/octet-stream'): array
+    {
+        $disposition = ($mime === 'application/octet-stream') ? 'attachment' : 'inline';
+        $downloadName = str_replace('"', '', $fileName);
+
+        return [
+            'Content-Type'           => $mime,
+            'Content-Disposition'    => "{$disposition}; filename=\"{$downloadName}\"",
+            'X-Content-Type-Options' => 'nosniff',
+        ];
+    }
+}
index 9d1f96ae42f99178f6b3fd10406a4572805b5943..6a92cb5a543c6a24b6ae069679965b09cb5dbfbc 100644 (file)
@@ -63,16 +63,6 @@ class AttachmentService
         return 'uploads/files/' . $path;
     }
 
-    /**
-     * Get an attachment from storage.
-     *
-     * @throws FileNotFoundException
-     */
-    public function getAttachmentFromStorage(Attachment $attachment): string
-    {
-        return $this->getStorageDisk()->get($this->adjustPathForStorageDisk($attachment->path));
-    }
-
     /**
      * Stream an attachment from storage.
      *
index ea58e586bbbeab595f804f8a0e40ce3a5ec37cf6..b182d8ac19b39ef6eec4e09acdbe00d8d86c4854 100644 (file)
@@ -17,6 +17,14 @@ class WebSafeMimeSniffer
         'application/json',
         'application/octet-stream',
         'application/pdf',
+        'audio/aac',
+        'audio/midi',
+        'audio/mpeg',
+        'audio/ogg',
+        'audio/opus',
+        'audio/wav',
+        'audio/webm',
+        'audio/x-m4a',
         'image/apng',
         'image/bmp',
         'image/jpeg',
index 4b0cd13d369f907cab3a8ec788b443bcf60b778c..59f93dfcf146df8b515b39da5c36614a3688e3f0 100644 (file)
         },
         {
             "name": "aws/aws-sdk-php",
-            "version": "3.222.1",
+            "version": "3.225.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/aws/aws-sdk-php.git",
-                "reference": "632621d97180e82d3adc1245a7b272774c30dbf5"
+                "reference": "b795c9c14997dac771f66d1f6cbadb62c742373a"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/632621d97180e82d3adc1245a7b272774c30dbf5",
-                "reference": "632621d97180e82d3adc1245a7b272774c30dbf5",
+                "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/b795c9c14997dac771f66d1f6cbadb62c742373a",
+                "reference": "b795c9c14997dac771f66d1f6cbadb62c742373a",
                 "shasum": ""
             },
             "require": {
             "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.222.1"
+                "source": "https://github.com/aws/aws-sdk-php/tree/3.225.1"
             },
-            "time": "2022-04-28T18:17:24+00:00"
+            "time": "2022-06-09T18:19:43+00:00"
         },
         {
             "name": "bacon/bacon-qr-code",
         },
         {
             "name": "barryvdh/laravel-dompdf",
-            "version": "v1.0.0",
+            "version": "v1.0.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/barryvdh/laravel-dompdf.git",
-                "reference": "e3f429e97087b2ef19b83e5ed313f080f2477685"
+                "reference": "de83130d029289e1b59f28b41c314ce1d157b4a0"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/barryvdh/laravel-dompdf/zipball/e3f429e97087b2ef19b83e5ed313f080f2477685",
-                "reference": "e3f429e97087b2ef19b83e5ed313f080f2477685",
+                "url": "https://api.github.com/repos/barryvdh/laravel-dompdf/zipball/de83130d029289e1b59f28b41c314ce1d157b4a0",
+                "reference": "de83130d029289e1b59f28b41c314ce1d157b4a0",
                 "shasum": ""
             },
             "require": {
-                "dompdf/dompdf": "^1",
+                "dompdf/dompdf": "^1.2.1",
                 "illuminate/support": "^6|^7|^8|^9",
                 "php": "^7.2 || ^8.0"
             },
             ],
             "support": {
                 "issues": "https://github.com/barryvdh/laravel-dompdf/issues",
-                "source": "https://github.com/barryvdh/laravel-dompdf/tree/v1.0.0"
+                "source": "https://github.com/barryvdh/laravel-dompdf/tree/v1.0.2"
             },
             "funding": [
                 {
                     "type": "github"
                 }
             ],
-            "time": "2022-01-29T08:02:59+00:00"
+            "time": "2022-05-19T15:08:38+00:00"
         },
         {
             "name": "barryvdh/laravel-snappy",
         },
         {
             "name": "doctrine/cache",
-            "version": "2.1.1",
+            "version": "2.2.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/doctrine/cache.git",
-                "reference": "331b4d5dbaeab3827976273e9356b3b453c300ce"
+                "reference": "1ca8f21980e770095a31456042471a57bc4c68fb"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/cache/zipball/331b4d5dbaeab3827976273e9356b3b453c300ce",
-                "reference": "331b4d5dbaeab3827976273e9356b3b453c300ce",
+                "url": "https://api.github.com/repos/doctrine/cache/zipball/1ca8f21980e770095a31456042471a57bc4c68fb",
+                "reference": "1ca8f21980e770095a31456042471a57bc4c68fb",
                 "shasum": ""
             },
             "require": {
                 "doctrine/common": ">2.2,<2.4"
             },
             "require-dev": {
-                "alcaeus/mongo-php-adapter": "^1.1",
                 "cache/integration-tests": "dev-master",
-                "doctrine/coding-standard": "^8.0",
-                "mongodb/mongodb": "^1.1",
-                "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
-                "predis/predis": "~1.0",
+                "doctrine/coding-standard": "^9",
+                "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
                 "psr/cache": "^1.0 || ^2.0 || ^3.0",
-                "symfony/cache": "^4.4 || ^5.2 || ^6.0@dev",
-                "symfony/var-exporter": "^4.4 || ^5.2 || ^6.0@dev"
-            },
-            "suggest": {
-                "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver"
+                "symfony/cache": "^4.4 || ^5.4 || ^6",
+                "symfony/var-exporter": "^4.4 || ^5.4 || ^6"
             },
             "type": "library",
             "autoload": {
             ],
             "support": {
                 "issues": "https://github.com/doctrine/cache/issues",
-                "source": "https://github.com/doctrine/cache/tree/2.1.1"
+                "source": "https://github.com/doctrine/cache/tree/2.2.0"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2021-07-17T14:49:29+00:00"
+            "time": "2022-05-20T20:07:39+00:00"
         },
         {
             "name": "doctrine/dbal",
-            "version": "3.3.5",
+            "version": "3.3.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/doctrine/dbal.git",
-                "reference": "719663b15983278227669c8595151586a2ff3327"
+                "reference": "9e7f76dd1cde81c62574fdffa5a9c655c847ad21"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/dbal/zipball/719663b15983278227669c8595151586a2ff3327",
-                "reference": "719663b15983278227669c8595151586a2ff3327",
+                "url": "https://api.github.com/repos/doctrine/dbal/zipball/9e7f76dd1cde81c62574fdffa5a9c655c847ad21",
+                "reference": "9e7f76dd1cde81c62574fdffa5a9c655c847ad21",
                 "shasum": ""
             },
             "require": {
                 "composer-runtime-api": "^2",
                 "doctrine/cache": "^1.11|^2.0",
-                "doctrine/deprecations": "^0.5.3",
+                "doctrine/deprecations": "^0.5.3|^1",
                 "doctrine/event-manager": "^1.0",
                 "php": "^7.3 || ^8.0",
                 "psr/cache": "^1|^2|^3",
             },
             "require-dev": {
                 "doctrine/coding-standard": "9.0.0",
-                "jetbrains/phpstorm-stubs": "2021.1",
-                "phpstan/phpstan": "1.5.3",
-                "phpstan/phpstan-strict-rules": "^1.1",
-                "phpunit/phpunit": "9.5.16",
+                "jetbrains/phpstorm-stubs": "2022.1",
+                "phpstan/phpstan": "1.6.3",
+                "phpstan/phpstan-strict-rules": "^1.2",
+                "phpunit/phpunit": "9.5.20",
                 "psalm/plugin-phpunit": "0.16.1",
                 "squizlabs/php_codesniffer": "3.6.2",
                 "symfony/cache": "^5.2|^6.0",
                 "symfony/console": "^2.7|^3.0|^4.0|^5.0|^6.0",
-                "vimeo/psalm": "4.22.0"
+                "vimeo/psalm": "4.23.0"
             },
             "suggest": {
                 "symfony/console": "For helpful console commands such as SQL execution and import of files."
             ],
             "support": {
                 "issues": "https://github.com/doctrine/dbal/issues",
-                "source": "https://github.com/doctrine/dbal/tree/3.3.5"
+                "source": "https://github.com/doctrine/dbal/tree/3.3.6"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-04-05T09:50:18+00:00"
+            "time": "2022-05-02T17:21:01+00:00"
         },
         {
             "name": "doctrine/deprecations",
-            "version": "v0.5.3",
+            "version": "v1.0.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/doctrine/deprecations.git",
-                "reference": "9504165960a1f83cc1480e2be1dd0a0478561314"
+                "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/deprecations/zipball/9504165960a1f83cc1480e2be1dd0a0478561314",
-                "reference": "9504165960a1f83cc1480e2be1dd0a0478561314",
+                "url": "https://api.github.com/repos/doctrine/deprecations/zipball/0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de",
+                "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de",
                 "shasum": ""
             },
             "require": {
                 "php": "^7.1|^8.0"
             },
             "require-dev": {
-                "doctrine/coding-standard": "^6.0|^7.0|^8.0",
-                "phpunit/phpunit": "^7.0|^8.0|^9.0",
-                "psr/log": "^1.0"
+                "doctrine/coding-standard": "^9",
+                "phpunit/phpunit": "^7.5|^8.5|^9.5",
+                "psr/log": "^1|^2|^3"
             },
             "suggest": {
                 "psr/log": "Allows logging deprecations via PSR-3 logger implementation"
             "homepage": "https://www.doctrine-project.org/",
             "support": {
                 "issues": "https://github.com/doctrine/deprecations/issues",
-                "source": "https://github.com/doctrine/deprecations/tree/v0.5.3"
+                "source": "https://github.com/doctrine/deprecations/tree/v1.0.0"
             },
-            "time": "2021-03-21T12:59:47+00:00"
+            "time": "2022-05-02T15:47:09+00:00"
         },
         {
             "name": "doctrine/event-manager",
         },
         {
             "name": "guzzlehttp/guzzle",
-            "version": "7.4.2",
+            "version": "7.4.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/guzzle/guzzle.git",
-                "reference": "ac1ec1cd9b5624694c3a40be801d94137afb12b4"
+                "reference": "e3ff079b22820c2029d4c2a87796b6a0b8716ad8"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/ac1ec1cd9b5624694c3a40be801d94137afb12b4",
-                "reference": "ac1ec1cd9b5624694c3a40be801d94137afb12b4",
+                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/e3ff079b22820c2029d4c2a87796b6a0b8716ad8",
+                "reference": "e3ff079b22820c2029d4c2a87796b6a0b8716ad8",
                 "shasum": ""
             },
             "require": {
             ],
             "support": {
                 "issues": "https://github.com/guzzle/guzzle/issues",
-                "source": "https://github.com/guzzle/guzzle/tree/7.4.2"
+                "source": "https://github.com/guzzle/guzzle/tree/7.4.4"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-03-20T14:16:28+00:00"
+            "time": "2022-06-09T21:39:15+00:00"
         },
         {
             "name": "guzzlehttp/promises",
         },
         {
             "name": "guzzlehttp/psr7",
-            "version": "2.2.1",
+            "version": "2.3.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/guzzle/psr7.git",
-                "reference": "c94a94f120803a18554c1805ef2e539f8285f9a2"
+                "reference": "83260bb50b8fc753c72d14dc1621a2dac31877ee"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/guzzle/psr7/zipball/c94a94f120803a18554c1805ef2e539f8285f9a2",
-                "reference": "c94a94f120803a18554c1805ef2e539f8285f9a2",
+                "url": "https://api.github.com/repos/guzzle/psr7/zipball/83260bb50b8fc753c72d14dc1621a2dac31877ee",
+                "reference": "83260bb50b8fc753c72d14dc1621a2dac31877ee",
                 "shasum": ""
             },
             "require": {
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "2.2-dev"
+                    "dev-master": "2.3-dev"
                 }
             },
             "autoload": {
             ],
             "support": {
                 "issues": "https://github.com/guzzle/psr7/issues",
-                "source": "https://github.com/guzzle/psr7/tree/2.2.1"
+                "source": "https://github.com/guzzle/psr7/tree/2.3.0"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-03-20T21:55:58+00:00"
+            "time": "2022-06-09T08:26:02+00:00"
         },
         {
             "name": "intervention/image",
-            "version": "2.7.1",
+            "version": "2.7.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/Intervention/image.git",
-                "reference": "744ebba495319501b873a4e48787759c72e3fb8c"
+                "reference": "04be355f8d6734c826045d02a1079ad658322dad"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/Intervention/image/zipball/744ebba495319501b873a4e48787759c72e3fb8c",
-                "reference": "744ebba495319501b873a4e48787759c72e3fb8c",
+                "url": "https://api.github.com/repos/Intervention/image/zipball/04be355f8d6734c826045d02a1079ad658322dad",
+                "reference": "04be355f8d6734c826045d02a1079ad658322dad",
                 "shasum": ""
             },
             "require": {
             "authors": [
                 {
                     "name": "Oliver Vogel",
-                    "email": "oliver@olivervogel.com",
-                    "homepage": "http://olivervogel.com/"
+                    "email": "oliver@intervention.io",
+                    "homepage": "https://intervention.io/"
                 }
             ],
             "description": "Image handling and manipulation library with support for Laravel integration",
             ],
             "support": {
                 "issues": "https://github.com/Intervention/image/issues",
-                "source": "https://github.com/Intervention/image/tree/2.7.1"
+                "source": "https://github.com/Intervention/image/tree/2.7.2"
             },
             "funding": [
                 {
-                    "url": "https://www.paypal.me/interventionphp",
+                    "url": "https://paypal.me/interventionio",
                     "type": "custom"
                 },
                 {
                     "type": "github"
                 }
             ],
-            "time": "2021-12-16T16:49:26+00:00"
+            "time": "2022-05-21T17:30:32+00:00"
         },
         {
             "name": "knplabs/knp-snappy",
         },
         {
             "name": "laravel/framework",
-            "version": "v8.83.10",
+            "version": "v8.83.16",
             "source": {
                 "type": "git",
                 "url": "https://github.com/laravel/framework.git",
-                "reference": "148ae59b7da6c3db6736374d357c5aaef9506fb7"
+                "reference": "6be5abd144faf517879af7298e9d79f06f250f75"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/laravel/framework/zipball/148ae59b7da6c3db6736374d357c5aaef9506fb7",
-                "reference": "148ae59b7da6c3db6736374d357c5aaef9506fb7",
+                "url": "https://api.github.com/repos/laravel/framework/zipball/6be5abd144faf517879af7298e9d79f06f250f75",
+                "reference": "6be5abd144faf517879af7298e9d79f06f250f75",
                 "shasum": ""
             },
             "require": {
                 "issues": "https://github.com/laravel/framework/issues",
                 "source": "https://github.com/laravel/framework"
             },
-            "time": "2022-04-27T14:07:37+00:00"
+            "time": "2022-06-07T15:09:06+00:00"
         },
         {
             "name": "laravel/serializable-closure",
-            "version": "v1.1.1",
+            "version": "v1.2.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/laravel/serializable-closure.git",
-                "reference": "9e4b005daa20b0c161f3845040046dc9ddc1d74e"
+                "reference": "09f0e9fb61829f628205b7c94906c28740ff9540"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/9e4b005daa20b0c161f3845040046dc9ddc1d74e",
-                "reference": "9e4b005daa20b0c161f3845040046dc9ddc1d74e",
+                "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/09f0e9fb61829f628205b7c94906c28740ff9540",
+                "reference": "09f0e9fb61829f628205b7c94906c28740ff9540",
                 "shasum": ""
             },
             "require": {
                 "issues": "https://github.com/laravel/serializable-closure/issues",
                 "source": "https://github.com/laravel/serializable-closure"
             },
-            "time": "2022-02-11T19:23:53+00:00"
+            "time": "2022-05-16T17:09:47+00:00"
         },
         {
             "name": "laravel/socialite",
         },
         {
             "name": "laravel/ui",
-            "version": "v3.4.5",
+            "version": "v3.4.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/laravel/ui.git",
-                "reference": "f11d295de1508c5bb56206a620b00b6616de414c"
+                "reference": "65ec5c03f7fee2c8ecae785795b829a15be48c2c"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/laravel/ui/zipball/f11d295de1508c5bb56206a620b00b6616de414c",
-                "reference": "f11d295de1508c5bb56206a620b00b6616de414c",
+                "url": "https://api.github.com/repos/laravel/ui/zipball/65ec5c03f7fee2c8ecae785795b829a15be48c2c",
+                "reference": "65ec5c03f7fee2c8ecae785795b829a15be48c2c",
                 "shasum": ""
             },
             "require": {
                 "ui"
             ],
             "support": {
-                "source": "https://github.com/laravel/ui/tree/v3.4.5"
+                "source": "https://github.com/laravel/ui/tree/v3.4.6"
             },
-            "time": "2022-02-21T14:59:16+00:00"
+            "time": "2022-05-20T13:38:08+00:00"
         },
         {
             "name": "league/commonmark",
         },
         {
             "name": "monolog/monolog",
-            "version": "2.5.0",
+            "version": "2.7.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/Seldaek/monolog.git",
-                "reference": "4192345e260f1d51b365536199744b987e160edc"
+                "reference": "5579edf28aee1190a798bfa5be8bc16c563bd524"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/Seldaek/monolog/zipball/4192345e260f1d51b365536199744b987e160edc",
-                "reference": "4192345e260f1d51b365536199744b987e160edc",
+                "url": "https://api.github.com/repos/Seldaek/monolog/zipball/5579edf28aee1190a798bfa5be8bc16c563bd524",
+                "reference": "5579edf28aee1190a798bfa5be8bc16c563bd524",
                 "shasum": ""
             },
             "require": {
             "require-dev": {
                 "aws/aws-sdk-php": "^2.4.9 || ^3.0",
                 "doctrine/couchdb": "~1.0@dev",
-                "elasticsearch/elasticsearch": "^7",
+                "elasticsearch/elasticsearch": "^7 || ^8",
+                "ext-json": "*",
                 "graylog2/gelf-php": "^1.4.2",
+                "guzzlehttp/guzzle": "^7.4",
+                "guzzlehttp/psr7": "^2.2",
                 "mongodb/mongodb": "^1.8",
                 "php-amqplib/php-amqplib": "~2.4 || ^3",
                 "php-console/php-console": "^3.1.3",
-                "phpspec/prophecy": "^1.6.1",
+                "phpspec/prophecy": "^1.15",
                 "phpstan/phpstan": "^0.12.91",
-                "phpunit/phpunit": "^8.5",
+                "phpunit/phpunit": "^8.5.14",
                 "predis/predis": "^1.1",
                 "rollbar/rollbar": "^1.3 || ^2 || ^3",
-                "ruflin/elastica": ">=0.90@dev",
-                "swiftmailer/swiftmailer": "^5.3|^6.0"
+                "ruflin/elastica": "^7",
+                "swiftmailer/swiftmailer": "^5.3|^6.0",
+                "symfony/mailer": "^5.4 || ^6",
+                "symfony/mime": "^5.4 || ^6"
             },
             "suggest": {
                 "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
             ],
             "support": {
                 "issues": "https://github.com/Seldaek/monolog/issues",
-                "source": "https://github.com/Seldaek/monolog/tree/2.5.0"
+                "source": "https://github.com/Seldaek/monolog/tree/2.7.0"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-04-08T15:43:54+00:00"
+            "time": "2022-06-09T08:59:12+00:00"
         },
         {
             "name": "mtdowling/jmespath.php",
         },
         {
             "name": "nesbot/carbon",
-            "version": "2.57.0",
+            "version": "2.58.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/briannesbitt/Carbon.git",
-                "reference": "4a54375c21eea4811dbd1149fe6b246517554e78"
+                "reference": "97a34af22bde8d0ac20ab34b29d7bfe360902055"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/4a54375c21eea4811dbd1149fe6b246517554e78",
-                "reference": "4a54375c21eea4811dbd1149fe6b246517554e78",
+                "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/97a34af22bde8d0ac20ab34b29d7bfe360902055",
+                "reference": "97a34af22bde8d0ac20ab34b29d7bfe360902055",
                 "shasum": ""
             },
             "require": {
                 "phpmd/phpmd": "^2.9",
                 "phpstan/extension-installer": "^1.0",
                 "phpstan/phpstan": "^0.12.54 || ^1.0",
-                "phpunit/phpunit": "^7.5.20 || ^8.5.14",
+                "phpunit/php-file-iterator": "^2.0.5",
+                "phpunit/phpunit": "^7.5.20 || ^8.5.23",
                 "squizlabs/php_codesniffer": "^3.4"
             },
             "bin": [
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-02-13T18:13:33+00:00"
+            "time": "2022-04-25T19:31:17+00:00"
         },
         {
             "name": "nikic/php-parser",
-            "version": "v4.13.2",
+            "version": "v4.14.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/nikic/PHP-Parser.git",
-                "reference": "210577fe3cf7badcc5814d99455df46564f3c077"
+                "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/210577fe3cf7badcc5814d99455df46564f3c077",
-                "reference": "210577fe3cf7badcc5814d99455df46564f3c077",
+                "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/34bea19b6e03d8153165d8f30bba4c3be86184c1",
+                "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1",
                 "shasum": ""
             },
             "require": {
             ],
             "support": {
                 "issues": "https://github.com/nikic/PHP-Parser/issues",
-                "source": "https://github.com/nikic/PHP-Parser/tree/v4.13.2"
+                "source": "https://github.com/nikic/PHP-Parser/tree/v4.14.0"
             },
-            "time": "2021-11-30T19:35:32+00:00"
+            "time": "2022-05-31T20:59:12+00:00"
         },
         {
             "name": "onelogin/php-saml",
         },
         {
             "name": "paragonie/constant_time_encoding",
-            "version": "v2.5.0",
+            "version": "v2.6.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/paragonie/constant_time_encoding.git",
-                "reference": "9229e15f2e6ba772f0c55dd6986c563b937170a8"
+                "reference": "3f3bf06406244a94aeffd5818ba05b41a1754ae5"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/9229e15f2e6ba772f0c55dd6986c563b937170a8",
-                "reference": "9229e15f2e6ba772f0c55dd6986c563b937170a8",
+                "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/3f3bf06406244a94aeffd5818ba05b41a1754ae5",
+                "reference": "3f3bf06406244a94aeffd5818ba05b41a1754ae5",
                 "shasum": ""
             },
             "require": {
                 "issues": "https://github.com/paragonie/constant_time_encoding/issues",
                 "source": "https://github.com/paragonie/constant_time_encoding"
             },
-            "time": "2022-01-17T05:32:27+00:00"
+            "time": "2022-06-10T07:38:28+00:00"
         },
         {
             "name": "paragonie/random_compat",
         },
         {
             "name": "psy/psysh",
-            "version": "v0.11.2",
+            "version": "v0.11.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/bobthecow/psysh.git",
-                "reference": "7f7da640d68b9c9fec819caae7c744a213df6514"
+                "reference": "c23686f9c48ca202710dbb967df8385a952a2daf"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/bobthecow/psysh/zipball/7f7da640d68b9c9fec819caae7c744a213df6514",
-                "reference": "7f7da640d68b9c9fec819caae7c744a213df6514",
+                "url": "https://api.github.com/repos/bobthecow/psysh/zipball/c23686f9c48ca202710dbb967df8385a952a2daf",
+                "reference": "c23686f9c48ca202710dbb967df8385a952a2daf",
                 "shasum": ""
             },
             "require": {
                 "symfony/console": "4.4.37 || 5.3.14 || 5.3.15 || 5.4.3 || 5.4.4 || 6.0.3 || 6.0.4"
             },
             "require-dev": {
-                "bamarni/composer-bin-plugin": "^1.2",
-                "hoa/console": "3.17.05.02"
+                "bamarni/composer-bin-plugin": "^1.2"
             },
             "suggest": {
                 "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)",
                 "ext-pdo-sqlite": "The doc command requires SQLite to work.",
                 "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well.",
-                "ext-readline": "Enables support for arrow-key history navigation, and showing and manipulating command history.",
-                "hoa/console": "A pure PHP readline implementation. You'll want this if your PHP install doesn't already support readline or libedit."
+                "ext-readline": "Enables support for arrow-key history navigation, and showing and manipulating command history."
             },
             "bin": [
                 "bin/psysh"
             ],
             "support": {
                 "issues": "https://github.com/bobthecow/psysh/issues",
-                "source": "https://github.com/bobthecow/psysh/tree/v0.11.2"
+                "source": "https://github.com/bobthecow/psysh/tree/v0.11.5"
             },
-            "time": "2022-02-28T15:28:54+00:00"
+            "time": "2022-05-27T18:03:49+00:00"
         },
         {
             "name": "ralouphie/getallheaders",
         },
         {
             "name": "symfony/console",
-            "version": "v5.4.8",
+            "version": "v5.4.9",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/console.git",
-                "reference": "ffe3aed36c4d60da2cf1b0a1cee6b8f2e5fa881b"
+                "reference": "829d5d1bf60b2efeb0887b7436873becc71a45eb"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/console/zipball/ffe3aed36c4d60da2cf1b0a1cee6b8f2e5fa881b",
-                "reference": "ffe3aed36c4d60da2cf1b0a1cee6b8f2e5fa881b",
+                "url": "https://api.github.com/repos/symfony/console/zipball/829d5d1bf60b2efeb0887b7436873becc71a45eb",
+                "reference": "829d5d1bf60b2efeb0887b7436873becc71a45eb",
                 "shasum": ""
             },
             "require": {
                 "terminal"
             ],
             "support": {
-                "source": "https://github.com/symfony/console/tree/v5.4.8"
+                "source": "https://github.com/symfony/console/tree/v5.4.9"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-04-12T16:02:29+00:00"
+            "time": "2022-05-18T06:17:34+00:00"
         },
         {
             "name": "symfony/css-selector",
         },
         {
             "name": "symfony/error-handler",
-            "version": "v5.4.8",
+            "version": "v5.4.9",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/error-handler.git",
-                "reference": "c1fcde614dfe99d62a83b796a53b8bad358b266a"
+                "reference": "c116cda1f51c678782768dce89a45f13c949455d"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/error-handler/zipball/c1fcde614dfe99d62a83b796a53b8bad358b266a",
-                "reference": "c1fcde614dfe99d62a83b796a53b8bad358b266a",
+                "url": "https://api.github.com/repos/symfony/error-handler/zipball/c116cda1f51c678782768dce89a45f13c949455d",
+                "reference": "c116cda1f51c678782768dce89a45f13c949455d",
                 "shasum": ""
             },
             "require": {
             "description": "Provides tools to manage errors and ease debugging PHP code",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/error-handler/tree/v5.4.8"
+                "source": "https://github.com/symfony/error-handler/tree/v5.4.9"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-04-12T15:48:08+00:00"
+            "time": "2022-05-21T13:57:48+00:00"
         },
         {
             "name": "symfony/event-dispatcher",
-            "version": "v5.4.3",
+            "version": "v5.4.9",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/event-dispatcher.git",
-                "reference": "dec8a9f58d20df252b9cd89f1c6c1530f747685d"
+                "reference": "8e6ce1cc0279e3ff3c8ff0f43813bc88d21ca1bc"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/dec8a9f58d20df252b9cd89f1c6c1530f747685d",
-                "reference": "dec8a9f58d20df252b9cd89f1c6c1530f747685d",
+                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/8e6ce1cc0279e3ff3c8ff0f43813bc88d21ca1bc",
+                "reference": "8e6ce1cc0279e3ff3c8ff0f43813bc88d21ca1bc",
                 "shasum": ""
             },
             "require": {
             "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.3"
+                "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.9"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-01-02T09:53:40+00:00"
+            "time": "2022-05-05T16:45:39+00:00"
         },
         {
             "name": "symfony/event-dispatcher-contracts",
         },
         {
             "name": "symfony/http-foundation",
-            "version": "v5.4.8",
+            "version": "v5.4.9",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/http-foundation.git",
-                "reference": "ff2818d1c3d49860bcae1f2cbb5eb00fcd3bf9e2"
+                "reference": "6b0d0e4aca38d57605dcd11e2416994b38774522"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/http-foundation/zipball/ff2818d1c3d49860bcae1f2cbb5eb00fcd3bf9e2",
-                "reference": "ff2818d1c3d49860bcae1f2cbb5eb00fcd3bf9e2",
+                "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6b0d0e4aca38d57605dcd11e2416994b38774522",
+                "reference": "6b0d0e4aca38d57605dcd11e2416994b38774522",
                 "shasum": ""
             },
             "require": {
             "description": "Defines an object-oriented layer for the HTTP specification",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/http-foundation/tree/v5.4.8"
+                "source": "https://github.com/symfony/http-foundation/tree/v5.4.9"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-04-22T08:14:12+00:00"
+            "time": "2022-05-17T15:07:29+00:00"
         },
         {
             "name": "symfony/http-kernel",
-            "version": "v5.4.8",
+            "version": "v5.4.9",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/http-kernel.git",
-                "reference": "cf7e61106abfc19b305ca0aedc41724ced89a02a"
+                "reference": "34b121ad3dc761f35fe1346d2f15618f8cbf77f8"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/http-kernel/zipball/cf7e61106abfc19b305ca0aedc41724ced89a02a",
-                "reference": "cf7e61106abfc19b305ca0aedc41724ced89a02a",
+                "url": "https://api.github.com/repos/symfony/http-kernel/zipball/34b121ad3dc761f35fe1346d2f15618f8cbf77f8",
+                "reference": "34b121ad3dc761f35fe1346d2f15618f8cbf77f8",
                 "shasum": ""
             },
             "require": {
             "description": "Provides a structured process for converting a Request into a Response",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/http-kernel/tree/v5.4.8"
+                "source": "https://github.com/symfony/http-kernel/tree/v5.4.9"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-04-27T17:22:21+00:00"
+            "time": "2022-05-27T07:09:08+00:00"
         },
         {
             "name": "symfony/mime",
-            "version": "v5.4.8",
+            "version": "v5.4.9",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/mime.git",
-                "reference": "af49bc163ec3272f677bde3bc44c0d766c1fd662"
+                "reference": "2b3802a24e48d0cfccf885173d2aac91e73df92e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/mime/zipball/af49bc163ec3272f677bde3bc44c0d766c1fd662",
-                "reference": "af49bc163ec3272f677bde3bc44c0d766c1fd662",
+                "url": "https://api.github.com/repos/symfony/mime/zipball/2b3802a24e48d0cfccf885173d2aac91e73df92e",
+                "reference": "2b3802a24e48d0cfccf885173d2aac91e73df92e",
                 "shasum": ""
             },
             "require": {
                 "mime-type"
             ],
             "support": {
-                "source": "https://github.com/symfony/mime/tree/v5.4.8"
+                "source": "https://github.com/symfony/mime/tree/v5.4.9"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-04-12T15:48:08+00:00"
+            "time": "2022-05-21T10:24:18+00:00"
         },
         {
             "name": "symfony/polyfill-ctype",
-            "version": "v1.25.0",
+            "version": "v1.26.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-ctype.git",
-                "reference": "30885182c981ab175d4d034db0f6f469898070ab"
+                "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab",
-                "reference": "30885182c981ab175d4d034db0f6f469898070ab",
+                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4",
+                "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4",
                 "shasum": ""
             },
             "require": {
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-main": "1.23-dev"
+                    "dev-main": "1.26-dev"
                 },
                 "thanks": {
                     "name": "symfony/polyfill",
                 "portable"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0"
+                "source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2021-10-20T20:35:02+00:00"
+            "time": "2022-05-24T11:49:31+00:00"
         },
         {
             "name": "symfony/polyfill-iconv",
-            "version": "v1.25.0",
+            "version": "v1.26.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-iconv.git",
-                "reference": "f1aed619e28cb077fc83fac8c4c0383578356e40"
+                "reference": "143f1881e655bebca1312722af8068de235ae5dc"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/f1aed619e28cb077fc83fac8c4c0383578356e40",
-                "reference": "f1aed619e28cb077fc83fac8c4c0383578356e40",
+                "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/143f1881e655bebca1312722af8068de235ae5dc",
+                "reference": "143f1881e655bebca1312722af8068de235ae5dc",
                 "shasum": ""
             },
             "require": {
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-main": "1.23-dev"
+                    "dev-main": "1.26-dev"
                 },
                 "thanks": {
                     "name": "symfony/polyfill",
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-iconv/tree/v1.25.0"
+                "source": "https://github.com/symfony/polyfill-iconv/tree/v1.26.0"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-01-04T09:04:05+00:00"
+            "time": "2022-05-24T11:49:31+00:00"
         },
         {
             "name": "symfony/polyfill-intl-grapheme",
-            "version": "v1.25.0",
+            "version": "v1.26.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-intl-grapheme.git",
-                "reference": "81b86b50cf841a64252b439e738e97f4a34e2783"
+                "reference": "433d05519ce6990bf3530fba6957499d327395c2"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/81b86b50cf841a64252b439e738e97f4a34e2783",
-                "reference": "81b86b50cf841a64252b439e738e97f4a34e2783",
+                "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/433d05519ce6990bf3530fba6957499d327395c2",
+                "reference": "433d05519ce6990bf3530fba6957499d327395c2",
                 "shasum": ""
             },
             "require": {
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-main": "1.23-dev"
+                    "dev-main": "1.26-dev"
                 },
                 "thanks": {
                     "name": "symfony/polyfill",
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.25.0"
+                "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.26.0"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2021-11-23T21:10:46+00:00"
+            "time": "2022-05-24T11:49:31+00:00"
         },
         {
             "name": "symfony/polyfill-intl-idn",
-            "version": "v1.25.0",
+            "version": "v1.26.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-intl-idn.git",
-                "reference": "749045c69efb97c70d25d7463abba812e91f3a44"
+                "reference": "59a8d271f00dd0e4c2e518104cc7963f655a1aa8"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/749045c69efb97c70d25d7463abba812e91f3a44",
-                "reference": "749045c69efb97c70d25d7463abba812e91f3a44",
+                "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/59a8d271f00dd0e4c2e518104cc7963f655a1aa8",
+                "reference": "59a8d271f00dd0e4c2e518104cc7963f655a1aa8",
                 "shasum": ""
             },
             "require": {
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-main": "1.23-dev"
+                    "dev-main": "1.26-dev"
                 },
                 "thanks": {
                     "name": "symfony/polyfill",
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.25.0"
+                "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.26.0"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2021-09-14T14:02:44+00:00"
+            "time": "2022-05-24T11:49:31+00:00"
         },
         {
             "name": "symfony/polyfill-intl-normalizer",
-            "version": "v1.25.0",
+            "version": "v1.26.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-intl-normalizer.git",
-                "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8"
+                "reference": "219aa369ceff116e673852dce47c3a41794c14bd"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8590a5f561694770bdcd3f9b5c69dde6945028e8",
-                "reference": "8590a5f561694770bdcd3f9b5c69dde6945028e8",
+                "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd",
+                "reference": "219aa369ceff116e673852dce47c3a41794c14bd",
                 "shasum": ""
             },
             "require": {
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-main": "1.23-dev"
+                    "dev-main": "1.26-dev"
                 },
                 "thanks": {
                     "name": "symfony/polyfill",
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.25.0"
+                "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.26.0"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2021-02-19T12:13:01+00:00"
+            "time": "2022-05-24T11:49:31+00:00"
         },
         {
             "name": "symfony/polyfill-mbstring",
-            "version": "v1.25.0",
+            "version": "v1.26.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-mbstring.git",
-                "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825"
+                "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825",
-                "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e",
+                "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e",
                 "shasum": ""
             },
             "require": {
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-main": "1.23-dev"
+                    "dev-main": "1.26-dev"
                 },
                 "thanks": {
                     "name": "symfony/polyfill",
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0"
+                "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2021-11-30T18:21:41+00:00"
+            "time": "2022-05-24T11:49:31+00:00"
         },
         {
             "name": "symfony/polyfill-php72",
-            "version": "v1.25.0",
+            "version": "v1.26.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-php72.git",
-                "reference": "9a142215a36a3888e30d0a9eeea9766764e96976"
+                "reference": "bf44a9fd41feaac72b074de600314a93e2ae78e2"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/9a142215a36a3888e30d0a9eeea9766764e96976",
-                "reference": "9a142215a36a3888e30d0a9eeea9766764e96976",
+                "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/bf44a9fd41feaac72b074de600314a93e2ae78e2",
+                "reference": "bf44a9fd41feaac72b074de600314a93e2ae78e2",
                 "shasum": ""
             },
             "require": {
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-main": "1.23-dev"
+                    "dev-main": "1.26-dev"
                 },
                 "thanks": {
                     "name": "symfony/polyfill",
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-php72/tree/v1.25.0"
+                "source": "https://github.com/symfony/polyfill-php72/tree/v1.26.0"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2021-05-27T09:17:38+00:00"
+            "time": "2022-05-24T11:49:31+00:00"
         },
         {
             "name": "symfony/polyfill-php73",
-            "version": "v1.25.0",
+            "version": "v1.26.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-php73.git",
-                "reference": "cc5db0e22b3cb4111010e48785a97f670b350ca5"
+                "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/cc5db0e22b3cb4111010e48785a97f670b350ca5",
-                "reference": "cc5db0e22b3cb4111010e48785a97f670b350ca5",
+                "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/e440d35fa0286f77fb45b79a03fedbeda9307e85",
+                "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85",
                 "shasum": ""
             },
             "require": {
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-main": "1.23-dev"
+                    "dev-main": "1.26-dev"
                 },
                 "thanks": {
                     "name": "symfony/polyfill",
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-php73/tree/v1.25.0"
+                "source": "https://github.com/symfony/polyfill-php73/tree/v1.26.0"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2021-06-05T21:20:04+00:00"
+            "time": "2022-05-24T11:49:31+00:00"
         },
         {
             "name": "symfony/polyfill-php80",
-            "version": "v1.25.0",
+            "version": "v1.26.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-php80.git",
-                "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c"
+                "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4407588e0d3f1f52efb65fbe92babe41f37fe50c",
-                "reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c",
+                "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace",
+                "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace",
                 "shasum": ""
             },
             "require": {
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-main": "1.23-dev"
+                    "dev-main": "1.26-dev"
                 },
                 "thanks": {
                     "name": "symfony/polyfill",
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-php80/tree/v1.25.0"
+                "source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-03-04T08:16:47+00:00"
+            "time": "2022-05-10T07:21:04+00:00"
         },
         {
             "name": "symfony/polyfill-php81",
-            "version": "v1.25.0",
+            "version": "v1.26.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-php81.git",
-                "reference": "5de4ba2d41b15f9bd0e19b2ab9674135813ec98f"
+                "reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/5de4ba2d41b15f9bd0e19b2ab9674135813ec98f",
-                "reference": "5de4ba2d41b15f9bd0e19b2ab9674135813ec98f",
+                "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/13f6d1271c663dc5ae9fb843a8f16521db7687a1",
+                "reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1",
                 "shasum": ""
             },
             "require": {
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-main": "1.23-dev"
+                    "dev-main": "1.26-dev"
                 },
                 "thanks": {
                     "name": "symfony/polyfill",
                 "shim"
             ],
             "support": {
-                "source": "https://github.com/symfony/polyfill-php81/tree/v1.25.0"
+                "source": "https://github.com/symfony/polyfill-php81/tree/v1.26.0"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2021-09-13T13:58:11+00:00"
+            "time": "2022-05-24T11:49:31+00:00"
         },
         {
             "name": "symfony/process",
         },
         {
             "name": "symfony/string",
-            "version": "v5.4.8",
+            "version": "v5.4.9",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/string.git",
-                "reference": "3c061a76bff6d6ea427d85e12ad1bb8ed8cd43e8"
+                "reference": "985e6a9703ef5ce32ba617c9c7d97873bb7b2a99"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/string/zipball/3c061a76bff6d6ea427d85e12ad1bb8ed8cd43e8",
-                "reference": "3c061a76bff6d6ea427d85e12ad1bb8ed8cd43e8",
+                "url": "https://api.github.com/repos/symfony/string/zipball/985e6a9703ef5ce32ba617c9c7d97873bb7b2a99",
+                "reference": "985e6a9703ef5ce32ba617c9c7d97873bb7b2a99",
                 "shasum": ""
             },
             "require": {
                 "utf8"
             ],
             "support": {
-                "source": "https://github.com/symfony/string/tree/v5.4.8"
+                "source": "https://github.com/symfony/string/tree/v5.4.9"
             },
             "funding": [
                 {
         },
         {
             "name": "symfony/translation",
-            "version": "v5.4.8",
+            "version": "v5.4.9",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/translation.git",
-                "reference": "f5c0f6d1f20993b2606f3a5f36b1dc8c1899170b"
+                "reference": "1639abc1177d26bcd4320e535e664cef067ab0ca"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/translation/zipball/f5c0f6d1f20993b2606f3a5f36b1dc8c1899170b",
-                "reference": "f5c0f6d1f20993b2606f3a5f36b1dc8c1899170b",
+                "url": "https://api.github.com/repos/symfony/translation/zipball/1639abc1177d26bcd4320e535e664cef067ab0ca",
+                "reference": "1639abc1177d26bcd4320e535e664cef067ab0ca",
                 "shasum": ""
             },
             "require": {
             "description": "Provides tools to internationalize your application",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/translation/tree/v5.4.8"
+                "source": "https://github.com/symfony/translation/tree/v5.4.9"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-04-22T08:14:12+00:00"
+            "time": "2022-05-06T12:33:37+00:00"
         },
         {
             "name": "symfony/translation-contracts",
         },
         {
             "name": "symfony/var-dumper",
-            "version": "v5.4.8",
+            "version": "v5.4.9",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/var-dumper.git",
-                "reference": "cdcadd343d31ad16fc5e006b0de81ea307435053"
+                "reference": "af52239a330fafd192c773795520dc2dd62b5657"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/var-dumper/zipball/cdcadd343d31ad16fc5e006b0de81ea307435053",
-                "reference": "cdcadd343d31ad16fc5e006b0de81ea307435053",
+                "url": "https://api.github.com/repos/symfony/var-dumper/zipball/af52239a330fafd192c773795520dc2dd62b5657",
+                "reference": "af52239a330fafd192c773795520dc2dd62b5657",
                 "shasum": ""
             },
             "require": {
                 "dump"
             ],
             "support": {
-                "source": "https://github.com/symfony/var-dumper/tree/v5.4.8"
+                "source": "https://github.com/symfony/var-dumper/tree/v5.4.9"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-04-26T13:19:20+00:00"
+            "time": "2022-05-21T10:24:18+00:00"
         },
         {
             "name": "tijsverkoyen/css-to-inline-styles",
         },
         {
             "name": "webmozart/assert",
-            "version": "1.10.0",
+            "version": "1.11.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/webmozarts/assert.git",
-                "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25"
+                "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25",
-                "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25",
+                "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991",
+                "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991",
                 "shasum": ""
             },
             "require": {
-                "php": "^7.2 || ^8.0",
-                "symfony/polyfill-ctype": "^1.8"
+                "ext-ctype": "*",
+                "php": "^7.2 || ^8.0"
             },
             "conflict": {
                 "phpstan/phpstan": "<0.12.20",
             ],
             "support": {
                 "issues": "https://github.com/webmozarts/assert/issues",
-                "source": "https://github.com/webmozarts/assert/tree/1.10.0"
+                "source": "https://github.com/webmozarts/assert/tree/1.11.0"
             },
-            "time": "2021-03-09T10:59:23+00:00"
+            "time": "2022-06-03T18:03:27+00:00"
         }
     ],
     "packages-dev": [
         {
             "name": "composer/ca-bundle",
-            "version": "1.3.1",
+            "version": "1.3.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/composer/ca-bundle.git",
-                "reference": "4c679186f2aca4ab6a0f1b0b9cf9252decb44d0b"
+                "reference": "fd5dd441932a7e10ca6e5b490e272d34c8430640"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/composer/ca-bundle/zipball/4c679186f2aca4ab6a0f1b0b9cf9252decb44d0b",
-                "reference": "4c679186f2aca4ab6a0f1b0b9cf9252decb44d0b",
+                "url": "https://api.github.com/repos/composer/ca-bundle/zipball/fd5dd441932a7e10ca6e5b490e272d34c8430640",
+                "reference": "fd5dd441932a7e10ca6e5b490e272d34c8430640",
                 "shasum": ""
             },
             "require": {
             "support": {
                 "irc": "irc://irc.freenode.org/composer",
                 "issues": "https://github.com/composer/ca-bundle/issues",
-                "source": "https://github.com/composer/ca-bundle/tree/1.3.1"
+                "source": "https://github.com/composer/ca-bundle/tree/1.3.2"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2021-10-28T20:44:15+00:00"
+            "time": "2022-05-24T11:56:16+00:00"
         },
         {
             "name": "composer/composer",
-            "version": "2.3.5",
+            "version": "2.3.7",
             "source": {
                 "type": "git",
                 "url": "https://github.com/composer/composer.git",
-                "reference": "50c47b1f907cfcdb8f072b88164d22b527557ae1"
+                "reference": "10cd375cf85dede2ff417ceab517ef9a0dc55407"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/composer/composer/zipball/50c47b1f907cfcdb8f072b88164d22b527557ae1",
-                "reference": "50c47b1f907cfcdb8f072b88164d22b527557ae1",
+                "url": "https://api.github.com/repos/composer/composer/zipball/10cd375cf85dede2ff417ceab517ef9a0dc55407",
+                "reference": "10cd375cf85dede2ff417ceab517ef9a0dc55407",
                 "shasum": ""
             },
             "require": {
                 "react/promise": "^2.8",
                 "seld/jsonlint": "^1.4",
                 "seld/phar-utils": "^1.2",
-                "symfony/console": "^5.4.1 || ^6.0",
+                "symfony/console": "^5.4.7 || ^6.0.7",
                 "symfony/filesystem": "^5.4 || ^6.0",
                 "symfony/finder": "^5.4 || ^6.0",
                 "symfony/polyfill-php73": "^1.24",
             "extra": {
                 "branch-alias": {
                     "dev-main": "2.3-dev"
+                },
+                "phpstan": {
+                    "includes": [
+                        "phpstan/rules.neon"
+                    ]
                 }
             },
             "autoload": {
             "support": {
                 "irc": "ircs://irc.libera.chat:6697/composer",
                 "issues": "https://github.com/composer/composer/issues",
-                "source": "https://github.com/composer/composer/tree/2.3.5"
+                "source": "https://github.com/composer/composer/tree/2.3.7"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-04-13T14:43:00+00:00"
+            "time": "2022-06-06T14:43:28+00:00"
         },
         {
             "name": "composer/metadata-minifier",
         },
         {
             "name": "composer/spdx-licenses",
-            "version": "1.5.6",
+            "version": "1.5.7",
             "source": {
                 "type": "git",
                 "url": "https://github.com/composer/spdx-licenses.git",
-                "reference": "a30d487169d799745ca7280bc90fdfa693536901"
+                "reference": "c848241796da2abf65837d51dce1fae55a960149"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/a30d487169d799745ca7280bc90fdfa693536901",
-                "reference": "a30d487169d799745ca7280bc90fdfa693536901",
+                "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/c848241796da2abf65837d51dce1fae55a960149",
+                "reference": "c848241796da2abf65837d51dce1fae55a960149",
                 "shasum": ""
             },
             "require": {
             "support": {
                 "irc": "irc://irc.freenode.org/composer",
                 "issues": "https://github.com/composer/spdx-licenses/issues",
-                "source": "https://github.com/composer/spdx-licenses/tree/1.5.6"
+                "source": "https://github.com/composer/spdx-licenses/tree/1.5.7"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2021-11-18T10:14:14+00:00"
+            "time": "2022-05-23T07:37:50+00:00"
         },
         {
             "name": "composer/xdebug-handler",
         },
         {
             "name": "phpstan/phpstan",
-            "version": "1.6.3",
+            "version": "1.7.12",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phpstan/phpstan.git",
-                "reference": "6128620b98292e0b69ea6d799871d77163681c8e"
+                "reference": "32f10779d9cd88a9cbd972ec611a4148a3cbbc7e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpstan/phpstan/zipball/6128620b98292e0b69ea6d799871d77163681c8e",
-                "reference": "6128620b98292e0b69ea6d799871d77163681c8e",
+                "url": "https://api.github.com/repos/phpstan/phpstan/zipball/32f10779d9cd88a9cbd972ec611a4148a3cbbc7e",
+                "reference": "32f10779d9cd88a9cbd972ec611a4148a3cbbc7e",
                 "shasum": ""
             },
             "require": {
             "description": "PHPStan - PHP Static Analysis Tool",
             "support": {
                 "issues": "https://github.com/phpstan/phpstan/issues",
-                "source": "https://github.com/phpstan/phpstan/tree/1.6.3"
+                "source": "https://github.com/phpstan/phpstan/tree/1.7.12"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-04-28T11:27:53+00:00"
+            "time": "2022-06-09T12:39:36+00:00"
         },
         {
             "name": "phpunit/php-code-coverage",
         },
         {
             "name": "symfony/dom-crawler",
-            "version": "v5.4.6",
+            "version": "v5.4.9",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/dom-crawler.git",
-                "reference": "c0bda97480d96337bd3866026159a8b358665457"
+                "reference": "a213cbc80382320b0efdccdcdce232f191fafe3a"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/c0bda97480d96337bd3866026159a8b358665457",
-                "reference": "c0bda97480d96337bd3866026159a8b358665457",
+                "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/a213cbc80382320b0efdccdcdce232f191fafe3a",
+                "reference": "a213cbc80382320b0efdccdcdce232f191fafe3a",
                 "shasum": ""
             },
             "require": {
             "description": "Eases DOM navigation for HTML and XML documents",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/dom-crawler/tree/v5.4.6"
+                "source": "https://github.com/symfony/dom-crawler/tree/v5.4.9"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-03-02T12:42:23+00:00"
+            "time": "2022-05-04T14:46:32+00:00"
         },
         {
             "name": "symfony/filesystem",
-            "version": "v5.4.7",
+            "version": "v5.4.9",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/filesystem.git",
-                "reference": "3a4442138d80c9f7b600fb297534ac718b61d37f"
+                "reference": "36a017fa4cce1eff1b8e8129ff53513abcef05ba"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/filesystem/zipball/3a4442138d80c9f7b600fb297534ac718b61d37f",
-                "reference": "3a4442138d80c9f7b600fb297534ac718b61d37f",
+                "url": "https://api.github.com/repos/symfony/filesystem/zipball/36a017fa4cce1eff1b8e8129ff53513abcef05ba",
+                "reference": "36a017fa4cce1eff1b8e8129ff53513abcef05ba",
                 "shasum": ""
             },
             "require": {
             "description": "Provides basic utilities for the filesystem",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/filesystem/tree/v5.4.7"
+                "source": "https://github.com/symfony/filesystem/tree/v5.4.9"
             },
             "funding": [
                 {
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-04-01T12:33:59+00:00"
+            "time": "2022-05-20T13:55:35+00:00"
         },
         {
             "name": "theseer/tokenizer",
index a952e0487f1677e2b2876c360eb2970cfe6c9a64..6ab3d710e96f8e61f32288f12dba915f5e26f6a8 100644 (file)
@@ -21,8 +21,9 @@ class RoleFactory extends Factory
     public function definition()
     {
         return [
-            'display_name' => $this->faker->sentence(3),
-            'description'  => $this->faker->sentence(10),
+            'display_name'     => $this->faker->sentence(3),
+            'description'      => $this->faker->sentence(10),
+            'external_auth_id' => '',
         ];
     }
 }
index 45a8c542f387b0a8000387fd2ccb2bcb9e72aa65..0f8458feda29861821108f85b887ec4261b22e7a 100644 (file)
@@ -9,8 +9,7 @@
       "updated_at": "2019-12-11T20:57:31.000000Z",
       "created_by": 1,
       "updated_by": 1,
-      "owned_by": 1,
-      "image_id": 3
+      "owned_by": 1
     },
     {
       "id": 2,
@@ -21,8 +20,7 @@
       "updated_at": "2019-12-11T20:57:23.000000Z",
       "created_by": 4,
       "updated_by": 3,
-      "owned_by": 3,
-      "image_id": 34
+      "owned_by": 3
     }
   ],
   "total": 14
index 30c910aa32db870deed44c4954b894f6df8e142e..7d3d6735e1ecaa9efea5c39c3da88cf16b5e2dd4 100644 (file)
@@ -7,6 +7,5 @@
   "updated_at": "2020-01-12T14:16:10.000000Z",
   "created_by": 1,
   "updated_by": 1,
-  "owned_by": 1,
-  "image_id": 452
+  "owned_by": 1
 }
\ No newline at end of file
index 82ead7d93a3da98279cef96cb1d2a905218fe317..4b1a1b43f930fc27b92694d23ecc0e29c822a86d 100644 (file)
@@ -9,8 +9,7 @@
       "updated_at": "2020-04-10T13:00:45.000000Z",
       "created_by": 4,
       "updated_by": 1,
-      "owned_by": 1,
-      "image_id": 31
+      "owned_by": 1
     },
     {
       "id": 9,
@@ -21,8 +20,7 @@
       "updated_at": "2020-04-10T13:00:58.000000Z",
       "created_by": 4,
       "updated_by": 1,
-      "owned_by": 1,
-      "image_id": 28
+      "owned_by": 1
     },
     {
       "id": 10,
@@ -33,8 +31,7 @@
       "updated_at": "2020-04-10T13:00:53.000000Z",
       "created_by": 4,
       "updated_by": 1,
-      "owned_by": 4,
-      "image_id": 30
+      "owned_by": 4
     }
   ],
   "total": 3
index 551dacb1f305aebcf55b2d3355266676cc8151a9..e199d8d68a552cfc96664ff6aa7ad09f33b20c84 100644 (file)
@@ -6,7 +6,6 @@
   "created_by": 1,
   "updated_by": 1,
   "owned_by": 1,
-  "image_id": 501,
   "created_at": "2020-04-10T13:24:09.000000Z",
   "updated_at": "2020-04-10T13:48:22.000000Z"
 }
\ No newline at end of file
index 7f0df22827f0d874439831c512f6a3c66ba13b0a..1448d592fa0f72b3f40b39fc4b86d033fb402928 100644 (file)
@@ -5,20 +5,21 @@
   "packages": {
     "": {
       "dependencies": {
-        "clipboard": "^2.0.10",
-        "codemirror": "^5.65.2",
+        "clipboard": "^2.0.11",
+        "codemirror": "^5.65.5",
         "dropzone": "^5.9.3",
-        "markdown-it": "^12.3.2",
+        "markdown-it": "^13.0.1",
         "markdown-it-task-lists": "^2.1.1",
+        "snabbdom": "^3.5.0",
         "sortablejs": "^1.15.0"
       },
       "devDependencies": {
         "chokidar-cli": "^3.0",
-        "esbuild": "0.14.36",
+        "esbuild": "0.14.42",
         "livereload": "^0.9.3",
         "npm-run-all": "^4.1.5",
         "punycode": "^2.1.1",
-        "sass": "^1.50.0"
+        "sass": "^1.52.1"
       }
     },
     "node_modules/ansi-regex": {
       }
     },
     "node_modules/clipboard": {
-      "version": "2.0.10",
-      "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.10.tgz",
-      "integrity": "sha512-cz3m2YVwFz95qSEbCDi2fzLN/epEN9zXBvfgAoGkvGOJZATMl9gtTDVOtBYkx2ODUJl2kvmud7n32sV2BpYR4g==",
+      "version": "2.0.11",
+      "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz",
+      "integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==",
       "dependencies": {
         "good-listener": "^1.2.2",
         "select": "^1.1.2",
       }
     },
     "node_modules/codemirror": {
-      "version": "5.65.2",
-      "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.2.tgz",
-      "integrity": "sha512-SZM4Zq7XEC8Fhroqe3LxbEEX1zUPWH1wMr5zxiBuiUF64iYOUH/JI88v4tBag8MiBS8B8gRv8O1pPXGYXQ4ErA=="
+      "version": "5.65.5",
+      "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.5.tgz",
+      "integrity": "sha512-HNyhvGLnYz5c+kIsB9QKVitiZUevha3ovbIYaQiGzKo7ECSL/elWD9RXt3JgNr0NdnyqE9/Rc/7uLfkJQL638w=="
     },
     "node_modules/color-convert": {
       "version": "1.9.3",
       "dev": true
     },
     "node_modules/entities": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz",
-      "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==",
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz",
+      "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==",
+      "engines": {
+        "node": ">=0.12"
+      },
       "funding": {
         "url": "https://github.com/fb55/entities?sponsor=1"
       }
       }
     },
     "node_modules/esbuild": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.36.tgz",
-      "integrity": "sha512-HhFHPiRXGYOCRlrhpiVDYKcFJRdO0sBElZ668M4lh2ER0YgnkLxECuFe7uWCf23FrcLc59Pqr7dHkTqmRPDHmw==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.42.tgz",
+      "integrity": "sha512-V0uPZotCEHokJdNqyozH6qsaQXqmZEOiZWrXnds/zaH/0SyrIayRXWRB98CENO73MIZ9T3HBIOsmds5twWtmgw==",
       "dev": true,
       "hasInstallScript": true,
       "bin": {
         "node": ">=12"
       },
       "optionalDependencies": {
-        "esbuild-android-64": "0.14.36",
-        "esbuild-android-arm64": "0.14.36",
-        "esbuild-darwin-64": "0.14.36",
-        "esbuild-darwin-arm64": "0.14.36",
-        "esbuild-freebsd-64": "0.14.36",
-        "esbuild-freebsd-arm64": "0.14.36",
-        "esbuild-linux-32": "0.14.36",
-        "esbuild-linux-64": "0.14.36",
-        "esbuild-linux-arm": "0.14.36",
-        "esbuild-linux-arm64": "0.14.36",
-        "esbuild-linux-mips64le": "0.14.36",
-        "esbuild-linux-ppc64le": "0.14.36",
-        "esbuild-linux-riscv64": "0.14.36",
-        "esbuild-linux-s390x": "0.14.36",
-        "esbuild-netbsd-64": "0.14.36",
-        "esbuild-openbsd-64": "0.14.36",
-        "esbuild-sunos-64": "0.14.36",
-        "esbuild-windows-32": "0.14.36",
-        "esbuild-windows-64": "0.14.36",
-        "esbuild-windows-arm64": "0.14.36"
+        "esbuild-android-64": "0.14.42",
+        "esbuild-android-arm64": "0.14.42",
+        "esbuild-darwin-64": "0.14.42",
+        "esbuild-darwin-arm64": "0.14.42",
+        "esbuild-freebsd-64": "0.14.42",
+        "esbuild-freebsd-arm64": "0.14.42",
+        "esbuild-linux-32": "0.14.42",
+        "esbuild-linux-64": "0.14.42",
+        "esbuild-linux-arm": "0.14.42",
+        "esbuild-linux-arm64": "0.14.42",
+        "esbuild-linux-mips64le": "0.14.42",
+        "esbuild-linux-ppc64le": "0.14.42",
+        "esbuild-linux-riscv64": "0.14.42",
+        "esbuild-linux-s390x": "0.14.42",
+        "esbuild-netbsd-64": "0.14.42",
+        "esbuild-openbsd-64": "0.14.42",
+        "esbuild-sunos-64": "0.14.42",
+        "esbuild-windows-32": "0.14.42",
+        "esbuild-windows-64": "0.14.42",
+        "esbuild-windows-arm64": "0.14.42"
       }
     },
     "node_modules/esbuild-android-64": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.36.tgz",
-      "integrity": "sha512-jwpBhF1jmo0tVCYC/ORzVN+hyVcNZUWuozGcLHfod0RJCedTDTvR4nwlTXdx1gtncDqjk33itjO+27OZHbiavw==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.42.tgz",
+      "integrity": "sha512-P4Y36VUtRhK/zivqGVMqhptSrFILAGlYp0Z8r9UQqHJ3iWztRCNWnlBzD9HRx0DbueXikzOiwyOri+ojAFfW6A==",
       "cpu": [
         "x64"
       ],
       }
     },
     "node_modules/esbuild-android-arm64": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.36.tgz",
-      "integrity": "sha512-/hYkyFe7x7Yapmfv4X/tBmyKnggUmdQmlvZ8ZlBnV4+PjisrEhAvC3yWpURuD9XoB8Wa1d5dGkTsF53pIvpjsg==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.42.tgz",
+      "integrity": "sha512-0cOqCubq+RWScPqvtQdjXG3Czb3AWI2CaKw3HeXry2eoA2rrPr85HF7IpdU26UWdBXgPYtlTN1LUiuXbboROhg==",
       "cpu": [
         "arm64"
       ],
       }
     },
     "node_modules/esbuild-darwin-64": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.36.tgz",
-      "integrity": "sha512-kkl6qmV0dTpyIMKagluzYqlc1vO0ecgpviK/7jwPbRDEv5fejRTaBBEE2KxEQbTHcLhiiDbhG7d5UybZWo/1zQ==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.42.tgz",
+      "integrity": "sha512-ipiBdCA3ZjYgRfRLdQwP82rTiv/YVMtW36hTvAN5ZKAIfxBOyPXY7Cejp3bMXWgzKD8B6O+zoMzh01GZsCuEIA==",
       "cpu": [
         "x64"
       ],
       }
     },
     "node_modules/esbuild-darwin-arm64": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.36.tgz",
-      "integrity": "sha512-q8fY4r2Sx6P0Pr3VUm//eFYKVk07C5MHcEinU1BjyFnuYz4IxR/03uBbDwluR6ILIHnZTE7AkTUWIdidRi1Jjw==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.42.tgz",
+      "integrity": "sha512-bU2tHRqTPOaoH/4m0zYHbFWpiYDmaA0gt90/3BMEFaM0PqVK/a6MA2V/ypV5PO0v8QxN6gH5hBPY4YJ2lopXgA==",
       "cpu": [
         "arm64"
       ],
       }
     },
     "node_modules/esbuild-freebsd-64": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.36.tgz",
-      "integrity": "sha512-Hn8AYuxXXRptybPqoMkga4HRFE7/XmhtlQjXFHoAIhKUPPMeJH35GYEUWGbjteai9FLFvBAjEAlwEtSGxnqWww==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.42.tgz",
+      "integrity": "sha512-75h1+22Ivy07+QvxHyhVqOdekupiTZVLN1PMwCDonAqyXd8TVNJfIRFrdL8QmSJrOJJ5h8H1I9ETyl2L8LQDaw==",
       "cpu": [
         "x64"
       ],
       }
     },
     "node_modules/esbuild-freebsd-arm64": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.36.tgz",
-      "integrity": "sha512-S3C0attylLLRiCcHiJd036eDEMOY32+h8P+jJ3kTcfhJANNjP0TNBNL30TZmEdOSx/820HJFgRrqpNAvTbjnDA==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.42.tgz",
+      "integrity": "sha512-W6Jebeu5TTDQMJUJVarEzRU9LlKpNkPBbjqSu+GUPTHDCly5zZEQq9uHkmHHl7OKm+mQ2zFySN83nmfCeZCyNA==",
       "cpu": [
         "arm64"
       ],
       }
     },
     "node_modules/esbuild-linux-32": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.36.tgz",
-      "integrity": "sha512-Eh9OkyTrEZn9WGO4xkI3OPPpUX7p/3QYvdG0lL4rfr73Ap2HAr6D9lP59VMF64Ex01LhHSXwIsFG/8AQjh6eNw==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.42.tgz",
+      "integrity": "sha512-Ooy/Bj+mJ1z4jlWcK5Dl6SlPlCgQB9zg1UrTCeY8XagvuWZ4qGPyYEWGkT94HUsRi2hKsXvcs6ThTOjBaJSMfg==",
       "cpu": [
         "ia32"
       ],
       }
     },
     "node_modules/esbuild-linux-64": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.36.tgz",
-      "integrity": "sha512-vFVFS5ve7PuwlfgoWNyRccGDi2QTNkQo/2k5U5ttVD0jRFaMlc8UQee708fOZA6zTCDy5RWsT5MJw3sl2X6KDg==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.42.tgz",
+      "integrity": "sha512-2L0HbzQfbTuemUWfVqNIjOfaTRt9zsvjnme6lnr7/MO9toz/MJ5tZhjqrG6uDWDxhsaHI2/nsDgrv8uEEN2eoA==",
       "cpu": [
         "x64"
       ],
       }
     },
     "node_modules/esbuild-linux-arm": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.36.tgz",
-      "integrity": "sha512-NhgU4n+NCsYgt7Hy61PCquEz5aevI6VjQvxwBxtxrooXsxt5b2xtOUXYZe04JxqQo+XZk3d1gcr7pbV9MAQ/Lg==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.42.tgz",
+      "integrity": "sha512-STq69yzCMhdRaWnh29UYrLSr/qaWMm/KqwaRF1pMEK7kDiagaXhSL1zQGXbYv94GuGY/zAwzK98+6idCMUOOCg==",
       "cpu": [
         "arm"
       ],
       }
     },
     "node_modules/esbuild-linux-arm64": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.36.tgz",
-      "integrity": "sha512-24Vq1M7FdpSmaTYuu1w0Hdhiqkbto1I5Pjyi+4Cdw5fJKGlwQuw+hWynTcRI/cOZxBcBpP21gND7W27gHAiftw==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.42.tgz",
+      "integrity": "sha512-c3Ug3e9JpVr8jAcfbhirtpBauLxzYPpycjWulD71CF6ZSY26tvzmXMJYooQ2YKqDY4e/fPu5K8bm7MiXMnyxuA==",
       "cpu": [
         "arm64"
       ],
       }
     },
     "node_modules/esbuild-linux-mips64le": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.36.tgz",
-      "integrity": "sha512-hZUeTXvppJN+5rEz2EjsOFM9F1bZt7/d2FUM1lmQo//rXh1RTFYzhC0txn7WV0/jCC7SvrGRaRz0NMsRPf8SIA==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.42.tgz",
+      "integrity": "sha512-QuvpHGbYlkyXWf2cGm51LBCHx6eUakjaSrRpUqhPwjh/uvNUYvLmz2LgPTTPwCqaKt0iwL+OGVL0tXA5aDbAbg==",
       "cpu": [
         "mips64el"
       ],
       }
     },
     "node_modules/esbuild-linux-ppc64le": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.36.tgz",
-      "integrity": "sha512-1Bg3QgzZjO+QtPhP9VeIBhAduHEc2kzU43MzBnMwpLSZ890azr4/A9Dganun8nsqD/1TBcqhId0z4mFDO8FAvg==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.42.tgz",
+      "integrity": "sha512-8ohIVIWDbDT+i7lCx44YCyIRrOW1MYlks9fxTo0ME2LS/fxxdoJBwHWzaDYhjvf8kNpA+MInZvyOEAGoVDrMHg==",
       "cpu": [
         "ppc64"
       ],
       }
     },
     "node_modules/esbuild-linux-riscv64": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.36.tgz",
-      "integrity": "sha512-dOE5pt3cOdqEhaufDRzNCHf5BSwxgygVak9UR7PH7KPVHwSTDAZHDoEjblxLqjJYpc5XaU9+gKJ9F8mp9r5I4A==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.42.tgz",
+      "integrity": "sha512-DzDqK3TuoXktPyG1Lwx7vhaF49Onv3eR61KwQyxYo4y5UKTpL3NmuarHSIaSVlTFDDpcIajCDwz5/uwKLLgKiQ==",
       "cpu": [
         "riscv64"
       ],
       }
     },
     "node_modules/esbuild-linux-s390x": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.36.tgz",
-      "integrity": "sha512-g4FMdh//BBGTfVHjF6MO7Cz8gqRoDPzXWxRvWkJoGroKA18G9m0wddvPbEqcQf5Tbt2vSc1CIgag7cXwTmoTXg==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.42.tgz",
+      "integrity": "sha512-YFRhPCxl8nb//Wn6SiS5pmtplBi4z9yC2gLrYoYI/tvwuB1jldir9r7JwAGy1Ck4D7sE7wBN9GFtUUX/DLdcEQ==",
       "cpu": [
         "s390x"
       ],
       }
     },
     "node_modules/esbuild-netbsd-64": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.36.tgz",
-      "integrity": "sha512-UB2bVImxkWk4vjnP62ehFNZ73lQY1xcnL5ZNYF3x0AG+j8HgdkNF05v67YJdCIuUJpBuTyCK8LORCYo9onSW+A==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.42.tgz",
+      "integrity": "sha512-QYSD2k+oT9dqB/4eEM9c+7KyNYsIPgzYOSrmfNGDIyJrbT1d+CFVKvnKahDKNJLfOYj8N4MgyFaU9/Ytc6w5Vw==",
       "cpu": [
         "x64"
       ],
       }
     },
     "node_modules/esbuild-openbsd-64": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.36.tgz",
-      "integrity": "sha512-NvGB2Chf8GxuleXRGk8e9zD3aSdRO5kLt9coTQbCg7WMGXeX471sBgh4kSg8pjx0yTXRt0MlrUDnjVYnetyivg==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.42.tgz",
+      "integrity": "sha512-M2meNVIKWsm2HMY7+TU9AxM7ZVwI9havdsw6m/6EzdXysyCFFSoaTQ/Jg03izjCsK17FsVRHqRe26Llj6x0MNA==",
       "cpu": [
         "x64"
       ],
       }
     },
     "node_modules/esbuild-sunos-64": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.36.tgz",
-      "integrity": "sha512-VkUZS5ftTSjhRjuRLp+v78auMO3PZBXu6xl4ajomGenEm2/rGuWlhFSjB7YbBNErOchj51Jb2OK8lKAo8qdmsQ==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.42.tgz",
+      "integrity": "sha512-uXV8TAZEw36DkgW8Ak3MpSJs1ofBb3Smkc/6pZ29sCAN1KzCAQzsje4sUwugf+FVicrHvlamCOlFZIXgct+iqQ==",
       "cpu": [
         "x64"
       ],
       }
     },
     "node_modules/esbuild-windows-32": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.36.tgz",
-      "integrity": "sha512-bIar+A6hdytJjZrDxfMBUSEHHLfx3ynoEZXx/39nxy86pX/w249WZm8Bm0dtOAByAf4Z6qV0LsnTIJHiIqbw0w==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.42.tgz",
+      "integrity": "sha512-4iw/8qWmRICWi9ZOnJJf9sYt6wmtp3hsN4TdI5NqgjfOkBVMxNdM9Vt3626G1Rda9ya2Q0hjQRD9W1o+m6Lz6g==",
       "cpu": [
         "ia32"
       ],
       }
     },
     "node_modules/esbuild-windows-64": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.36.tgz",
-      "integrity": "sha512-+p4MuRZekVChAeueT1Y9LGkxrT5x7YYJxYE8ZOTcEfeUUN43vktSn6hUNsvxzzATrSgq5QqRdllkVBxWZg7KqQ==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.42.tgz",
+      "integrity": "sha512-j3cdK+Y3+a5H0wHKmLGTJcq0+/2mMBHPWkItR3vytp/aUGD/ua/t2BLdfBIzbNN9nLCRL9sywCRpOpFMx3CxzA==",
       "cpu": [
         "x64"
       ],
       }
     },
     "node_modules/esbuild-windows-arm64": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.36.tgz",
-      "integrity": "sha512-fBB4WlDqV1m18EF/aheGYQkQZHfPHiHJSBYzXIo8yKehek+0BtBwo/4PNwKGJ5T0YK0oc8pBKjgwPbzSrPLb+Q==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.42.tgz",
+      "integrity": "sha512-+lRAARnF+hf8J0mN27ujO+VbhPbDqJ8rCcJKye4y7YZLV6C4n3pTRThAb388k/zqF5uM0lS5O201u0OqoWSicw==",
       "cpu": [
         "arm64"
       ],
       "dev": true
     },
     "node_modules/linkify-it": {
-      "version": "3.0.3",
-      "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz",
-      "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==",
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz",
+      "integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==",
       "dependencies": {
         "uc.micro": "^1.0.1"
       }
       "dev": true
     },
     "node_modules/markdown-it": {
-      "version": "12.3.2",
-      "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz",
-      "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==",
+      "version": "13.0.1",
+      "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.1.tgz",
+      "integrity": "sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==",
       "dependencies": {
         "argparse": "^2.0.1",
-        "entities": "~2.1.0",
-        "linkify-it": "^3.0.1",
+        "entities": "~3.0.1",
+        "linkify-it": "^4.0.1",
         "mdurl": "^1.0.1",
         "uc.micro": "^1.0.5"
       },
       }
     },
     "node_modules/sass": {
-      "version": "1.50.0",
-      "resolved": "https://registry.npmjs.org/sass/-/sass-1.50.0.tgz",
-      "integrity": "sha512-cLsD6MEZ5URXHStxApajEh7gW189kkjn4Rc8DQweMyF+o5HF5nfEz8QYLMlPsTOD88DknatTmBWkOcw5/LnJLQ==",
+      "version": "1.52.1",
+      "resolved": "https://registry.npmjs.org/sass/-/sass-1.52.1.tgz",
+      "integrity": "sha512-fSzYTbr7z8oQnVJ3Acp9hV80dM1fkMN7mSD/25mpcct9F7FPBMOI8krEYALgU1aZoqGhQNhTPsuSmxjnIvAm4Q==",
       "dev": true,
       "dependencies": {
         "chokidar": ">=3.0.0 <4.0.0",
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/snabbdom": {
+      "version": "3.5.0",
+      "resolved": "https://registry.npmjs.org/snabbdom/-/snabbdom-3.5.0.tgz",
+      "integrity": "sha512-Ff5BKG18KrrPuskHJlA9aujPHqEabItaDl96l7ZZndF4zt5AYSczz7ZjjgQAX5IBd5cd25lw9NfgX21yVUJ+9g==",
+      "engines": {
+        "node": ">=8.3.0"
+      }
+    },
     "node_modules/sortablejs": {
       "version": "1.15.0",
       "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.0.tgz",
       }
     },
     "clipboard": {
-      "version": "2.0.10",
-      "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.10.tgz",
-      "integrity": "sha512-cz3m2YVwFz95qSEbCDi2fzLN/epEN9zXBvfgAoGkvGOJZATMl9gtTDVOtBYkx2ODUJl2kvmud7n32sV2BpYR4g==",
+      "version": "2.0.11",
+      "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz",
+      "integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==",
       "requires": {
         "good-listener": "^1.2.2",
         "select": "^1.1.2",
       }
     },
     "codemirror": {
-      "version": "5.65.2",
-      "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.2.tgz",
-      "integrity": "sha512-SZM4Zq7XEC8Fhroqe3LxbEEX1zUPWH1wMr5zxiBuiUF64iYOUH/JI88v4tBag8MiBS8B8gRv8O1pPXGYXQ4ErA=="
+      "version": "5.65.5",
+      "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.5.tgz",
+      "integrity": "sha512-HNyhvGLnYz5c+kIsB9QKVitiZUevha3ovbIYaQiGzKo7ECSL/elWD9RXt3JgNr0NdnyqE9/Rc/7uLfkJQL638w=="
     },
     "color-convert": {
       "version": "1.9.3",
       "dev": true
     },
     "entities": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz",
-      "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w=="
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz",
+      "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q=="
     },
     "error-ex": {
       "version": "1.3.2",
       }
     },
     "esbuild": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.36.tgz",
-      "integrity": "sha512-HhFHPiRXGYOCRlrhpiVDYKcFJRdO0sBElZ668M4lh2ER0YgnkLxECuFe7uWCf23FrcLc59Pqr7dHkTqmRPDHmw==",
-      "dev": true,
-      "requires": {
-        "esbuild-android-64": "0.14.36",
-        "esbuild-android-arm64": "0.14.36",
-        "esbuild-darwin-64": "0.14.36",
-        "esbuild-darwin-arm64": "0.14.36",
-        "esbuild-freebsd-64": "0.14.36",
-        "esbuild-freebsd-arm64": "0.14.36",
-        "esbuild-linux-32": "0.14.36",
-        "esbuild-linux-64": "0.14.36",
-        "esbuild-linux-arm": "0.14.36",
-        "esbuild-linux-arm64": "0.14.36",
-        "esbuild-linux-mips64le": "0.14.36",
-        "esbuild-linux-ppc64le": "0.14.36",
-        "esbuild-linux-riscv64": "0.14.36",
-        "esbuild-linux-s390x": "0.14.36",
-        "esbuild-netbsd-64": "0.14.36",
-        "esbuild-openbsd-64": "0.14.36",
-        "esbuild-sunos-64": "0.14.36",
-        "esbuild-windows-32": "0.14.36",
-        "esbuild-windows-64": "0.14.36",
-        "esbuild-windows-arm64": "0.14.36"
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.42.tgz",
+      "integrity": "sha512-V0uPZotCEHokJdNqyozH6qsaQXqmZEOiZWrXnds/zaH/0SyrIayRXWRB98CENO73MIZ9T3HBIOsmds5twWtmgw==",
+      "dev": true,
+      "requires": {
+        "esbuild-android-64": "0.14.42",
+        "esbuild-android-arm64": "0.14.42",
+        "esbuild-darwin-64": "0.14.42",
+        "esbuild-darwin-arm64": "0.14.42",
+        "esbuild-freebsd-64": "0.14.42",
+        "esbuild-freebsd-arm64": "0.14.42",
+        "esbuild-linux-32": "0.14.42",
+        "esbuild-linux-64": "0.14.42",
+        "esbuild-linux-arm": "0.14.42",
+        "esbuild-linux-arm64": "0.14.42",
+        "esbuild-linux-mips64le": "0.14.42",
+        "esbuild-linux-ppc64le": "0.14.42",
+        "esbuild-linux-riscv64": "0.14.42",
+        "esbuild-linux-s390x": "0.14.42",
+        "esbuild-netbsd-64": "0.14.42",
+        "esbuild-openbsd-64": "0.14.42",
+        "esbuild-sunos-64": "0.14.42",
+        "esbuild-windows-32": "0.14.42",
+        "esbuild-windows-64": "0.14.42",
+        "esbuild-windows-arm64": "0.14.42"
       }
     },
     "esbuild-android-64": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.36.tgz",
-      "integrity": "sha512-jwpBhF1jmo0tVCYC/ORzVN+hyVcNZUWuozGcLHfod0RJCedTDTvR4nwlTXdx1gtncDqjk33itjO+27OZHbiavw==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.42.tgz",
+      "integrity": "sha512-P4Y36VUtRhK/zivqGVMqhptSrFILAGlYp0Z8r9UQqHJ3iWztRCNWnlBzD9HRx0DbueXikzOiwyOri+ojAFfW6A==",
       "dev": true,
       "optional": true
     },
     "esbuild-android-arm64": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.36.tgz",
-      "integrity": "sha512-/hYkyFe7x7Yapmfv4X/tBmyKnggUmdQmlvZ8ZlBnV4+PjisrEhAvC3yWpURuD9XoB8Wa1d5dGkTsF53pIvpjsg==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.42.tgz",
+      "integrity": "sha512-0cOqCubq+RWScPqvtQdjXG3Czb3AWI2CaKw3HeXry2eoA2rrPr85HF7IpdU26UWdBXgPYtlTN1LUiuXbboROhg==",
       "dev": true,
       "optional": true
     },
     "esbuild-darwin-64": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.36.tgz",
-      "integrity": "sha512-kkl6qmV0dTpyIMKagluzYqlc1vO0ecgpviK/7jwPbRDEv5fejRTaBBEE2KxEQbTHcLhiiDbhG7d5UybZWo/1zQ==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.42.tgz",
+      "integrity": "sha512-ipiBdCA3ZjYgRfRLdQwP82rTiv/YVMtW36hTvAN5ZKAIfxBOyPXY7Cejp3bMXWgzKD8B6O+zoMzh01GZsCuEIA==",
       "dev": true,
       "optional": true
     },
     "esbuild-darwin-arm64": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.36.tgz",
-      "integrity": "sha512-q8fY4r2Sx6P0Pr3VUm//eFYKVk07C5MHcEinU1BjyFnuYz4IxR/03uBbDwluR6ILIHnZTE7AkTUWIdidRi1Jjw==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.42.tgz",
+      "integrity": "sha512-bU2tHRqTPOaoH/4m0zYHbFWpiYDmaA0gt90/3BMEFaM0PqVK/a6MA2V/ypV5PO0v8QxN6gH5hBPY4YJ2lopXgA==",
       "dev": true,
       "optional": true
     },
     "esbuild-freebsd-64": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.36.tgz",
-      "integrity": "sha512-Hn8AYuxXXRptybPqoMkga4HRFE7/XmhtlQjXFHoAIhKUPPMeJH35GYEUWGbjteai9FLFvBAjEAlwEtSGxnqWww==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.42.tgz",
+      "integrity": "sha512-75h1+22Ivy07+QvxHyhVqOdekupiTZVLN1PMwCDonAqyXd8TVNJfIRFrdL8QmSJrOJJ5h8H1I9ETyl2L8LQDaw==",
       "dev": true,
       "optional": true
     },
     "esbuild-freebsd-arm64": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.36.tgz",
-      "integrity": "sha512-S3C0attylLLRiCcHiJd036eDEMOY32+h8P+jJ3kTcfhJANNjP0TNBNL30TZmEdOSx/820HJFgRrqpNAvTbjnDA==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.42.tgz",
+      "integrity": "sha512-W6Jebeu5TTDQMJUJVarEzRU9LlKpNkPBbjqSu+GUPTHDCly5zZEQq9uHkmHHl7OKm+mQ2zFySN83nmfCeZCyNA==",
       "dev": true,
       "optional": true
     },
     "esbuild-linux-32": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.36.tgz",
-      "integrity": "sha512-Eh9OkyTrEZn9WGO4xkI3OPPpUX7p/3QYvdG0lL4rfr73Ap2HAr6D9lP59VMF64Ex01LhHSXwIsFG/8AQjh6eNw==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.42.tgz",
+      "integrity": "sha512-Ooy/Bj+mJ1z4jlWcK5Dl6SlPlCgQB9zg1UrTCeY8XagvuWZ4qGPyYEWGkT94HUsRi2hKsXvcs6ThTOjBaJSMfg==",
       "dev": true,
       "optional": true
     },
     "esbuild-linux-64": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.36.tgz",
-      "integrity": "sha512-vFVFS5ve7PuwlfgoWNyRccGDi2QTNkQo/2k5U5ttVD0jRFaMlc8UQee708fOZA6zTCDy5RWsT5MJw3sl2X6KDg==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.42.tgz",
+      "integrity": "sha512-2L0HbzQfbTuemUWfVqNIjOfaTRt9zsvjnme6lnr7/MO9toz/MJ5tZhjqrG6uDWDxhsaHI2/nsDgrv8uEEN2eoA==",
       "dev": true,
       "optional": true
     },
     "esbuild-linux-arm": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.36.tgz",
-      "integrity": "sha512-NhgU4n+NCsYgt7Hy61PCquEz5aevI6VjQvxwBxtxrooXsxt5b2xtOUXYZe04JxqQo+XZk3d1gcr7pbV9MAQ/Lg==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.42.tgz",
+      "integrity": "sha512-STq69yzCMhdRaWnh29UYrLSr/qaWMm/KqwaRF1pMEK7kDiagaXhSL1zQGXbYv94GuGY/zAwzK98+6idCMUOOCg==",
       "dev": true,
       "optional": true
     },
     "esbuild-linux-arm64": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.36.tgz",
-      "integrity": "sha512-24Vq1M7FdpSmaTYuu1w0Hdhiqkbto1I5Pjyi+4Cdw5fJKGlwQuw+hWynTcRI/cOZxBcBpP21gND7W27gHAiftw==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.42.tgz",
+      "integrity": "sha512-c3Ug3e9JpVr8jAcfbhirtpBauLxzYPpycjWulD71CF6ZSY26tvzmXMJYooQ2YKqDY4e/fPu5K8bm7MiXMnyxuA==",
       "dev": true,
       "optional": true
     },
     "esbuild-linux-mips64le": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.36.tgz",
-      "integrity": "sha512-hZUeTXvppJN+5rEz2EjsOFM9F1bZt7/d2FUM1lmQo//rXh1RTFYzhC0txn7WV0/jCC7SvrGRaRz0NMsRPf8SIA==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.42.tgz",
+      "integrity": "sha512-QuvpHGbYlkyXWf2cGm51LBCHx6eUakjaSrRpUqhPwjh/uvNUYvLmz2LgPTTPwCqaKt0iwL+OGVL0tXA5aDbAbg==",
       "dev": true,
       "optional": true
     },
     "esbuild-linux-ppc64le": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.36.tgz",
-      "integrity": "sha512-1Bg3QgzZjO+QtPhP9VeIBhAduHEc2kzU43MzBnMwpLSZ890azr4/A9Dganun8nsqD/1TBcqhId0z4mFDO8FAvg==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.42.tgz",
+      "integrity": "sha512-8ohIVIWDbDT+i7lCx44YCyIRrOW1MYlks9fxTo0ME2LS/fxxdoJBwHWzaDYhjvf8kNpA+MInZvyOEAGoVDrMHg==",
       "dev": true,
       "optional": true
     },
     "esbuild-linux-riscv64": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.36.tgz",
-      "integrity": "sha512-dOE5pt3cOdqEhaufDRzNCHf5BSwxgygVak9UR7PH7KPVHwSTDAZHDoEjblxLqjJYpc5XaU9+gKJ9F8mp9r5I4A==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.42.tgz",
+      "integrity": "sha512-DzDqK3TuoXktPyG1Lwx7vhaF49Onv3eR61KwQyxYo4y5UKTpL3NmuarHSIaSVlTFDDpcIajCDwz5/uwKLLgKiQ==",
       "dev": true,
       "optional": true
     },
     "esbuild-linux-s390x": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.36.tgz",
-      "integrity": "sha512-g4FMdh//BBGTfVHjF6MO7Cz8gqRoDPzXWxRvWkJoGroKA18G9m0wddvPbEqcQf5Tbt2vSc1CIgag7cXwTmoTXg==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.42.tgz",
+      "integrity": "sha512-YFRhPCxl8nb//Wn6SiS5pmtplBi4z9yC2gLrYoYI/tvwuB1jldir9r7JwAGy1Ck4D7sE7wBN9GFtUUX/DLdcEQ==",
       "dev": true,
       "optional": true
     },
     "esbuild-netbsd-64": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.36.tgz",
-      "integrity": "sha512-UB2bVImxkWk4vjnP62ehFNZ73lQY1xcnL5ZNYF3x0AG+j8HgdkNF05v67YJdCIuUJpBuTyCK8LORCYo9onSW+A==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.42.tgz",
+      "integrity": "sha512-QYSD2k+oT9dqB/4eEM9c+7KyNYsIPgzYOSrmfNGDIyJrbT1d+CFVKvnKahDKNJLfOYj8N4MgyFaU9/Ytc6w5Vw==",
       "dev": true,
       "optional": true
     },
     "esbuild-openbsd-64": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.36.tgz",
-      "integrity": "sha512-NvGB2Chf8GxuleXRGk8e9zD3aSdRO5kLt9coTQbCg7WMGXeX471sBgh4kSg8pjx0yTXRt0MlrUDnjVYnetyivg==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.42.tgz",
+      "integrity": "sha512-M2meNVIKWsm2HMY7+TU9AxM7ZVwI9havdsw6m/6EzdXysyCFFSoaTQ/Jg03izjCsK17FsVRHqRe26Llj6x0MNA==",
       "dev": true,
       "optional": true
     },
     "esbuild-sunos-64": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.36.tgz",
-      "integrity": "sha512-VkUZS5ftTSjhRjuRLp+v78auMO3PZBXu6xl4ajomGenEm2/rGuWlhFSjB7YbBNErOchj51Jb2OK8lKAo8qdmsQ==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.42.tgz",
+      "integrity": "sha512-uXV8TAZEw36DkgW8Ak3MpSJs1ofBb3Smkc/6pZ29sCAN1KzCAQzsje4sUwugf+FVicrHvlamCOlFZIXgct+iqQ==",
       "dev": true,
       "optional": true
     },
     "esbuild-windows-32": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.36.tgz",
-      "integrity": "sha512-bIar+A6hdytJjZrDxfMBUSEHHLfx3ynoEZXx/39nxy86pX/w249WZm8Bm0dtOAByAf4Z6qV0LsnTIJHiIqbw0w==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.42.tgz",
+      "integrity": "sha512-4iw/8qWmRICWi9ZOnJJf9sYt6wmtp3hsN4TdI5NqgjfOkBVMxNdM9Vt3626G1Rda9ya2Q0hjQRD9W1o+m6Lz6g==",
       "dev": true,
       "optional": true
     },
     "esbuild-windows-64": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.36.tgz",
-      "integrity": "sha512-+p4MuRZekVChAeueT1Y9LGkxrT5x7YYJxYE8ZOTcEfeUUN43vktSn6hUNsvxzzATrSgq5QqRdllkVBxWZg7KqQ==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.42.tgz",
+      "integrity": "sha512-j3cdK+Y3+a5H0wHKmLGTJcq0+/2mMBHPWkItR3vytp/aUGD/ua/t2BLdfBIzbNN9nLCRL9sywCRpOpFMx3CxzA==",
       "dev": true,
       "optional": true
     },
     "esbuild-windows-arm64": {
-      "version": "0.14.36",
-      "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.36.tgz",
-      "integrity": "sha512-fBB4WlDqV1m18EF/aheGYQkQZHfPHiHJSBYzXIo8yKehek+0BtBwo/4PNwKGJ5T0YK0oc8pBKjgwPbzSrPLb+Q==",
+      "version": "0.14.42",
+      "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.42.tgz",
+      "integrity": "sha512-+lRAARnF+hf8J0mN27ujO+VbhPbDqJ8rCcJKye4y7YZLV6C4n3pTRThAb388k/zqF5uM0lS5O201u0OqoWSicw==",
       "dev": true,
       "optional": true
     },
       "dev": true
     },
     "linkify-it": {
-      "version": "3.0.3",
-      "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz",
-      "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==",
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz",
+      "integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==",
       "requires": {
         "uc.micro": "^1.0.1"
       }
       "dev": true
     },
     "markdown-it": {
-      "version": "12.3.2",
-      "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz",
-      "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==",
+      "version": "13.0.1",
+      "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.1.tgz",
+      "integrity": "sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==",
       "requires": {
         "argparse": "^2.0.1",
-        "entities": "~2.1.0",
-        "linkify-it": "^3.0.1",
+        "entities": "~3.0.1",
+        "linkify-it": "^4.0.1",
         "mdurl": "^1.0.1",
         "uc.micro": "^1.0.5"
       }
       }
     },
     "sass": {
-      "version": "1.50.0",
-      "resolved": "https://registry.npmjs.org/sass/-/sass-1.50.0.tgz",
-      "integrity": "sha512-cLsD6MEZ5URXHStxApajEh7gW189kkjn4Rc8DQweMyF+o5HF5nfEz8QYLMlPsTOD88DknatTmBWkOcw5/LnJLQ==",
+      "version": "1.52.1",
+      "resolved": "https://registry.npmjs.org/sass/-/sass-1.52.1.tgz",
+      "integrity": "sha512-fSzYTbr7z8oQnVJ3Acp9hV80dM1fkMN7mSD/25mpcct9F7FPBMOI8krEYALgU1aZoqGhQNhTPsuSmxjnIvAm4Q==",
       "dev": true,
       "requires": {
         "chokidar": ">=3.0.0 <4.0.0",
         "object-inspect": "^1.9.0"
       }
     },
+    "snabbdom": {
+      "version": "3.5.0",
+      "resolved": "https://registry.npmjs.org/snabbdom/-/snabbdom-3.5.0.tgz",
+      "integrity": "sha512-Ff5BKG18KrrPuskHJlA9aujPHqEabItaDl96l7ZZndF4zt5AYSczz7ZjjgQAX5IBd5cd25lw9NfgX21yVUJ+9g=="
+    },
     "sortablejs": {
       "version": "1.15.0",
       "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.0.tgz",
index b49a2a07f32b7196b7c8d1c503529adfa51eb0b4..9a2f664482a08958a2ae1d3e72fcce593e40c6bd 100644 (file)
@@ -1,8 +1,8 @@
 {
   "private": true,
   "scripts": {
-    "build:css:dev": "sass ./resources/sass:./public/dist",
-    "build:css:watch": "sass ./resources/sass:./public/dist --watch",
+    "build:css:dev": "sass ./resources/sass:./public/dist --embed-sources",
+    "build:css:watch": "sass ./resources/sass:./public/dist --watch --embed-sources",
     "build:css:production": "sass ./resources/sass:./public/dist -s compressed",
     "build:js:dev": "node dev/build/esbuild.js",
     "build:js:watch": "chokidar --initial \"./resources/**/*.js\" -c \"npm run build:js:dev\"",
   },
   "devDependencies": {
     "chokidar-cli": "^3.0",
-    "esbuild": "0.14.36",
+    "esbuild": "0.14.42",
     "livereload": "^0.9.3",
     "npm-run-all": "^4.1.5",
     "punycode": "^2.1.1",
-    "sass": "^1.50.0"
+    "sass": "^1.52.1"
   },
   "dependencies": {
-    "clipboard": "^2.0.10",
-    "codemirror": "^5.65.2",
+    "clipboard": "^2.0.11",
+    "codemirror": "^5.65.5",
     "dropzone": "^5.9.3",
-    "markdown-it": "^12.3.2",
+    "markdown-it": "^13.0.1",
     "markdown-it-task-lists": "^2.1.1",
+    "snabbdom": "^3.5.0",
     "sortablejs": "^1.15.0"
   }
 }
diff --git a/resources/icons/download.svg b/resources/icons/download.svg
new file mode 100644 (file)
index 0000000..6299571
--- /dev/null
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M16.59 9H15V4c0-.55-.45-1-1-1h-4c-.55 0-1 .45-1 1v5H7.41c-.89 0-1.34 1.08-.71 1.71l4.59 4.59c.39.39 1.02.39 1.41 0l4.59-4.59c.63-.63.19-1.71-.7-1.71zM5 19c0 .55.45 1 1 1h12c.55 0 1-.45 1-1s-.45-1-1-1H6c-.55 0-1 .45-1 1z"/></svg>
\ No newline at end of file
index 8e2ed72c89be270ba268d907ac38da6bd1587fce..5d4186dd0527abaa4257edce8e50312bd4c65c7f 100644 (file)
@@ -25,6 +25,7 @@ import 'codemirror/mode/ruby/ruby';
 import 'codemirror/mode/rust/rust';
 import 'codemirror/mode/shell/shell';
 import 'codemirror/mode/sql/sql';
+import 'codemirror/mode/stex/stex';
 import 'codemirror/mode/toml/toml';
 import 'codemirror/mode/vb/vb';
 import 'codemirror/mode/vbscript/vbscript';
@@ -49,16 +50,19 @@ const modeMap = {
     diff: 'diff',
     for: 'fortran',
     fortran: 'fortran',
+    'f#': 'text/x-fsharp',
+    fsharp: 'text/x-fsharp',
     go: 'go',
     haskell: 'haskell',
     hs: 'haskell',
     html: 'htmlmixed',
     ini: 'properties',
-    javascript: 'javascript',
-    json: {name: 'javascript', json: true},
-    js: 'javascript',
-    jl: 'julia',
-    julia: 'julia',
+    javascript: 'text/javascript',
+    json: 'application/json',
+    js: 'text/javascript',
+    jl: 'text/x-julia',
+    julia: 'text/x-julia',
+    latex: 'text/x-stex',
     lua: 'lua',
     md: 'markdown',
     mdown: 'markdown',
@@ -69,7 +73,7 @@ const modeMap = {
     pl: 'perl',
     powershell: 'powershell',
     properties: 'properties',
-    ocaml: 'mllike',
+    ocaml: 'text/x-ocaml',
     pascal: 'text/x-pascal',
     pas: 'text/x-pascal',
     php: (content) => {
@@ -83,8 +87,11 @@ const modeMap = {
     rs: 'rust',
     shell: 'shell',
     sh: 'shell',
+    stext: 'text/x-stex',
     bash: 'shell',
     toml: 'toml',
+    ts: 'text/typescript',
+    typescript: 'text/typescript',
     sql: 'text/x-sql',
     vbs: 'vbscript',
     vbscript: 'vbscript',
@@ -242,6 +249,21 @@ export function popupEditor(elem, modeSuggestion) {
     });
 }
 
+/**
+ * Create an inline editor to replace the given textarea.
+ * @param {HTMLTextAreaElement} textArea
+ * @param {String} mode
+ * @returns {CodeMirror3}
+ */
+export function inlineEditor(textArea, mode) {
+    return CodeMirror.fromTextArea(textArea, {
+        mode: getMode(mode, textArea.value),
+        lineNumbers: true,
+        lineWrapping: false,
+        theme: getTheme(),
+    });
+}
+
 /**
  * Set the mode of a codemirror instance.
  * @param cmInstance
diff --git a/resources/js/components/chapter-contents.js b/resources/js/components/chapter-contents.js
new file mode 100644 (file)
index 0000000..c824d0f
--- /dev/null
@@ -0,0 +1,37 @@
+import {slideUp, slideDown} from "../services/animations";
+
+/**
+ * @extends {Component}
+ */
+class ChapterContents {
+
+    setup() {
+        this.list = this.$refs.list;
+        this.toggle = this.$refs.toggle;
+
+        this.isOpen = this.toggle.classList.contains('open');
+        this.toggle.addEventListener('click', this.click.bind(this));
+    }
+
+    open() {
+        this.toggle.classList.add('open');
+        this.toggle.setAttribute('aria-expanded', 'true');
+        slideDown(this.list, 180);
+        this.isOpen = true;
+    }
+
+    close() {
+        this.toggle.classList.remove('open');
+        this.toggle.setAttribute('aria-expanded', 'false');
+        slideUp(this.list, 180);
+        this.isOpen = false;
+    }
+
+    click(event) {
+        event.preventDefault();
+        this.isOpen ?  this.close() : this.open();
+    }
+
+}
+
+export default ChapterContents;
diff --git a/resources/js/components/chapter-toggle.js b/resources/js/components/chapter-toggle.js
deleted file mode 100644 (file)
index bfd0ac7..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-import {slideUp, slideDown} from "../services/animations";
-
-class ChapterToggle {
-
-    constructor(elem) {
-        this.elem = elem;
-        this.isOpen = elem.classList.contains('open');
-        elem.addEventListener('click', this.click.bind(this));
-    }
-
-    open() {
-        const list = this.elem.parentNode.querySelector('.inset-list');
-        this.elem.classList.add('open');
-        this.elem.setAttribute('aria-expanded', 'true');
-        slideDown(list, 240);
-    }
-
-    close() {
-        const list = this.elem.parentNode.querySelector('.inset-list');
-        this.elem.classList.remove('open');
-        this.elem.setAttribute('aria-expanded', 'false');
-        slideUp(list, 240);
-    }
-
-    click(event) {
-        event.preventDefault();
-        this.isOpen ?  this.close() : this.open();
-        this.isOpen = !this.isOpen;
-    }
-
-}
-
-export default ChapterToggle;
index 4ee3531c58a7da957079a7a9ffb6c46aaaaf9c4c..27ff56395d1af49968014407f77c31d1b96e6c3c 100644 (file)
@@ -33,10 +33,11 @@ class CodeEditor {
         onSelect(this.languageLinks, event => {
             const language = event.target.dataset.lang;
             this.languageInput.value = language;
-            this.updateEditorMode(language);
+            this.languageInputChange(language);
         });
 
         onEnterPress(this.languageInput, e => this.save());
+        this.languageInput.addEventListener('input', e => this.languageInputChange(this.languageInput.value));
         onSelect(this.saveButton, e => this.save());
 
         onChildEvent(this.historyList, 'button', 'click', (event, elem) => {
@@ -60,7 +61,7 @@ class CodeEditor {
         this.callback = callback;
 
         this.show()
-            .then(() => this.updateEditorMode(language))
+            .then(() => this.languageInputChange(language))
             .then(() => window.importVersioned('code'))
             .then(Code => Code.setContent(this.editor, code));
     }
@@ -90,6 +91,22 @@ class CodeEditor {
         Code.setMode(this.editor, language, this.editor.getValue());
     }
 
+    languageInputChange(language) {
+        this.updateEditorMode(language);
+        const inputLang = language.toLowerCase();
+        let matched = false;
+
+        for (const link of this.languageLinks) {
+            const lang = link.dataset.lang.toLowerCase().trim();
+            const isMatch = inputLang && lang.startsWith(inputLang);
+            link.classList.toggle('active', isMatch);
+            if (isMatch && !matched) {
+                link.scrollIntoView({block: "center", behavior: "smooth"});
+                matched = true;
+            }
+        }
+    }
+
     loadHistory() {
         this.history = JSON.parse(window.sessionStorage.getItem(this.historyKey) || '{}');
         const historyKeys = Object.keys(this.history).reverse();
diff --git a/resources/js/components/code-textarea.js b/resources/js/components/code-textarea.js
new file mode 100644 (file)
index 0000000..988e51f
--- /dev/null
@@ -0,0 +1,16 @@
+/**
+ * A simple component to render a code editor within the textarea
+ * this exists upon.
+ * @extends {Component}
+ */
+class CodeTextarea {
+
+    async setup() {
+        const mode = this.$opts.mode;
+        const Code = await window.importVersioned('code');
+        Code.inlineEditor(this.$el, mode);
+    }
+
+}
+
+export default CodeTextarea;
\ No newline at end of file
index e2d55f9694ded7b9d938050f563fad4791a93cb4..81fa940c24ca7cf4a38ba66fe7b55e300d746100 100644 (file)
@@ -1,4 +1,5 @@
 import {debounce} from "../services/util";
+import {transitionHeight} from "../services/animations";
 
 class DropdownSearch {
 
@@ -51,7 +52,9 @@ class DropdownSearch {
 
         try {
             const resp = await window.$http.get(this.getAjaxUrl(searchTerm));
+            const animate = transitionHeight(this.listContainerElem, 80);
             this.listContainerElem.innerHTML = resp.data;
+            animate();
         } catch (err) {
             console.error(err);
         }
index f761ecf011541590963caf66daf4b145f43c8cde..473db37d43f666660d7276e1790a1bc47d101b20 100644 (file)
@@ -28,18 +28,31 @@ class DropDown {
         this.menu.classList.add('anim', 'menuIn');
         this.toggle.setAttribute('aria-expanded', 'true');
 
+        const menuOriginalRect = this.menu.getBoundingClientRect();
+        let heightOffset = 0;
+        const toggleHeight = this.toggle.getBoundingClientRect().height;
+        const dropUpwards = menuOriginalRect.bottom > window.innerHeight;
+
+        // If enabled, Move to body to prevent being trapped within scrollable sections
         if (this.moveMenu) {
-            // Move to body to prevent being trapped within scrollable sections
-            this.rect = this.menu.getBoundingClientRect();
             this.body.appendChild(this.menu);
             this.menu.style.position = 'fixed';
             if (this.direction === 'right') {
-                this.menu.style.right = `${(this.rect.right - this.rect.width)}px`;
+                this.menu.style.right = `${(menuOriginalRect.right - menuOriginalRect.width)}px`;
             } else {
-                this.menu.style.left = `${this.rect.left}px`;
+                this.menu.style.left = `${menuOriginalRect.left}px`;
             }
-            this.menu.style.top = `${this.rect.top}px`;
-            this.menu.style.width = `${this.rect.width}px`;
+            this.menu.style.width = `${menuOriginalRect.width}px`;
+            heightOffset = dropUpwards ? (window.innerHeight - menuOriginalRect.top  - toggleHeight / 2) : menuOriginalRect.top;
+        }
+
+        // Adjust menu to display upwards if near the bottom of the screen
+        if (dropUpwards) {
+            this.menu.style.top = 'initial';
+            this.menu.style.bottom = `${heightOffset}px`;
+        } else {
+            this.menu.style.top = `${heightOffset}px`;
+            this.menu.style.bottom = 'initial';
         }
 
         // Set listener to hide on mouse leave or window click
@@ -74,18 +87,21 @@ class DropDown {
         this.menu.style.display = 'none';
         this.menu.classList.remove('anim', 'menuIn');
         this.toggle.setAttribute('aria-expanded', 'false');
+        this.menu.style.top = '';
+        this.menu.style.bottom = '';
+
         if (this.moveMenu) {
             this.menu.style.position = '';
             this.menu.style[this.direction] = '';
-            this.menu.style.top = '';
             this.menu.style.width = '';
             this.container.appendChild(this.menu);
         }
+
         this.showing = false;
     }
 
     getFocusable() {
-        return Array.from(this.menu.querySelectorAll('[tabindex],[href],button,input:not([type=hidden])'));
+        return Array.from(this.menu.querySelectorAll('[tabindex]:not([tabindex="-1"]),[href],button,input:not([type=hidden])'));
     }
 
     focusNext() {
index 6a4a8c2b08fc9096380fc225f361e28196883150..f360e2b0c2b57e2178e4716df96513a36ed3d220 100644 (file)
@@ -6,9 +6,10 @@ import attachmentsList from "./attachments-list.js"
 import autoSuggest from "./auto-suggest.js"
 import backToTop from "./back-to-top.js"
 import bookSort from "./book-sort.js"
-import chapterToggle from "./chapter-toggle.js"
+import chapterContents from "./chapter-contents.js"
 import codeEditor from "./code-editor.js"
 import codeHighlighter from "./code-highlighter.js"
+import codeTextarea from "./code-textarea.js"
 import collapsible from "./collapsible.js"
 import confirmDialog from "./confirm-dialog"
 import customCheckbox from "./custom-checkbox.js"
@@ -62,9 +63,10 @@ const componentMapping = {
     "auto-suggest": autoSuggest,
     "back-to-top": backToTop,
     "book-sort": bookSort,
-    "chapter-toggle": chapterToggle,
+    "chapter-contents": chapterContents,
     "code-editor": codeEditor,
     "code-highlighter": codeHighlighter,
+    "code-textarea": codeTextarea,
     "collapsible": collapsible,
     "confirm-dialog": confirmDialog,
     "custom-checkbox": customCheckbox,
index 297d9c8ece8bcdae60befb118b59ffc2388165de..21cf37bb4438d745a3dbbf7225773cf8474a6c83 100644 (file)
@@ -2,7 +2,7 @@ import MarkdownIt from "markdown-it";
 import mdTasksLists from 'markdown-it-task-lists';
 import Clipboard from "../services/clipboard";
 import {debounce} from "../services/util";
-
+import {patchDomFromHtmlString} from "../services/vdom";
 import DrawIO from "../services/drawio";
 
 class MarkdownEditor {
@@ -127,18 +127,31 @@ class MarkdownEditor {
     updateAndRender() {
         const content = this.cm.getValue();
         this.input.value = content;
+
         const html = this.markdown.render(content);
         window.$events.emit('editor-html-change', html);
         window.$events.emit('editor-markdown-change', content);
 
         // Set body content
+        const target = this.getDisplayTarget();
         this.displayDoc.body.className = 'page-content';
-        this.displayDoc.body.innerHTML = html;
+        patchDomFromHtmlString(target, html);
 
         // Copy styles from page head and set custom styles for editor
         this.loadStylesIntoDisplay();
     }
 
+    getDisplayTarget() {
+        const body = this.displayDoc.body;
+
+        if (body.children.length === 0) {
+            const wrap = document.createElement('div');
+            this.displayDoc.body.append(wrap);
+        }
+
+        return body.children[0];
+    }
+
     loadStylesIntoDisplay() {
         if (this.displayStylesLoaded) return;
         this.displayDoc.documentElement.classList.add('markdown-editor-display');
index 278a765d5802e2af4feefd1fb1cb26aeaee9b805..12b8077cf7dccec37dc48e6f83a7c6aa1ce43c86 100644 (file)
@@ -49,7 +49,7 @@ export function slideUp(element, animTime = 400) {
     const currentPaddingTop = computedStyles.getPropertyValue('padding-top');
     const currentPaddingBottom = computedStyles.getPropertyValue('padding-bottom');
     const animStyles = {
-        height: [`${currentHeight}px`, '0px'],
+        maxHeight: [`${currentHeight}px`, '0px'],
         overflow: ['hidden', 'hidden'],
         paddingTop: [currentPaddingTop, '0px'],
         paddingBottom: [currentPaddingBottom, '0px'],
@@ -73,7 +73,7 @@ export function slideDown(element, animTime = 400) {
     const targetPaddingTop = computedStyles.getPropertyValue('padding-top');
     const targetPaddingBottom = computedStyles.getPropertyValue('padding-bottom');
     const animStyles = {
-        height: ['0px', `${targetHeight}px`],
+        maxHeight: ['0px', `${targetHeight}px`],
         overflow: ['hidden', 'hidden'],
         paddingTop: ['0px', targetPaddingTop],
         paddingBottom: ['0px', targetPaddingBottom],
@@ -82,6 +82,38 @@ export function slideDown(element, animTime = 400) {
     animateStyles(element, animStyles, animTime);
 }
 
+/**
+ * Transition the height of the given element between two states.
+ * Call with first state, and you'll receive a function in return.
+ * Call the returned function in the second state to animate between those two states.
+ * If animating to/from 0-height use the slide-up/slide down as easier alternatives.
+ * @param {Element} element - Element to animate
+ * @param {Number} animTime - Animation time in ms
+ * @returns {function} - Function to run in second state to trigger animation.
+ */
+export function transitionHeight(element, animTime = 400) {
+    const startHeight = element.getBoundingClientRect().height;
+    const initialComputedStyles = getComputedStyle(element);
+    const startPaddingTop = initialComputedStyles.getPropertyValue('padding-top');
+    const startPaddingBottom = initialComputedStyles.getPropertyValue('padding-bottom');
+
+    return () => {
+        cleanupExistingElementAnimation(element);
+        const targetHeight = element.getBoundingClientRect().height;
+        const computedStyles = getComputedStyle(element);
+        const targetPaddingTop = computedStyles.getPropertyValue('padding-top');
+        const targetPaddingBottom = computedStyles.getPropertyValue('padding-bottom');
+        const animStyles = {
+            height: [`${startHeight}px`, `${targetHeight}px`],
+            overflow: ['hidden', 'hidden'],
+            paddingTop: [startPaddingTop, targetPaddingTop],
+            paddingBottom: [startPaddingBottom, targetPaddingBottom],
+        };
+
+        animateStyles(element, animStyles, animTime);
+    };
+}
+
 /**
  * Animate the css styles of an element using FLIP animation techniques.
  * Styles must be an object where the keys are style properties, camelcase, and the values
diff --git a/resources/js/services/vdom.js b/resources/js/services/vdom.js
new file mode 100644 (file)
index 0000000..89a8090
--- /dev/null
@@ -0,0 +1,31 @@
+import {
+    init,
+    attributesModule,
+    toVNode
+} from "snabbdom";
+
+let patcher;
+
+/**
+ * @returns {Function}
+ */
+function getPatcher() {
+    if (patcher) return patcher;
+
+
+    patcher = init([
+        attributesModule,
+    ]);
+
+    return patcher;
+}
+
+/**
+ * @param {Element} domTarget
+ * @param {String} html
+ */
+export function patchDomFromHtmlString(domTarget, html) {
+    const contentDom = document.createElement('div');
+    contentDom.innerHTML = html;
+    getPatcher()(toVNode(domTarget), toVNode(contentDom));
+}
\ No newline at end of file
index ace74dd334e03c8a17bcfbf527ae3d178ac44295..0529f6b6cdd110f00335b3a5c2785df608e7fe71 100644 (file)
@@ -60,13 +60,7 @@ async function uploadImageFile(file, pageId) {
         throw new Error(`Not an image file`);
     }
 
-    let ext = 'png';
-    if (file.name) {
-        let fileNameMatches = file.name.match(/\.(.+)$/);
-        if (fileNameMatches.length > 1) ext = fileNameMatches[1];
-    }
-
-    const remoteFilename = "image-" + Date.now() + "." + ext;
+    const remoteFilename = file.name || `image-${Date.now()}.png`;
     const formData = new FormData();
     formData.append('file', file, remoteFilename);
     formData.append('uploaded_to', pageId);
index 82ab7d3c8eebad8327a842ef208c575e6451ded0..4123fb783a96cec3694d8366af5111a987820ed5 100644 (file)
@@ -86,7 +86,13 @@ function defineCodeBlockCustomElement(editor) {
         getContent() {
             const code = this.querySelector('code') || this.querySelector('pre');
             const tempEl = document.createElement('pre');
-            tempEl.innerHTML = code.innerHTML.replace().replace(/<br\s*[\/]?>/gi ,'\n').replace(/\ufeff/g, '');
+            tempEl.innerHTML = code.innerHTML.replace(/\ufeff/g, '');
+
+            const brs = tempEl.querySelectorAll('br');
+            for (const br of brs) {
+                br.replaceWith('\n');
+            }
+
             return tempEl.textContent;
         }
 
@@ -104,6 +110,7 @@ function defineCodeBlockCustomElement(editor) {
 
             const container = this.shadowRoot.querySelector('.CodeMirrorContainer');
             const renderCodeMirror = (Code) => {
+                console.log({content});
                 this.cm = Code.wysiwygView(container, content, this.getLanguage());
                 Code.updateLayout(this.cm);
                 setTimeout(() => {
@@ -159,6 +166,7 @@ function register(editor, url) {
             showPopup(editor, textContent, '', (newCode, newLang) => {
                 const pre = doc.createElement('pre');
                 const code = doc.createElement('code');
+                console.log(newCode);
                 code.classList.add(`language-${newLang}`);
                 code.innerText = newCode;
                 pre.append(code);
index 55c4713be15b39ea8974fae6ab1a6e4a7128a2bb..678fb1412681d1258bca3b9e7be053707ed8677f 100644 (file)
@@ -7,61 +7,61 @@ return [
 
     // Pages
     'page_create'                 => 'تم إنشاء صفحة',
-    'page_create_notification'    => 'Page successfully created',
+    'page_create_notification'    => 'تم إنشاء الصفحة بنجاح',
     'page_update'                 => 'تم تحديث الصفحة',
-    'page_update_notification'    => 'Page successfully updated',
+    'page_update_notification'    => 'تم تحديث الصفحة بنجاح',
     'page_delete'                 => 'تم حذف الصفحة',
-    'page_delete_notification'    => 'Page successfully deleted',
+    'page_delete_notification'    => 'تم حذف الصفحة بنجاح',
     'page_restore'                => 'تمت استعادة الصفحة',
-    'page_restore_notification'   => 'Page successfully restored',
+    'page_restore_notification'   => 'تمت استعادة الصفحة بنجاح',
     'page_move'                   => 'تم نقل الصفحة',
 
     // Chapters
     'chapter_create'              => 'تم إنشاء فصل',
-    'chapter_create_notification' => 'Chapter successfully created',
+    'chapter_create_notification' => 'تم إنشاء الفصل بنجاح',
     'chapter_update'              => 'تم تحديث الفصل',
-    'chapter_update_notification' => 'Chapter successfully updated',
+    'chapter_update_notification' => 'تم تحديث الفصل بنجاح',
     'chapter_delete'              => 'تم حذف الفصل',
-    'chapter_delete_notification' => 'Chapter successfully deleted',
+    'chapter_delete_notification' => 'تم حذف الفصل بنجاح',
     'chapter_move'                => 'تم نقل الفصل',
 
     // Books
     'book_create'                 => 'تم إنشاء كتاب',
-    'book_create_notification'    => 'Book successfully created',
+    'book_create_notification'    => 'تم إنشاء الكتاب بنجاح',
     'book_update'                 => 'تم تحديث الكتاب',
-    'book_update_notification'    => 'Book successfully updated',
+    'book_update_notification'    => 'تم تحديث الكتاب بنجاح',
     'book_delete'                 => 'تم حذف الكتاب',
-    'book_delete_notification'    => 'Book successfully deleted',
+    'book_delete_notification'    => 'تم حذف الكتاب بنجاح',
     'book_sort'                   => 'تم سرد الكتاب',
-    'book_sort_notification'      => 'Book successfully re-sorted',
+    'book_sort_notification'      => 'تم إعادة فرز الكتاب بنجاح',
 
     // Bookshelves
-    'bookshelf_create'            => 'created bookshelf',
-    'bookshelf_create_notification'    => 'Bookshelf successfully created',
+    'bookshelf_create'            => 'تم إنشاء رف كتب',
+    'bookshelf_create_notification'    => 'تم إنشاء الرف بنجاح',
     'bookshelf_update'                 => 'تم تحديث الرف',
-    'bookshelf_update_notification'    => 'Bookshelf successfully updated',
+    'bookshelf_update_notification'    => 'تم تحديث الرف بنجاح',
     'bookshelf_delete'                 => 'تم تحديث الرف',
-    'bookshelf_delete_notification'    => 'Bookshelf successfully deleted',
+    'bookshelf_delete_notification'    => 'تم حذف الرف بنجاح',
 
     // Favourites
-    'favourite_add_notification' => '":name" has been added to your favourites',
-    'favourite_remove_notification' => '":name" has been removed from your favourites',
+    'favourite_add_notification' => 'تم إضافة ":name" إلى المفضلة لديك',
+    'favourite_remove_notification' => 'تم إزالة ":name" من المفضلة لديك',
 
     // MFA
-    'mfa_setup_method_notification' => 'Multi-factor method successfully configured',
-    'mfa_remove_method_notification' => 'Multi-factor method successfully removed',
+    'mfa_setup_method_notification' => 'تم تكوين طريقة متعددة العوامل بنجاح',
+    'mfa_remove_method_notification' => 'تمت إزالة طريقة متعددة العوامل بنجاح',
 
     // Webhooks
-    'webhook_create' => 'created webhook',
-    'webhook_create_notification' => 'Webhook successfully created',
-    'webhook_update' => 'updated webhook',
-    'webhook_update_notification' => 'Webhook successfully updated',
-    'webhook_delete' => 'deleted webhook',
-    'webhook_delete_notification' => 'Webhook successfully deleted',
+    'webhook_create' => 'تم إنشاء webhook',
+    'webhook_create_notification' => 'تم إنشاء Webhook بنجاح',
+    'webhook_update' => 'تم تحديث webhook',
+    'webhook_update_notification' => 'تم تحديث Webhook بنجاح',
+    'webhook_delete' => 'حذف webhook',
+    'webhook_delete_notification' => 'تم حذف Webhook بنجاح',
 
     // Users
-    'user_update_notification' => 'User successfully updated',
-    'user_delete_notification' => 'User successfully removed',
+    'user_update_notification' => 'تم تحديث المستخدم بنجاح',
+    'user_delete_notification' => 'تم إزالة المستخدم بنجاح',
 
     // Other
     'commented_on'                => 'تم التعليق',
index 183e0b0b75f92309c3912fb062cef7d0fdd576ee..093e875bf8dbb1dcf294e8c0794ef06e1a37f551 100755 (executable)
@@ -279,6 +279,7 @@ return [
         'es_AR' => 'Español Argentina',
         'et' => 'Eesti keel',
         'eu' => 'Euskara',
+        'fa' => 'فارسی',
         'fr' => 'Français',
         'he' => 'עברית',
         'hr' => 'Hrvatski',
index cd004be6543acd8f920fc40f615f9bbc04bb223e..7b467fe45acc6caffb38202fd7db126c0cf43de8 100644 (file)
@@ -279,6 +279,7 @@ return [
         'es_AR' => 'Español Argentina',
         'et' => 'Eesti keel',
         'eu' => 'Euskara',
+        'fa' => 'فارسی',
         'fr' => 'Français',
         'he' => 'עברית',
         'hr' => 'Hrvatski',
index af2dcc1e1655bab7208832cb6e090a578f2e931f..3bfe70bc4cd556934240e8a78d2cc2d7ba41e542 100644 (file)
@@ -279,6 +279,7 @@ return [
         'es_AR' => 'Español Argentina',
         'et' => 'Eesti keel',
         'eu' => 'Euskara',
+        'fa' => 'فارسی',
         'fr' => 'Français',
         'he' => 'עברית',
         'hr' => 'Hrvatski',
index a4dd95323224bd2fbdde322e224c9270f4c08cbe..d5185a4dbef3b6ee6e8dfa95fe6d684719881e03 100755 (executable)
@@ -279,6 +279,7 @@ return [
         'es_AR' => 'Español Argentina',
         'et' => 'Eesti keel',
         'eu' => 'Euskara',
+        'fa' => 'فارسی',
         'fr' => 'Français',
         'he' => 'עברית',
         'hr' => 'Hrvatski',
index 7da021da40d304de7fd09f60ac884650f5ea30d1..cf1e0029a4f97dd6867aa50a0678d53f9c58264c 100644 (file)
@@ -279,6 +279,7 @@ return [
         'es_AR' => 'Español Argentina',
         'et' => 'Eesti keel',
         'eu' => 'Euskara',
+        'fa' => 'فارسی',
         'fr' => 'Français',
         'he' => 'עברית',
         'hr' => 'Hrvatski',
index c3f3339461ca71558b02679e8104aa6869418e39..2d3c5522eee35bd35e9fccde7e19a4e5aabe8aac 100644 (file)
@@ -279,6 +279,7 @@ return [
         'es_AR' => 'Español Argentina',
         'et' => 'Eesti keel',
         'eu' => 'Euskara',
+        'fa' => 'فارسی',
         'fr' => 'Français',
         'he' => 'Hebraisk',
         'hr' => 'Hrvatski',
index d7ef9dc9960a792f28f1568335cf0fa5c4a93e10..fac489dfbe20bb65c8aa136b4eb88d65fa8464de 100644 (file)
@@ -282,6 +282,7 @@ Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung 
         'es_AR' => 'Spanisch Argentinisch',
         'et' => 'Estnisch',
         'eu' => 'Euskara',
+        'fa' => 'فارسی',
         'fr' => 'Französisch',
         'he' => 'Hebräisch',
         'hr' => 'Kroatisch',
index 1968e547acf16b89a3640da9b9f3e3924a786060..25b0b6bd17727a6954e9c815c7be85e3319fb52a 100644 (file)
@@ -282,6 +282,7 @@ Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung 
         'es_AR' => 'Spanisch Argentinisch',
         'et' => 'Estnisch',
         'eu' => 'Euskara',
+        'fa' => 'فارسی',
         'fr' => 'Französisch',
         'he' => 'עברית',
         'hr' => 'Kroatisch',
index 77c39b50c8c3be5cb6110c074b016ef9aca122e8..edddf9aebcc6a4cf9c3e70f6b6ea4ebf98d646af 100644 (file)
@@ -28,6 +28,8 @@ return [
     // Books
     'book_create'                 => 'created book',
     'book_create_notification'    => 'Book successfully created',
+    'book_create_from_chapter'              => 'converted chapter to book',
+    'book_create_from_chapter_notification' => 'Chapter successfully converted to a book',
     'book_update'                 => 'updated book',
     'book_update_notification'    => 'Book successfully updated',
     'book_delete'                 => 'deleted book',
@@ -38,6 +40,8 @@ return [
     // Bookshelves
     'bookshelf_create'            => 'created bookshelf',
     'bookshelf_create_notification'    => 'Bookshelf successfully created',
+    'bookshelf_create_from_book'    => 'converted book to bookshelf',
+    'bookshelf_create_from_book_notification'    => 'Book successfully converted to a shelf',
     'bookshelf_update'                 => 'updated bookshelf',
     'bookshelf_update_notification'    => 'Bookshelf successfully updated',
     'bookshelf_delete'                 => 'deleted bookshelf',
index 2f09e53d1fe88389e923863d875c43e8d2713d4a..703a70c7e6aa5bea2b59f7319d4739c5f7162c13 100644 (file)
@@ -47,6 +47,8 @@ return [
     'previous' => 'Previous',
     'filter_active' => 'Active Filter:',
     'filter_clear' => 'Clear Filter',
+    'download' => 'Download',
+    'open_in_tab' => 'Open in Tab',
 
     // Sort Options
     'sort_options' => 'Sort Options',
index bed781b6129b180789556b43597883ba5671da21..27d67487a0b0cb3aaffe6adb71c70d10f11c73e5 100644 (file)
@@ -355,4 +355,16 @@ return [
     'copy_consider_images' => 'Page image files will not be duplicated & the original images will retain their relation to the page they were originally uploaded to.',
     'copy_consider_attachments' => 'Page attachments will not be copied.',
     'copy_consider_access' => 'A change of location, owner or permissions may result in this content being accessible to those previously without access.',
+
+    // Conversions
+    'convert_to_shelf' => 'Convert to Shelf',
+    'convert_to_shelf_contents_desc' => 'You can convert this book to a new shelf with the same contents. Chapters contained within this book will be converted to new books. If this book contains any pages, that are not in a chapter, this book will be renamed and contain such pages, and this book will become part of the new shelf.',
+    'convert_to_shelf_permissions_desc' => 'Any permissions set on this book will be copied to the new shelf and to all new child books that don\'t have their own permissions enforced. Note that permissions on shelves do not auto-cascade to content within, as they do for books.',
+    'convert_book' => 'Convert Book',
+    'convert_book_confirm' => 'Are you sure you want to convert this book?',
+    'convert_undo_warning' => 'This cannot be as easily undone.',
+    'convert_to_book' => 'Convert to Book',
+    'convert_to_book_desc' => 'You can convert this chapter to a new book with the same contents. Any permissions set on this chapter will be copied to the new book but any inherited permissions, from the parent book, will not be copied which could lead to a change of access control.',
+    'convert_chapter' => 'Convert Chapter',
+    'convert_chapter_confirm' => 'Are you sure you want to convert this chapter?',
 ];
index af2dcc1e1655bab7208832cb6e090a578f2e931f..3bfe70bc4cd556934240e8a78d2cc2d7ba41e542 100755 (executable)
@@ -279,6 +279,7 @@ return [
         'es_AR' => 'Español Argentina',
         'et' => 'Eesti keel',
         'eu' => 'Euskara',
+        'fa' => 'فارسی',
         'fr' => 'Français',
         'he' => 'עברית',
         'hr' => 'Hrvatski',
index 3e27359e3122c952d5907bde951318fc7434eb3d..e81e2a1f6e2322d611e3e2b32f8f61f4403e7f4f 100644 (file)
@@ -279,6 +279,7 @@ return [
         'es_AR' => 'Español Argentina',
         'et' => 'Eesti keel',
         'eu' => 'Euskara',
+        'fa' => 'فارسی',
         'fr' => 'Francés',
         'he' => 'עברית',
         'hr' => 'Croata',
index 133f91efa9a94a7d70ec037bf31fa18360a4c1f6..5f65d0a6dd5dac8216ae364726860f8d7a5e7adf 100644 (file)
@@ -280,6 +280,7 @@ return [
         'es_AR' => 'Español Argentina',
         'et' => 'Eesti keel',
         'eu' => 'Euskara',
+        'fa' => 'فارسی',
         'fr' => 'Francés',
         'he' => 'עברית',
         'hr' => 'Croata',
index 90727eef51b06ac825e8fec24daf721ea868470b..7014ef145ea791a6c162b4bd8b1ede37565f5a55 100644 (file)
@@ -279,6 +279,7 @@ return [
         'es_AR' => 'Español Argentina (Argentiina hispaania keel)',
         'et' => 'Eesti keel',
         'eu' => 'Euskara',
+        'fa' => 'فارسی',
         'fr' => 'Français (prantsuse keel)',
         'he' => 'עברית (heebrea keel)',
         'hr' => 'Hrvatski (horvaadi keel)',
index dbac076c8595ca1f48e5633e67bbbec848f95c59..8ac7b0e865f24a63336a05c1e19085dc8413b9e1 100644 (file)
@@ -279,6 +279,7 @@ return [
         'es_AR' => 'Español Argentina',
         'et' => 'Eesti keel',
         'eu' => 'Euskara',
+        'fa' => 'فارسی',
         'fr' => 'Français',
         'he' => 'עברית',
         'hr' => 'Hrvatski',
index dc599fd7ad552a6dcaf2c993e826b232ce278b1e..c1211e92fd9076d21a1d940614a5ccd791e284f5 100644 (file)
@@ -60,8 +60,8 @@ return [
     'webhook_delete_notification' => 'وب هوک با موفقیت حذف شد',
 
     // Users
-    'user_update_notification' => 'User successfully updated',
-    'user_delete_notification' => 'User successfully removed',
+    'user_update_notification' => 'کاربر با موفقیت به روز شد',
+    'user_delete_notification' => 'کاربر با موفقیت حذف شد',
 
     // Other
     'commented_on'                => 'ثبت دیدگاه',
index 0e4a5b6bf10cadc98dafaf0a59ad216187996975..8dbe8d7c807c1f9b578f1aae82844b92ae28fce2 100644 (file)
@@ -75,7 +75,7 @@ return [
     'status_active' => 'فعال',
     'status_inactive' => 'غیر فعال',
     'never' => 'هرگز',
-    'none' => 'None',
+    'none' => 'هیچکدام',
 
     // Header
     'header_menu_expand' => 'گسترش منو',
index 2eab19661f858b18a23b00c776716f027362f56f..eb6115468ed746e9718f538cca443d493ff011c0 100644 (file)
@@ -196,19 +196,19 @@ return [
     'pages_edit_draft_save_at' => 'پیش نویس ذخیره شده در',
     'pages_edit_delete_draft' => 'حذف پیش نویس',
     'pages_edit_discard_draft' => 'دور انداختن پیش نویس',
-    'pages_edit_switch_to_markdown' => 'Switch to Markdown Editor',
-    'pages_edit_switch_to_markdown_clean' => '(Clean Content)',
-    'pages_edit_switch_to_markdown_stable' => '(Stable Content)',
-    'pages_edit_switch_to_wysiwyg' => 'Switch to WYSIWYG Editor',
+    'pages_edit_switch_to_markdown' => 'به ویرایشگر Markdown بروید',
+    'pages_edit_switch_to_markdown_clean' => '(مطالب تمیز)',
+    'pages_edit_switch_to_markdown_stable' => '(محتوای پایدار)',
+    'pages_edit_switch_to_wysiwyg' => 'به ویرایشگر WYSIWYG بروید',
     'pages_edit_set_changelog' => 'تنظیم تغییرات',
     'pages_edit_enter_changelog_desc' => 'توضیح مختصری از تغییراتی که ایجاد کرده اید وارد کنید',
     'pages_edit_enter_changelog' => 'وارد کردن تغییرات',
-    'pages_editor_switch_title' => 'Switch Editor',
-    'pages_editor_switch_are_you_sure' => 'Are you sure you want to change the editor for this page?',
-    'pages_editor_switch_consider_following' => 'Consider the following when changing editors:',
-    'pages_editor_switch_consideration_a' => 'Once saved, the new editor option will be used by any future editors, including those that may not be able to change editor type themselves.',
-    'pages_editor_switch_consideration_b' => 'This can potentially lead to a loss of detail and syntax in certain circumstances.',
-    'pages_editor_switch_consideration_c' => 'Tag or changelog changes, made since last save, won\'t persist across this change.',
+    'pages_editor_switch_title' => 'ویرایشگر را تغییر دهید',
+    'pages_editor_switch_are_you_sure' => 'آیا مطمئن هستید که می خواهید ویرایشگر این صفحه را تغییر دهید؟',
+    'pages_editor_switch_consider_following' => 'هنگام تغییر ویرایشگر موارد زیر را در نظر بگیرید:',
+    'pages_editor_switch_consideration_a' => 'پس از ذخیره، گزینه ویرایشگر جدید توسط هر ویرایشگر آینده، از جمله ویرایشگرانی که ممکن است خودشان نتوانند نوع ویرایشگر را تغییر دهند، استفاده خواهد شد.',
+    'pages_editor_switch_consideration_b' => 'این به طور بالقوه می تواند منجر به از دست دادن جزئیات و نحو در شرایط خاص شود.',
+    'pages_editor_switch_consideration_c' => 'تغییرات برچسب‌ها یا تغییرات ثبت شده از آخرین ذخیره‌سازی انجام شده، در این تغییر باقی نمی‌مانند.',
     'pages_save' => 'ذخیره صفحه',
     'pages_title' => 'عنوان صفحه',
     'pages_name' => 'نام صفحه',
@@ -235,7 +235,7 @@ return [
     'pages_revisions_number' => '#',
     'pages_revisions_numbered' => 'تجدید نظر #:id',
     'pages_revisions_numbered_changes' => 'بازبینی #:id تغییرات',
-    'pages_revisions_editor' => 'Editor Type',
+    'pages_revisions_editor' => 'نوع ویرایشگر',
     'pages_revisions_changelog' => 'لیست تغییرات',
     'pages_revisions_changes' => 'تغییرات',
     'pages_revisions_current' => 'نسخه‌ی جاری',
index 5ab82290d8ebb16fe004de43098704ea49821085..a55b86d7deb05b6ea751bccf88869c9f831e8445 100644 (file)
@@ -10,8 +10,8 @@ return [
     'settings' => 'تنظیمات',
     'settings_save' => 'تنظیمات را ذخیره کن',
     'settings_save_success' => 'تنظیمات ذخیره شد',
-    'system_version' => 'System Version',
-    'categories' => 'Categories',
+    'system_version' => 'نسخه سیستم',
+    'categories' => 'دسته بندی ها',
 
     // App Settings
     'app_customization' => 'سفارشی سازی',
@@ -27,7 +27,7 @@ return [
     'app_secure_images' => 'آپلود تصویر با امنیت بالاتر',
     'app_secure_images_toggle' => 'آپلود تصویر با امنیت بالاتر',
     'app_secure_images_desc' => 'به دلایل عملکرد، همه تصاویر عمومی هستند. این گزینه یک رشته تصادفی و غیرقابل حدس زدن را در مقابل آدرس های تصویر اضافه می کند. برای جلوگیری از دسترسی آسان، اطمینان حاصل کنید که فهرست های دایرکتوری فعال نیستند.',
-    'app_default_editor' => 'Default Page Editor',
+    'app_default_editor' => 'ویرایشگر پیش فرض صفحه',
     'app_default_editor_desc' => 'Select which editor will be used by default when editing new pages. This can be overridden at a page level where permissions allow.',
     'app_custom_html' => 'محتوای اصلی HTML سفارشی',
     'app_custom_html_desc' => 'هر محتوای اضافه شده در اینجا در پایین بخش <head> هر صفحه درج می شود. این برای تغییر سبک ها یا اضافه کردن کد تجزیه و تحلیل مفید است.',
@@ -279,6 +279,7 @@ return [
         'es_AR' => 'Español Argentina',
         'et' => 'Eesti keel',
         'eu' => 'Euskara',
+        'fa' => 'فارسی',
         'fr' => 'Français',
         'he' => 'עברית',
         'hr' => 'Hrvatski',
index 1c57ad4c0f53e3042d257b224489c1b6a8a7601e..f8858d692f57e9ff39ce0074cf12eec7d8ae222a 100644 (file)
@@ -279,6 +279,7 @@ return [
         'es_AR' => 'Espagnol (Argentine)',
         'et' => 'Estonien',
         'eu' => 'Euskara',
+        'fa' => 'فارسی',
         'fr' => 'Français',
         'he' => 'Hébreu',
         'hr' => 'Croate',
index e9cf47b279f49a71f9dadf9a4e54e42e93934a42..6dddc5a002a23eb3f828208309c30a54163923a1 100755 (executable)
@@ -279,6 +279,7 @@ return [
         'es_AR' => 'Español Argentina',
         'et' => 'Eesti keel',
         'eu' => 'Euskara',
+        'fa' => 'فارسی',
         'fr' => 'Français',
         'he' => 'עברית',
         'hr' => 'Hrvatski',
index cc7c5c89b713e3c7963637732d3f0495f712f896..db5ad5404e833bf4654f71ea7ab62ab6cb147ca4 100644 (file)
@@ -279,6 +279,7 @@ return [
         'es_AR' => 'Español Argentina',
         'et' => 'Eesti keel',
         'eu' => 'Euskara',
+        'fa' => 'فارسی',
         'fr' => 'Français',
         'he' => 'עברית',
         'hr' => 'Hrvatski',
index 5cd15c4877e286f536a39af1010c397ce9aea647..e52fe17c493559732c452b21de35e3c64d5b1d59 100644 (file)
@@ -279,6 +279,7 @@ return [
         'es_AR' => 'Español Argentina',
         'et' => 'Eesti keel',
         'eu' => 'Euskara',
+        'fa' => 'فارسی',
         'fr' => 'Français',
         'he' => 'עברית',
         'hr' => 'Hrvatski',
index 0f53c758c44abac32408ed2d5774cf839bf8563a..4b931b54f10681bd288b1f196ed4448becf44258 100644 (file)
@@ -279,6 +279,7 @@ return [
         'es_AR' => 'Español Argentina',
         'et' => 'Eesti keel',
         'eu' => 'Euskara',
+        'fa' => 'فارسی',
         'fr' => 'Français',
         'he' => 'עברית',
         'hr' => 'Hrvatski',
index ab4286d721b3cfa2b1d25f62d65508047f89c6a6..e37777928dc60c13643d82f78688d531e3c71e17 100755 (executable)
@@ -196,19 +196,19 @@ return [
     'pages_edit_draft_save_at' => 'Bozza salvata alle ',
     'pages_edit_delete_draft' => 'Elimina Bozza',
     'pages_edit_discard_draft' => 'Scarta Bozza',
-    'pages_edit_switch_to_markdown' => 'Switch to Markdown Editor',
-    'pages_edit_switch_to_markdown_clean' => '(Clean Content)',
-    'pages_edit_switch_to_markdown_stable' => '(Stable Content)',
-    'pages_edit_switch_to_wysiwyg' => 'Switch to WYSIWYG Editor',
+    'pages_edit_switch_to_markdown' => 'Passa all\'editor Markdown',
+    'pages_edit_switch_to_markdown_clean' => '(Contenuto Chiaro)',
+    'pages_edit_switch_to_markdown_stable' => '(Contenuto Stabile)',
+    'pages_edit_switch_to_wysiwyg' => 'Passa all\'editor WYSIWYG',
     'pages_edit_set_changelog' => 'Imposta Changelog',
     'pages_edit_enter_changelog_desc' => 'Inserisci una breve descrizione dei cambiamenti che hai apportato',
     'pages_edit_enter_changelog' => 'Inserisci Changelog',
-    'pages_editor_switch_title' => 'Switch Editor',
-    'pages_editor_switch_are_you_sure' => 'Are you sure you want to change the editor for this page?',
-    'pages_editor_switch_consider_following' => 'Consider the following when changing editors:',
-    'pages_editor_switch_consideration_a' => 'Once saved, the new editor option will be used by any future editors, including those that may not be able to change editor type themselves.',
-    'pages_editor_switch_consideration_b' => 'This can potentially lead to a loss of detail and syntax in certain circumstances.',
-    'pages_editor_switch_consideration_c' => 'Tag or changelog changes, made since last save, won\'t persist across this change.',
+    'pages_editor_switch_title' => 'Cambia Editor',
+    'pages_editor_switch_are_you_sure' => 'Sei sicuro di voler cambiare l\'editor di questa pagina?',
+    'pages_editor_switch_consider_following' => 'Considerare quanto segue quando si cambia editor:',
+    'pages_editor_switch_consideration_a' => 'Una volta salvata, la nuova opzione di editor sarà utilizzata da qualsiasi editor futuro, inclusi quelli che potrebbero non essere in grado di cambiare il tipo di editor da solo.',
+    'pages_editor_switch_consideration_b' => 'Ciò può potenzialmente portare a una perdita di dettagli e sintassi in determinate circostanze.',
+    'pages_editor_switch_consideration_c' => 'Le modifiche al tag o al changelog, fatte dall\'ultimo salvataggio, non persisteranno in questa modifica.',
     'pages_save' => 'Salva Pagina',
     'pages_title' => 'Titolo Pagina',
     'pages_name' => 'Nome Pagina',
@@ -235,7 +235,7 @@ return [
     'pages_revisions_number' => '#',
     'pages_revisions_numbered' => 'Revisione #:id',
     'pages_revisions_numbered_changes' => 'Modifiche Revisione #:id',
-    'pages_revisions_editor' => 'Editor Type',
+    'pages_revisions_editor' => 'Tipo Di Editor',
     'pages_revisions_changelog' => 'Cambiamenti',
     'pages_revisions_changes' => 'Cambiamenti',
     'pages_revisions_current' => 'Versione Corrente',
index 94f2de6f76e88a6e50bec5cf0f5486d6851eb6cd..c3425bcf59939a9ad4d1e148df1c9d90305d28f0 100755 (executable)
@@ -27,8 +27,8 @@ return [
     'app_secure_images' => 'Abilitare una sicurezza maggiore per le immagini caricate?',
     'app_secure_images_toggle' => 'Abilita sicurezza aggiuntiva negli upload delle immagini',
     'app_secure_images_desc' => 'Per una ragione di prestazioni, tutte le immagini sono pubbliche. Questa opzione aaggiunge una stringa, difficile da indovinare, random negli url delle immagini. Assicurati che il listing delle cartelle non sia abilitato per prevenire un accesso semplice.',
-    'app_default_editor' => 'Default Page Editor',
-    'app_default_editor_desc' => 'Select which editor will be used by default when editing new pages. This can be overridden at a page level where permissions allow.',
+    'app_default_editor' => 'Editor Di Pagina Predefinito',
+    'app_default_editor_desc' => 'Seleziona quale editor sarà usato per impostazione predefinita quando modifichi nuove pagine. Questa impostazione potrà essere sovrascritta a livello di pagina dove i permessi lo permettano.',
     'app_custom_html' => 'Contenuto Head HTML Custom',
     'app_custom_html_desc' => 'Qualsiasi contenuto aggiunto qui verrà inserito alla fine della sezione <head> di tutte le pagine. Questo è utile per sovrascrivere lo stile o aggiungere il codice per gli analytics.',
     'app_custom_html_disabled_notice' => 'Il contenuto HTML personalizzato è disabilitato su questa pagina impostazioni per garantire che eventuali modifiche possano essere ripristinate.',
@@ -152,7 +152,7 @@ return [
     'role_access_api' => 'API sistema d\'accesso',
     'role_manage_settings' => 'Gestire impostazioni app',
     'role_export_content' => 'Esporta contenuto',
-    'role_editor_change' => 'Change page editor',
+    'role_editor_change' => 'Cambia editor di pagina',
     'role_asset' => 'Permessi Entità',
     'roles_system_warning' => 'Siate consapevoli che l\'accesso a uno dei tre permessi qui sopra, può consentire a un utente di modificare i propri privilegi o i privilegi di altri nel sistema. Assegna ruoli con questi permessi solo ad utenti fidati.',
     'role_asset_desc' => 'Questi permessi controllano l\'accesso di default alle entità. I permessi nei Libri, Capitoli e Pagine sovrascriveranno questi.',
@@ -279,6 +279,7 @@ return [
         'es_AR' => 'Spagnolo d\'Argentina',
         'et' => 'Estone',
         'eu' => 'Euskara',
+        'fa' => 'فارسی',
         'fr' => 'Francese',
         'he' => 'Ebraico',
         'hr' => 'Croato',
index 75d6434072896ee5e6fa6153fe58f0f5dd37d5a0..427d9eb711d329d313ea4e4120db117b48243140 100644 (file)
@@ -279,6 +279,7 @@ return [
         'es_AR' => 'Español Argentina',
         'et' => 'Eesti keel',
         'eu' => 'Euskara',
+        'fa' => 'فارسی',
         'fr' => 'Français',
         'he' => 'עברית',
         'hr' => 'Hrvatski',
index 4af7aee2bbc150029f799f4429635fec6a2c5d30..4742a933ae82ab8c1d34ae1a50b9de1d021fb3f0 100755 (executable)
@@ -279,6 +279,7 @@ return [
         'es_AR' => 'Español Argentina',
         'et' => 'Eesti keel',
         'eu' => 'Euskara',
+        'fa' => 'فارسی',
         'fr' => 'Français',
         'he' => '히브리어',
         'hr' => 'Hrvatski',
index 9032dc38001348833baeb920b3cf63cf8b2ce221..cd44eff673d6339ed456a0dd12fbc764b1b24fe0 100644 (file)
@@ -279,6 +279,7 @@ return [
         'es_AR' => 'Español Argentina',
         'et' => 'Eesti keel',
         'eu' => 'Euskara',
+        'fa' => 'فارسی',
         'fr' => 'Français',
         'he' => 'עברית',
         'hr' => 'Hrvatski',
index e63d420004a593a0f8439f34e932d7d3e5fa7ca4..9bd3fd5e91353f4b49b579c58122d43aee0a1b62 100644 (file)
  */
 return [
     // General editor terms
-    'general' => 'General',
-    'advanced' => 'Advanced',
-    'none' => 'None',
+    'general' => 'Vispārīgi',
+    'advanced' => 'Papildu iespējas',
+    'none' => 'Neviens',
     'cancel' => 'Atcelt',
     'save' => 'Saglabāt',
     'close' => 'Aizvērt',
-    'undo' => 'Undo',
-    'redo' => 'Redo',
-    'left' => 'Left',
-    'center' => 'Center',
-    'right' => 'Right',
-    'top' => 'Top',
-    'middle' => 'Middle',
-    'bottom' => 'Bottom',
+    'undo' => 'Atsaukt',
+    'redo' => 'Atcelt atsaukšanu',
+    'left' => 'Pa kreisi',
+    'center' => 'Cent',
+    'right' => 'Pa labi',
+    'top' => 'Augšā',
+    'middle' => 'Vidū',
+    'bottom' => 'Apakšā',
     'width' => 'Platums',
     'height' => 'Augstums',
     'More' => 'Vairāk',
-    'select' => 'Select...',
+    'select' => 'Atlasīt...',
 
     // Toolbar
     'formats' => 'Formāti',
-    'header_large' => 'Large Header',
-    'header_medium' => 'Medium Header',
-    'header_small' => 'Small Header',
-    'header_tiny' => 'Tiny Header',
+    'header_large' => 'Liels virsraksts',
+    'header_medium' => 'Vidējs virsraksts',
+    'header_small' => 'Mazs virsraksts',
+    'header_tiny' => 'Ļoti mazs virsraksts',
     'paragraph' => 'Rindkopa',
-    'blockquote' => 'Blockquote',
-    'inline_code' => 'Inline code',
+    'blockquote' => 'Citāts',
+    'inline_code' => 'Kods iekļauts rindā',
     'callouts' => 'Norādes',
     'callout_information' => 'Informācija',
-    'callout_success' => 'Success',
+    'callout_success' => 'Veiksmīgi',
     'callout_warning' => 'Brīdinājums',
-    'callout_danger' => 'Danger',
-    'bold' => 'Bold',
-    'italic' => 'Italic',
-    'underline' => 'Underline',
-    'strikethrough' => 'Strikethrough',
-    'superscript' => 'Superscript',
-    'subscript' => 'Subscript',
+    'callout_danger' => 'Bīstami',
+    'bold' => 'Treknraksts',
+    'italic' => 'Slīpraksts',
+    'underline' => 'Pasvītrojums',
+    'strikethrough' => 'Pārsvītrojums',
+    'superscript' => 'Augšraksts',
+    'subscript' => 'Apakšraksts',
     'text_color' => 'Teksta krāsa',
-    'custom_color' => 'Custom color',
-    'remove_color' => 'Remove color',
+    'custom_color' => 'Pielāgot krāsu',
+    'remove_color' => 'Noņemt krāsu',
     'background_color' => 'Fona krāsa',
-    'align_left' => 'Align left',
-    'align_center' => 'Align center',
-    'align_right' => 'Align right',
-    'align_justify' => 'Justify',
-    'list_bullet' => 'Bullet list',
+    'align_left' => 'Līdzināt pa kreisi',
+    'align_center' => 'Līdzināt pa vidu',
+    'align_right' => 'Līdzināt pa labi',
+    'align_justify' => 'Līdzināt gar abām malām',
+    'list_bullet' => 'Nenumurēts saraksts',
     'list_numbered' => 'Numurēts saraksts',
-    'list_task' => 'Task list',
+    'list_task' => 'Uzdevumu saraksts',
     'indent_increase' => 'Palielināt atkāpi',
     'indent_decrease' => 'Samazināt atkāpi',
     'table' => 'Tabula',
     'insert_image' => 'Ievietot attēlu',
-    'insert_image_title' => 'Insert/Edit Image',
-    'insert_link' => 'Insert/edit link',
-    'insert_link_title' => 'Insert/Edit Link',
-    'insert_horizontal_line' => 'Insert horizontal line',
-    'insert_code_block' => 'Insert code block',
-    'insert_drawing' => 'Insert/edit drawing',
-    'drawing_manager' => 'Drawing manager',
-    'insert_media' => 'Insert/edit media',
-    'insert_media_title' => 'Insert/Edit Media',
-    'clear_formatting' => 'Clear formatting',
-    'source_code' => 'Source code',
-    'source_code_title' => 'Source Code',
-    'fullscreen' => 'Fullscreen',
-    'image_options' => 'Image options',
+    'insert_image_title' => 'Ievietot/rediģēt attēlu',
+    'insert_link' => 'Ievietot/rediģēt saiti',
+    'insert_link_title' => 'Ievietot/rediģēt saiti',
+    'insert_horizontal_line' => 'Ievietot horizontālu līniju',
+    'insert_code_block' => 'Ievietot koda bloku',
+    'insert_drawing' => 'Ievietot/rediģēt zīmējumu',
+    'drawing_manager' => 'Zīmēšanas pārvaldnieks',
+    'insert_media' => 'Ievietot/rediģēt mediju',
+    'insert_media_title' => 'Ievietot/rediģēt mediju',
+    'clear_formatting' => 'Notīrīt noformējumu',
+    'source_code' => 'Pirmkods',
+    'source_code_title' => 'Pirmkods',
+    'fullscreen' => 'Pilnekrāns',
+    'image_options' => 'Attēla uzstādījumu',
 
     // Tables
-    'table_properties' => 'Table properties',
-    'table_properties_title' => 'Table Properties',
-    'delete_table' => 'Delete table',
-    'insert_row_before' => 'Insert row before',
-    'insert_row_after' => 'Insert row after',
-    'delete_row' => 'Delete row',
-    'insert_column_before' => 'Insert column before',
-    'insert_column_after' => 'Insert column after',
-    'delete_column' => 'Delete column',
+    'table_properties' => 'Tabulas īpašības',
+    'table_properties_title' => 'Tabulas īpašības',
+    'delete_table' => 'Dzēst tabulu',
+    'insert_row_before' => 'Ievietot rindu augstāk',
+    'insert_row_after' => 'Ievietot rindu zemāk',
+    'delete_row' => 'Dzēst rindu',
+    'insert_column_before' => 'Ievietot kolonnu pirms',
+    'insert_column_after' => 'Ievietot kolonnu pēc',
+    'delete_column' => 'Dzēst kolonnu',
     'table_cell' => 'Šūna',
     'table_row' => 'Rinda',
     'table_column' => 'Kolonna',
-    'cell_properties' => 'Cell properties',
-    'cell_properties_title' => 'Cell Properties',
-    'cell_type' => 'Cell type',
+    'cell_properties' => 'Šūnas īpašības',
+    'cell_properties_title' => 'Šūnas īpašības',
+    'cell_type' => 'Šūnas tips',
     'cell_type_cell' => 'Šūna',
-    'cell_scope' => 'Scope',
-    'cell_type_header' => 'Header cell',
-    'merge_cells' => 'Merge cells',
-    'split_cell' => 'Split cell',
-    'table_row_group' => 'Row Group',
-    'table_column_group' => 'Column Group',
-    'horizontal_align' => 'Horizontal align',
-    'vertical_align' => 'Vertical align',
-    'border_width' => 'Border width',
-    'border_style' => 'Border style',
-    'border_color' => 'Border color',
-    'row_properties' => 'Row properties',
-    'row_properties_title' => 'Row Properties',
-    'cut_row' => 'Cut row',
-    'copy_row' => 'Copy row',
-    'paste_row_before' => 'Paste row before',
-    'paste_row_after' => 'Paste row after',
-    'row_type' => 'Row type',
-    'row_type_header' => 'Header',
-    'row_type_body' => 'Body',
-    'row_type_footer' => 'Footer',
-    'alignment' => 'Alignment',
-    'cut_column' => 'Cut column',
-    'copy_column' => 'Copy column',
-    'paste_column_before' => 'Paste column before',
-    'paste_column_after' => 'Paste column after',
-    'cell_padding' => 'Cell padding',
-    'cell_spacing' => 'Cell spacing',
-    'caption' => 'Caption',
-    'show_caption' => 'Show caption',
-    'constrain' => 'Constrain proportions',
-    'cell_border_solid' => 'Solid',
-    'cell_border_dotted' => 'Dotted',
-    'cell_border_dashed' => 'Dashed',
-    'cell_border_double' => 'Double',
-    'cell_border_groove' => 'Groove',
-    'cell_border_ridge' => 'Ridge',
-    'cell_border_inset' => 'Inset',
-    'cell_border_outset' => 'Outset',
-    'cell_border_none' => 'None',
-    'cell_border_hidden' => 'Hidden',
+    'cell_scope' => 'Darbības lauks',
+    'cell_type_header' => 'Galvenes šūna',
+    'merge_cells' => 'Sapludināt šūnas',
+    'split_cell' => 'Sadalīt šūnas',
+    'table_row_group' => 'Rindu grupa',
+    'table_column_group' => 'Kolonnu grupa',
+    'horizontal_align' => 'Horizontāls novietojums',
+    'vertical_align' => 'Vertikāls novietojums',
+    'border_width' => 'Apmales platums',
+    'border_style' => 'Apmales veids',
+    'border_color' => 'Apmales krāsa',
+    'row_properties' => 'Rindas īpašības',
+    'row_properties_title' => 'Rindas īpašības',
+    'cut_row' => 'Izgriezt rindu',
+    'copy_row' => 'Kopēt rindu',
+    'paste_row_before' => 'Ielīmēt rindu augstāk',
+    'paste_row_after' => 'Ielīmēt rindu zemāk',
+    'row_type' => 'Rindas tips',
+    'row_type_header' => 'Galvene',
+    'row_type_body' => 'Pamata saturs',
+    'row_type_footer' => 'Kājene',
+    'alignment' => 'Līdzināšana',
+    'cut_column' => 'Izgriezt kolonnu',
+    'copy_column' => 'Kopēt kolonnu',
+    'paste_column_before' => 'Ielīmēt kolonnu pirms',
+    'paste_column_after' => 'Ielīmēt kolonnu pēc',
+    'cell_padding' => 'Šūnu iekšējais attālums',
+    'cell_spacing' => 'Šūnu attālums',
+    'caption' => 'Virsraksts',
+    'show_caption' => 'Parādīt virsrakstu',
+    'constrain' => 'Saglabāt proporcijas',
+    'cell_border_solid' => 'Pilna',
+    'cell_border_dotted' => 'Punktēta',
+    'cell_border_dashed' => 'Raustīta līnija',
+    'cell_border_double' => 'Dubulta',
+    'cell_border_groove' => 'Iedoba',
+    'cell_border_ridge' => 'Izcelta',
+    'cell_border_inset' => 'Iespiesta',
+    'cell_border_outset' => 'Pacelta',
+    'cell_border_none' => 'Nekas',
+    'cell_border_hidden' => 'Paslēpts',
 
     // Images, links, details/summary & embed
-    'source' => 'Source',
-    'alt_desc' => 'Alternative description',
-    'embed' => 'Embed',
-    'paste_embed' => 'Paste your embed code below:',
+    'source' => 'Avots',
+    'alt_desc' => 'Alternatīvais apraksts',
+    'embed' => 'Iekļaut',
+    'paste_embed' => 'Iekopējiet savu iekļaušanas kodu zemāk:',
     'url' => 'URL',
-    'text_to_display' => 'Text to display',
+    'text_to_display' => 'Attēlojamais teksts',
     'title' => 'Nosaukums',
-    'open_link' => 'Open link in...',
-    'open_link_current' => 'Current window',
-    'open_link_new' => 'New window',
-    'insert_collapsible' => 'Insert collapsible block',
-    'collapsible_unwrap' => 'Unwrap',
-    'edit_label' => 'Edit label',
-    'toggle_open_closed' => 'Toggle open/closed',
-    'collapsible_edit' => 'Edit collapsible block',
-    'toggle_label' => 'Toggle label',
+    'open_link' => 'Atvērt saiti...',
+    'open_link_current' => 'Šis logs',
+    'open_link_new' => 'Jauns logs',
+    'insert_collapsible' => 'Ievietot sakļaujamu bloku',
+    'collapsible_unwrap' => 'Attīt',
+    'edit_label' => 'Rediģēt marķējumu',
+    'toggle_open_closed' => 'Pārslēgt atvērts/aizvērts',
+    'collapsible_edit' => 'Rediģēt sakļaujamu bloku',
+    'toggle_label' => 'Pārslēgt marķējumu',
 
     // About view
-    'about' => 'About the editor',
-    'about_title' => 'About the WYSIWYG Editor',
-    'editor_license' => 'Editor License & Copyright',
-    'editor_tiny_license' => 'This editor is built using :tinyLink which is provided under an LGPL v2.1 license.',
-    'editor_tiny_license_link' => 'The copyright and license details of TinyMCE can be found here.',
-    'save_continue' => 'Save Page & Continue',
-    'callouts_cycle' => '(Keep pressing to toggle through types)',
-    'link_selector' => 'Link to content',
+    'about' => 'Par redaktoru',
+    'about_title' => 'Par WYSIWYG redaktoru',
+    'editor_license' => 'Redaktora licence un autortiesības',
+    'editor_tiny_license' => 'Šis redaktors ir izveidots, izmantojot :tinyLink, kas ir publicēts ar LGPL v2.1 licenci.',
+    'editor_tiny_license_link' => 'TinyMCE autortiesības un licences detaļas var atrast šeit.',
+    'save_continue' => 'Saglabāt lapu un turpināt',
+    'callouts_cycle' => '(Turpiniet spiest, lai pārslēgtu tipus)',
+    'link_selector' => 'Saite uz saturu',
     'shortcuts' => 'Saīsnes',
     'shortcut' => 'Saīsne',
-    'shortcuts_intro' => 'The following shortcuts are available in the editor:',
+    'shortcuts_intro' => 'Šajā redaktorā pieejamas šādas saīsnes:',
     'windows_linux' => '(Windows/Linux)',
     'mac' => '(Mac)',
     'description' => 'Apraksts',
index 975207bfd3165aaa44ef0c84472d541c1cb5cf53..677333c5a818f742ee726e624d28011e1377b842 100644 (file)
@@ -196,19 +196,19 @@ return [
     'pages_edit_draft_save_at' => 'Melnraksts saglabāts ',
     'pages_edit_delete_draft' => 'Dzēst melnrakstu',
     'pages_edit_discard_draft' => 'Atmest malnrakstu',
-    'pages_edit_switch_to_markdown' => 'Switch to Markdown Editor',
-    'pages_edit_switch_to_markdown_clean' => '(Clean Content)',
-    'pages_edit_switch_to_markdown_stable' => '(Stable Content)',
-    'pages_edit_switch_to_wysiwyg' => 'Switch to WYSIWYG Editor',
+    'pages_edit_switch_to_markdown' => 'Pārslēgties uz Markdown redaktoru',
+    'pages_edit_switch_to_markdown_clean' => '(Iztīrītais saturs)',
+    'pages_edit_switch_to_markdown_stable' => '(Stabilais saturs)',
+    'pages_edit_switch_to_wysiwyg' => 'Pārslēgties uz WYSIWYG redaktoru',
     'pages_edit_set_changelog' => 'Pievienot izmaiņu aprakstu',
     'pages_edit_enter_changelog_desc' => 'Ievadi nelielu aprakstu par vaiktajām izmaiņām',
     'pages_edit_enter_changelog' => 'Izmaiņu apraksts',
-    'pages_editor_switch_title' => 'Switch Editor',
-    'pages_editor_switch_are_you_sure' => 'Are you sure you want to change the editor for this page?',
-    'pages_editor_switch_consider_following' => 'Consider the following when changing editors:',
-    'pages_editor_switch_consideration_a' => 'Once saved, the new editor option will be used by any future editors, including those that may not be able to change editor type themselves.',
-    'pages_editor_switch_consideration_b' => 'This can potentially lead to a loss of detail and syntax in certain circumstances.',
-    'pages_editor_switch_consideration_c' => 'Tag or changelog changes, made since last save, won\'t persist across this change.',
+    'pages_editor_switch_title' => 'Pārslēgt redaktoru',
+    'pages_editor_switch_are_you_sure' => 'Vai tiešām vēlaties pārslēgt šai lapai lietojamo redaktoru?',
+    'pages_editor_switch_consider_following' => 'Pārslēdzot redaktorus, ņemiet vērā:',
+    'pages_editor_switch_consideration_a' => 'Pēc saglabāšanas jaunā redaktora izvēle tiks izmantota nākotnē visiem lietotājiem, tai skaitā tiem, kam var nebūt tiesības pašiem mainīt redaktora veidu.',
+    'pages_editor_switch_consideration_b' => 'Tas var noteiktos apstākļos novest pie iespējamiem noformējuma un sintakses zudumiem.',
+    'pages_editor_switch_consideration_c' => 'Birku vai izmaiņu saraksta ieraksti, kas veikti kopš pēdējās saglabāšanas, nesaglabāsies kopā ar šīm izmaiņām.',
     'pages_save' => 'Saglabāt lapu',
     'pages_title' => 'Lapas virsraksts',
     'pages_name' => 'Lapas nosaukums',
@@ -235,7 +235,7 @@ return [
     'pages_revisions_number' => '#',
     'pages_revisions_numbered' => 'Revīzija #:id',
     'pages_revisions_numbered_changes' => 'Revīzijas #:id izmaiņas',
-    'pages_revisions_editor' => 'Editor Type',
+    'pages_revisions_editor' => 'Redaktora veids',
     'pages_revisions_changelog' => 'Izmaiņu žurnāls',
     'pages_revisions_changes' => 'Izmaiņas',
     'pages_revisions_current' => 'Pašreizējā versija',
index d946042629d74eca654ecd817fe744f855557f4a..60faef802cc0213c9f14dd46138bfaea290a06ab 100644 (file)
@@ -10,8 +10,8 @@ return [
     'settings' => 'Iestatījumi',
     'settings_save' => 'Saglabāt iestatījumus',
     'settings_save_success' => 'Iestatījumi saglabāti',
-    'system_version' => 'System Version',
-    'categories' => 'Categories',
+    'system_version' => 'Sistēmas versija',
+    'categories' => 'Kategorijas',
 
     // App Settings
     'app_customization' => 'Pielāgojumi',
@@ -27,8 +27,8 @@ return [
     'app_secure_images' => 'Paaugstinātas drošības attēlu ielāde',
     'app_secure_images_toggle' => 'Ieslēgt paaugstinātas drošības attēlu ielādi',
     'app_secure_images_desc' => 'Ātrdarbības nolūkos attēli ir publiski pieejami. Šī opcija pievieno nejaušu grūti uzminamu teksta virkni attēlu adresēs. Pārliecinieties kā ir izslēgta direktoriju pārlūkošana, lai nepieļautu vieglu piekļuvi šiem failiem.',
-    'app_default_editor' => 'Default Page Editor',
-    'app_default_editor_desc' => 'Select which editor will be used by default when editing new pages. This can be overridden at a page level where permissions allow.',
+    'app_default_editor' => 'Noklusētais lapu redaktors',
+    'app_default_editor_desc' => 'Izvēlieties noklusēto redaktoru jaunu lapu rediģēšanai. To iespējams norādīt arī lapu līmenī, ja piekļuves tiesības to atļauj.',
     'app_custom_html' => 'Pielāgot HTML head saturu',
     'app_custom_html_desc' => 'Šis saturs tiks pievienots <head> sadaļas apakšā visām lapām. Tas ir noderīgi papildinot CSS stilus vai pievienojot analītikas kodu.',
     'app_custom_html_disabled_notice' => 'Pielāgots HTML head saturs ir izslēgts šajā uzstādījumu lapā, lai nodrošinātu, ka iespējams atcelt jebkādas kritiskas izmaiņas.',
@@ -152,7 +152,7 @@ return [
     'role_access_api' => 'Piekļūt sistēmas API',
     'role_manage_settings' => 'Pārvaldīt iestatījumus',
     'role_export_content' => 'Eksportēt saturu',
-    'role_editor_change' => 'Change page editor',
+    'role_editor_change' => 'Mainīt lapu redaktoru',
     'role_asset' => 'Resursa piekļuves tiesības',
     'roles_system_warning' => 'Jebkuras no trīs augstāk redzamajām atļaujām dod iespēju lietotājam mainīt savas un citu lietotāju sistēmas atļaujas. Pievieno šīs grupu atļaujas tikai tiem lietotājiem, kuriem uzticies.',
     'role_asset_desc' => 'Šīs piekļuves tiesības kontrolē noklusēto piekļuvi sistēmas resursiem. Grāmatām, nodaļām un lapām norādītās tiesības būs pārākas par šīm.',
@@ -279,6 +279,7 @@ return [
         'es_AR' => 'Español Argentina',
         'et' => 'Igauņu',
         'eu' => 'Euskara',
+        'fa' => 'فارسی',
         'fr' => 'Français',
         'he' => 'עברית',
         'hr' => 'Hrvatski',
index 113ecef24559bfe23362f62ac4ce6d1763ee6c83..4251cce690d838bd40585e44d25ae74c39cb134c 100644 (file)
@@ -279,6 +279,7 @@ return [
         'es_AR' => 'Español Argentina',
         'et' => 'Eesti keel',
         'eu' => 'Euskara',
+        'fa' => 'فارسی',
         'fr' => 'Français',
         'he' => 'עברית',
         'hr' => 'Hrvatski',
index 2d452ebb64081b42aa8fe5089d538b37de4f127f..2c1ad13f36ae1a5a25a3545ff8f894d5687b87f1 100644 (file)
@@ -154,10 +154,10 @@ return [
     'toggle_label' => 'Schakel label aan/uit',
 
     // About view
-    'about' => 'Over de editor',
-    'about_title' => 'Over de WYSIWYG-bewerker',
-    'editor_license' => 'Editor Licentie & Copyright',
-    'editor_tiny_license' => 'Deze editor is gebouwd met :tinyLink, dat aangeboden wordt via een LGPL v2.1 licentie.',
+    'about' => 'Over de bewerker',
+    'about_title' => 'Over de WYSIWYG Bewerker',
+    'editor_license' => 'Bewerker Licentie & Copyright',
+    'editor_tiny_license' => 'Deze bewerker is gebouwd met :tinyLink, dat aangeboden wordt via een LGPL v2.1 licentie.',
     'editor_tiny_license_link' => 'De copyright- en licentiegegevens van TinyMCE vindt u hier.',
     'save_continue' => 'Pagina opslaan en verdergaan',
     'callouts_cycle' => '(Blijf drukken om door de types te wisselen)',
index e45194e65d3e361302ba3ea3ba009d7cdb4bfaf3..03eaf9f942b00f8aa7e263692e8266eb7aa11abf 100644 (file)
@@ -196,23 +196,23 @@ return [
     'pages_edit_draft_save_at' => 'Concept opgeslagen op ',
     'pages_edit_delete_draft' => 'Concept verwijderen',
     'pages_edit_discard_draft' => 'Concept verwijderen',
-    'pages_edit_switch_to_markdown' => 'Switch to Markdown Editor',
-    'pages_edit_switch_to_markdown_clean' => '(Clean Content)',
-    'pages_edit_switch_to_markdown_stable' => '(Stable Content)',
-    'pages_edit_switch_to_wysiwyg' => 'Switch to WYSIWYG Editor',
+    'pages_edit_switch_to_markdown' => 'Verander naar Markdown Bewerker',
+    'pages_edit_switch_to_markdown_clean' => '(Schoongemaakte Inhoud)',
+    'pages_edit_switch_to_markdown_stable' => '(Stabiele Inhoud)',
+    'pages_edit_switch_to_wysiwyg' => 'Verander naar WYSIWYG Bewerker',
     'pages_edit_set_changelog' => 'Wijzigingslogboek instellen',
     'pages_edit_enter_changelog_desc' => 'Geef een korte omschrijving van de wijzigingen die je gemaakt hebt',
     'pages_edit_enter_changelog' => 'Voeg toe aan wijzigingslogboek',
-    'pages_editor_switch_title' => 'Switch Editor',
-    'pages_editor_switch_are_you_sure' => 'Are you sure you want to change the editor for this page?',
-    'pages_editor_switch_consider_following' => 'Consider the following when changing editors:',
-    'pages_editor_switch_consideration_a' => 'Once saved, the new editor option will be used by any future editors, including those that may not be able to change editor type themselves.',
-    'pages_editor_switch_consideration_b' => 'This can potentially lead to a loss of detail and syntax in certain circumstances.',
-    'pages_editor_switch_consideration_c' => 'Tag or changelog changes, made since last save, won\'t persist across this change.',
+    'pages_editor_switch_title' => 'Wijzig Bewerker',
+    'pages_editor_switch_are_you_sure' => 'Weet u zeker dat u de bewerker voor deze pagina wilt wijzigen?',
+    'pages_editor_switch_consider_following' => 'Houd rekening met het volgende als u van bewerker verandert:',
+    'pages_editor_switch_consideration_a' => 'Eenmaal opgeslagen, zal de nieuwe bewerker keuze gebruikt worden door alle toekomstige gebruikers, ook diegene die zelf niet van bewerker type kunnen veranderen.',
+    'pages_editor_switch_consideration_b' => 'Dit kan mogelijks tot een verlies van detail en syntax leiden in bepaalde omstandigheden.',
+    'pages_editor_switch_consideration_c' => 'De veranderingen aan Tags of aan het wijzigingslogboek, sinds de laatste keer opslaan, zullen niet behouden blijven met deze wijziging.',
     'pages_save' => 'Pagina opslaan',
     'pages_title' => 'Pagina titel',
     'pages_name' => 'Pagina naam',
-    'pages_md_editor' => 'Bewerken',
+    'pages_md_editor' => 'Bewerker',
     'pages_md_preview' => 'Voorbeeld',
     'pages_md_insert_image' => 'Afbeelding invoegen',
     'pages_md_insert_link' => 'Entity link invoegen',
@@ -235,7 +235,7 @@ return [
     'pages_revisions_number' => '#',
     'pages_revisions_numbered' => 'Revisie #:id',
     'pages_revisions_numbered_changes' => 'Revisie #:id wijzigingen',
-    'pages_revisions_editor' => 'Editor Type',
+    'pages_revisions_editor' => 'Bewerker Type',
     'pages_revisions_changelog' => 'Wijzigingsoverzicht',
     'pages_revisions_changes' => 'Wijzigingen',
     'pages_revisions_current' => 'Huidige versie',
index 870d32b1747c22fe626c773dae6e6cf204fc1ae4..65596d3b491a129e12d401c434e655eb306c891a 100644 (file)
@@ -27,8 +27,8 @@ return [
     'app_secure_images' => 'Uploaden van afbeeldingen met hogere beveiliging',
     'app_secure_images_toggle' => 'Activeer uploaden van afbeeldingen met hogere beveiliging',
     'app_secure_images_desc' => 'Om prestatieredenen zijn alle afbeeldingen openbaar. Deze optie voegt een willekeurige en moeilijk te raden tekst toe aan de URL\'s van de afbeeldingen. Zorg ervoor dat "directory indexes" niet ingeschakeld zijn om eenvoudige toegang te voorkomen.',
-    'app_default_editor' => 'Default Page Editor',
-    'app_default_editor_desc' => 'Select which editor will be used by default when editing new pages. This can be overridden at a page level where permissions allow.',
+    'app_default_editor' => 'Standaard Pagina Bewerker',
+    'app_default_editor_desc' => 'Selecteer welke bewerker standaard zal worden gebruikt bij het bewerken van nieuwe pagina\'s. Dit kan worden overschreven op paginaniveau als de rechten dat toestaan.',
     'app_custom_html' => 'HTML aan <head> toevoegen',
     'app_custom_html_desc' => 'Alle hieronder toegevoegde data wordt aan het einde van de <head> sectie van elke pagina toegevoegd. Gebruik dit om stijlen te overschrijven of analytische code toe te voegen.',
     'app_custom_html_disabled_notice' => 'Bovenstaande wordt niet toegevoegd aan deze pagina om ervoor te zorgen dat je foutieve code steeds ongedaan kan maken.',
@@ -152,7 +152,7 @@ return [
     'role_access_api' => 'Ga naar systeem API',
     'role_manage_settings' => 'Beheer app instellingen',
     'role_export_content' => 'Exporteer inhoud',
-    'role_editor_change' => 'Change page editor',
+    'role_editor_change' => 'Wijzig pagina bewerker',
     'role_asset' => 'Asset Machtigingen',
     'roles_system_warning' => 'Wees ervan bewust dat toegang tot een van de bovengenoemde drie machtigingen een gebruiker in staat kan stellen zijn eigen machtigingen of de machtigingen van anderen in het systeem kan wijzigen. Wijs alleen rollen toe met deze machtigingen aan vertrouwde gebruikers.',
     'role_asset_desc' => 'Deze machtigingen bepalen de standaard toegang tot de assets binnen het systeem. Machtigingen op boeken, hoofdstukken en pagina\'s overschrijven deze instelling.',
@@ -279,6 +279,7 @@ return [
         'es_AR' => 'Español Argentina (Argentijns Spaans)',
         'et' => 'Eesti keel (Estisch)',
         'eu' => 'Euskara',
+        'fa' => 'فارسی',
         'fr' => 'Français (Frans)',
         'he' => 'עברית (Hebreeuws)',
         'hr' => 'Hrvatski (Kroatisch)',
index bb337d7c4030dfa1fddfbaa2a9ab62f43a243589..544f374f3fe07f2708c756cc7b3e4dc6b764c61c 100644 (file)
@@ -196,19 +196,19 @@ return [
     'pages_edit_draft_save_at' => 'Wersja robocza zapisana ',
     'pages_edit_delete_draft' => 'Usuń wersje roboczą',
     'pages_edit_discard_draft' => 'Porzuć wersje roboczą',
-    'pages_edit_switch_to_markdown' => 'Switch to Markdown Editor',
-    'pages_edit_switch_to_markdown_clean' => '(Clean Content)',
-    'pages_edit_switch_to_markdown_stable' => '(Stable Content)',
-    'pages_edit_switch_to_wysiwyg' => 'Switch to WYSIWYG Editor',
+    'pages_edit_switch_to_markdown' => 'Przełącz na edytor Markdown',
+    'pages_edit_switch_to_markdown_clean' => '(Czysta zawartość)',
+    'pages_edit_switch_to_markdown_stable' => '(Statyczna zawartość)',
+    'pages_edit_switch_to_wysiwyg' => 'Przełącz na edytor WYSIWYG',
     'pages_edit_set_changelog' => 'Ustaw dziennik zmian',
     'pages_edit_enter_changelog_desc' => 'Opisz zmiany, które zostały wprowadzone',
     'pages_edit_enter_changelog' => 'Wyświetl dziennik zmian',
-    'pages_editor_switch_title' => 'Switch Editor',
-    'pages_editor_switch_are_you_sure' => 'Are you sure you want to change the editor for this page?',
-    'pages_editor_switch_consider_following' => 'Consider the following when changing editors:',
-    'pages_editor_switch_consideration_a' => 'Once saved, the new editor option will be used by any future editors, including those that may not be able to change editor type themselves.',
-    'pages_editor_switch_consideration_b' => 'This can potentially lead to a loss of detail and syntax in certain circumstances.',
-    'pages_editor_switch_consideration_c' => 'Tag or changelog changes, made since last save, won\'t persist across this change.',
+    'pages_editor_switch_title' => 'Przełącz edytor',
+    'pages_editor_switch_are_you_sure' => 'Czy na pewno chcesz zmienić edytor dla tej strony?',
+    'pages_editor_switch_consider_following' => 'Przy zmianie edytorów pamiętaj, że:',
+    'pages_editor_switch_consideration_a' => 'Po zapisaniu nowo ustawiony edytor będzie używany przez przyszłych edytujących użytkowników, w tym również tych, którzy sami mogą nie być w stanie zmienić edytora.',
+    'pages_editor_switch_consideration_b' => 'Może to potencjalnie prowadzić do utraty szczegółów i składni w pewnych przypadkach.',
+    'pages_editor_switch_consideration_c' => 'Zmiany w znacznikach lub w dzienniku zmian, zrobione od ostatniego zapisu, nie zostaną zapamiętane przy tej zmianie.',
     'pages_save' => 'Zapisz stronę',
     'pages_title' => 'Tytuł strony',
     'pages_name' => 'Nazwa strony',
@@ -235,7 +235,7 @@ return [
     'pages_revisions_number' => '#',
     'pages_revisions_numbered' => 'Wersja #:id',
     'pages_revisions_numbered_changes' => 'Zmiany w wersji #:id',
-    'pages_revisions_editor' => 'Editor Type',
+    'pages_revisions_editor' => 'Typ edytora',
     'pages_revisions_changelog' => 'Dziennik zmian',
     'pages_revisions_changes' => 'Zmiany',
     'pages_revisions_current' => 'Obecna wersja',
index b35981b0147c7207609ab1278266e67bfdb9c736..f7ef384e8f75e2e32e6bd131ee8dc77a8ea9ba73 100644 (file)
@@ -27,8 +27,8 @@ return [
     'app_secure_images' => 'Włączyć przesyłanie obrazów o wyższym poziomie bezpieczeństwa?',
     'app_secure_images_toggle' => 'Włącz wyższy poziom bezpieczeństwa dla obrazów',
     'app_secure_images_desc' => 'Ze względów wydajnościowych wszystkie obrazki są publiczne. Ta opcja dodaje dodatkowy, trudny do odgadnięcia losowy ciąg na początku nazwy obrazka. Upewnij się że indeksowanie katalogów jest zablokowane, aby uniemożliwić łatwy dostęp do obrazków.',
-    'app_default_editor' => 'Default Page Editor',
-    'app_default_editor_desc' => 'Select which editor will be used by default when editing new pages. This can be overridden at a page level where permissions allow.',
+    'app_default_editor' => 'Domyślny edytor stron',
+    'app_default_editor_desc' => 'Wybierz, który edytor będzie domyślnie używany podczas edycji nowych stron. Może to być nadpisane na poziomie strony, na którym pozwalają na to uprawnienia.',
     'app_custom_html' => 'Własna zawartość w tagu <head>',
     'app_custom_html_desc' => 'Zawartość dodana tutaj zostanie dołączona na dole sekcji <head> każdej strony. Przydatne przy nadpisywaniu styli lub dodawaniu analityki.',
     'app_custom_html_disabled_notice' => 'Niestandardowa zawartość nagłówka HTML jest wyłączona na tej stronie ustawień aby zapewnić, że wszystkie błedne zmiany (braking change) mogą zostać cofnięte.',
@@ -152,7 +152,7 @@ return [
     'role_access_api' => 'Dostęp do systemowego API',
     'role_manage_settings' => 'Zarządzanie ustawieniami aplikacji',
     'role_export_content' => 'Eksportuj zawartość',
-    'role_editor_change' => 'Change page editor',
+    'role_editor_change' => 'Zmień edytor strony',
     'role_asset' => 'Zarządzanie zasobami',
     'roles_system_warning' => 'Pamiętaj, że dostęp do trzech powyższych uprawnień może pozwolić użytkownikowi na zmianę własnych uprawnień lub uprawnień innych osób w systemie. Przypisz tylko role z tymi uprawnieniami do zaufanych użytkowników.',
     'role_asset_desc' => 'Te ustawienia kontrolują zarządzanie zasobami systemu. Uprawnienia książek, rozdziałów i stron nadpisują te ustawienia.',
@@ -279,6 +279,7 @@ return [
         'es_AR' => 'Español Argentina',
         'et' => 'Estoński',
         'eu' => 'Euskara',
+        'fa' => 'فارسی',
         'fr' => 'Français',
         'he' => 'עברית',
         'hr' => 'Hrvatski',
index c5ad184d990a78215d4c5ddf911d4e0882c1f4e8..782ee494b599976976742c77a1127198dadecd59 100644 (file)
@@ -279,6 +279,7 @@ return [
         'es_AR' => 'Español Argentina',
         'et' => 'Eesti keel',
         'eu' => 'Euskara',
+        'fa' => 'فارسی',
         'fr' => 'Français',
         'he' => 'עברית',
         'hr' => 'Hrvatski',
index 51742541122d62226fa7f114ec32f0b03bcdcca5..a9b53277f32bf49512f1f1fd93c4c69a5ab07485 100644 (file)
@@ -279,6 +279,7 @@ return [
         'es_AR' => 'Español Argentina',
         'et' => 'Eesti keel',
         'eu' => 'Euskara',
+        'fa' => 'فارسی',
         'fr' => 'Français',
         'he' => 'עברית',
         'hr' => 'Hrvatski',
index 4503ee14e67a8fe7b032950dc340ad71c382071e..2daf99cd04efaaa274b2b0fae5345d06e192ceaf 100755 (executable)
@@ -279,6 +279,7 @@ return [
         'es_AR' => 'Español Argentina',
         'et' => 'Eesti keel',
         'eu' => 'Euskara',
+        'fa' => 'فارسی',
         'fr' => 'Français',
         'he' => 'עברית',
         'hr' => 'Hrvatski',
index 6d9e3219128c27f21565a4ea01119141faf2f392..b2481753b6facd7b8082eeba0f3e3002a797ee17 100644 (file)
@@ -279,6 +279,7 @@ return [
         'es_AR' => 'Español Argentina',
         'et' => 'Eesti keel',
         'eu' => 'Euskara',
+        'fa' => 'فارسی',
         'fr' => 'Français',
         'he' => 'עברית',
         'hr' => 'Hrvatski',
index 7849fe5fcc3aafe60987bd0ee1d432971b0e202c..40c99f7105561762cb8700e93b9a519392d1abc7 100644 (file)
@@ -280,6 +280,7 @@ return [
         'es_AR' => 'Español Argentina',
         'et' => 'Eesti keel',
         'eu' => 'Euskara',
+        'fa' => 'فارسی',
         'fr' => 'Français',
         'he' => 'עברית',
         'hr' => 'Hrvatski',
index a1e167153fda5b90adb22e751b6a1f532465b134..57d01123f3d993c4ce252a0af5289d789fcf6222 100644 (file)
@@ -279,6 +279,7 @@ return [
         'es_AR' => 'Español Argentina',
         'et' => 'Eesti keel',
         'eu' => 'Euskara',
+        'fa' => 'فارسی',
         'fr' => 'Français',
         'he' => 'עברית',
         'hr' => 'Hrvatski',
index c4715b4f9908ae35a967b392810c3af98d99162b..1d7efd66444f7046d21f8944006b235a2721d6b7 100755 (executable)
@@ -279,6 +279,7 @@ return [
         'es_AR' => 'Español Argentina',
         'et' => 'Eesti keel',
         'eu' => 'Euskara',
+        'fa' => 'فارسی',
         'fr' => 'Français',
         'he' => 'İbranice',
         'hr' => 'Hrvatski',
index 984b5ab3466d0eab481aed09e1875d2541466c41..9c9b654717d45b83e6e9784bfe44d423d8112f4f 100644 (file)
@@ -24,7 +24,7 @@ return [
     'width' => 'Ширина',
     'height' => 'Висота',
     'More' => 'Більше',
-    'select' => 'Select...',
+    'select' => 'Вибрати…',
 
     // Toolbar
     'formats' => 'Формати',
@@ -53,10 +53,10 @@ return [
     'align_left' => 'Вирівняти по лівому краю',
     'align_center' => 'Вирівняти по центру',
     'align_right' => 'Вирівнювання по правому краю',
-    'align_justify' => 'Justify',
+    'align_justify' => 'За шириною',
     'list_bullet' => 'Маркований список',
     'list_numbered' => 'Нумерований список',
-    'list_task' => 'Task list',
+    'list_task' => 'Список завдань',
     'indent_increase' => 'Збільшити відступ',
     'indent_decrease' => 'Зменшити відступ',
     'table' => 'Таблиця',
@@ -95,8 +95,8 @@ return [
     'cell_type_cell' => 'Комірка',
     'cell_scope' => 'Scope',
     'cell_type_header' => 'Комірка заголовка',
-    'merge_cells' => 'Merge cells',
-    'split_cell' => 'Split cell',
+    'merge_cells' => 'Об\'єднати комірки',
+    'split_cell' => 'Роз\'єднати комірку',
     'table_row_group' => 'Група рядків',
     'table_column_group' => 'Група стовпців',
     'horizontal_align' => 'Горизонтальне вирівнювання',
@@ -154,7 +154,7 @@ return [
     'toggle_label' => 'Перемкнути ярлики',
 
     // About view
-    'about' => 'About the editor',
+    'about' => 'Про редактор',
     'about_title' => 'Про WYSIWYG редактор',
     'editor_license' => 'Ліцензія редактора і авторські права',
     'editor_tiny_license' => 'Цей редактор побудований за допомогою :tinyLink що забезпечується під ліцензією LGPL v2.1.',
index 0aad8ee93bc30d2f7b976524ed45958a0d980f4f..bd9eadf1f99e52530c03b2c2646c4cad1a819525 100644 (file)
@@ -196,14 +196,14 @@ return [
     'pages_edit_draft_save_at' => 'Чернетка збережена о ',
     'pages_edit_delete_draft' => 'Видалити чернетку',
     'pages_edit_discard_draft' => 'Відхилити чернетку',
-    'pages_edit_switch_to_markdown' => 'Switch to Markdown Editor',
-    'pages_edit_switch_to_markdown_clean' => '(Clean Content)',
-    'pages_edit_switch_to_markdown_stable' => '(Stable Content)',
-    'pages_edit_switch_to_wysiwyg' => 'Switch to WYSIWYG Editor',
+    'pages_edit_switch_to_markdown' => 'Змінити редактор на Markdown',
+    'pages_edit_switch_to_markdown_clean' => '(Очистити вміст)',
+    'pages_edit_switch_to_markdown_stable' => '(Стабілізувати вміст)',
+    'pages_edit_switch_to_wysiwyg' => 'Змінити редактор на WYSIWYG',
     'pages_edit_set_changelog' => 'Встановити журнал змін',
     'pages_edit_enter_changelog_desc' => 'Введіть короткий опис внесених вами змін',
     'pages_edit_enter_changelog' => 'Введіть список змін',
-    'pages_editor_switch_title' => 'Switch Editor',
+    'pages_editor_switch_title' => 'Змінити редактор',
     'pages_editor_switch_are_you_sure' => 'Are you sure you want to change the editor for this page?',
     'pages_editor_switch_consider_following' => 'Consider the following when changing editors:',
     'pages_editor_switch_consideration_a' => 'Once saved, the new editor option will be used by any future editors, including those that may not be able to change editor type themselves.',
index 91565fd6baf32a075c1a25c2448e2e283b4d93b8..946a0c0271d92812be3a14fbc6dcbc933294ed4d 100644 (file)
@@ -10,8 +10,8 @@ return [
     'settings' => 'Налаштування',
     'settings_save' => 'Зберегти налаштування',
     'settings_save_success' => 'Налаштування збережено',
-    'system_version' => 'System Version',
-    'categories' => 'Categories',
+    'system_version' => 'Версія',
+    'categories' => 'Категорії',
 
     // App Settings
     'app_customization' => 'Налаштування',
@@ -27,8 +27,8 @@ return [
     'app_secure_images' => 'Вищі налаштування безпеки для зображень',
     'app_secure_images_toggle' => 'Увімкунти вищі налаштування безпеки для завантаження зображень',
     'app_secure_images_desc' => 'З міркувань продуктивності всі зображення є загальнодоступними. Цей параметр додає випадковий, важко передбачуваний рядок перед URL-адресами зображень. Переконайтеся, що індексація каталогів не активована, щоб запобігти легкому доступу.',
-    'app_default_editor' => 'Default Page Editor',
-    'app_default_editor_desc' => 'Select which editor will be used by default when editing new pages. This can be overridden at a page level where permissions allow.',
+    'app_default_editor' => 'Стандартний редактор сторінок',
+    'app_default_editor_desc' => 'Виберіть, який редактор буде використовуватися за замовчуванням під час редагування нових сторінок. Це можна перевизначити на рівні дозволів сторінки.',
     'app_custom_html' => 'Користувацький вміст HTML-заголовку',
     'app_custom_html_desc' => 'Будь-який доданий тут вміст буде вставлено в нижню частину розділу <head> кожної сторінки. Це зручно для перевизначення стилів, або додавання коду аналітики.',
     'app_custom_html_disabled_notice' => 'На цій сторінці налаштувань відключений користувацький вміст заголовка HTML, щоб гарантувати, що будь-які невдалі зміни можна буде відновити.',
@@ -152,7 +152,7 @@ return [
     'role_access_api' => 'Доступ до системного API',
     'role_manage_settings' => 'Керування налаштуваннями програми',
     'role_export_content' => 'Вміст експорту',
-    'role_editor_change' => 'Change page editor',
+    'role_editor_change' => 'Змінити редактор сторінок',
     'role_asset' => 'Дозволи',
     'roles_system_warning' => 'Майте на увазі, що доступ до будь-якого з вищезазначених трьох дозволів може дозволити користувачеві змінювати власні привілеї або привілеї інших в системі. Ролі з цими дозволами призначайте лише довіреним користувачам.',
     'role_asset_desc' => 'Ці дозволи контролюють стандартні доступи всередині системи. Права на книги, розділи та сторінки перевизначать ці дозволи.',
@@ -279,6 +279,7 @@ return [
         'es_AR' => 'Español Argentina',
         'et' => 'Eesti keel',
         'eu' => 'Euskara',
+        'fa' => 'فارسی',
         'fr' => 'Français',
         'he' => 'עברית',
         'hr' => 'Hrvatski',
index af2dcc1e1655bab7208832cb6e090a578f2e931f..3bfe70bc4cd556934240e8a78d2cc2d7ba41e542 100644 (file)
@@ -279,6 +279,7 @@ return [
         'es_AR' => 'Español Argentina',
         'et' => 'Eesti keel',
         'eu' => 'Euskara',
+        'fa' => 'فارسی',
         'fr' => 'Français',
         'he' => 'עברית',
         'hr' => 'Hrvatski',
index b59727b1680dc2f808f8f593dfb9f4976c9624a0..b4e0006873b48a23e25184c1719c524226e46c86 100644 (file)
@@ -279,6 +279,7 @@ return [
         'es_AR' => 'Español Argentina',
         'et' => 'Eesti keel',
         'eu' => 'Euskara',
+        'fa' => 'فارسی',
         'fr' => 'Français',
         'he' => 'עברית',
         'hr' => 'Hrvatski',
index c93577798d9224b8bbdf21b1fc32c0fe13004e5c..b4f439b68e055d2be9899277b8e1b85ec2d50107 100644 (file)
@@ -23,7 +23,7 @@ return [
     'meta_updated' => '更新于 :timeLength',
     'meta_updated_name' => '由 :user 更新于 :timeLength',
     'meta_owned_name' => '拥有者 :user',
-    'entity_select' => '实体选择',
+    'entity_select' => '选择项目',
     'images' => '图片',
     'my_recent_drafts' => '我最近的草稿',
     'my_recently_viewed' => '我最近看过',
@@ -197,7 +197,7 @@ return [
     'pages_edit_delete_draft' => '删除草稿',
     'pages_edit_discard_draft' => '放弃草稿',
     'pages_edit_switch_to_markdown' => '切换到 Markdown 编辑器',
-    'pages_edit_switch_to_markdown_clean' => 'ï¼\88æ¸\85é\99¤内容)',
+    'pages_edit_switch_to_markdown_clean' => 'ï¼\88æ\95´ç\90\86内容)',
     'pages_edit_switch_to_markdown_stable' => '(保留内容)',
     'pages_edit_switch_to_wysiwyg' => '切换到所见即所得编辑器',
     'pages_edit_set_changelog' => '更新说明',
@@ -206,16 +206,16 @@ return [
     'pages_editor_switch_title' => '切换编辑器',
     'pages_editor_switch_are_you_sure' => '您确定要更改此页面的编辑器吗?',
     'pages_editor_switch_consider_following' => '更改编辑器时请注意以下事项:',
-    'pages_editor_switch_consideration_a' => '一旦保存,任何未来的编辑都将使用新的编辑器,包括那些可能无法自行更改编辑器类型的编辑器。',
-    'pages_editor_switch_consideration_b' => '这可能导致在某些情况下失去细节和语法。',
-    'pages_editor_switch_consideration_c' => '此次修改不会保存上次保存后修改的标签和更改日志。',
+    'pages_editor_switch_consideration_a' => '一旦保存,任何未来的编辑都将使用新的编辑器,包括那些没有权限自行更改编辑器类型的用户。',
+    'pages_editor_switch_consideration_b' => '在某些情况下这可能会导致丢失页面格式或功能损坏。',
+    'pages_editor_switch_consideration_c' => '上次保存后修改的标签和更改日志将不会被保存。',
     'pages_save' => '保存页面',
     'pages_title' => '页面标题',
     'pages_name' => '页面名',
     'pages_md_editor' => '编者',
     'pages_md_preview' => '预览',
     'pages_md_insert_image' => '插入图片',
-    'pages_md_insert_link' => '插入实体链接',
+    'pages_md_insert_link' => '插入项目链接',
     'pages_md_insert_drawing' => '插入图表',
     'pages_not_in_chapter' => '本页面不在某章节中',
     'pages_move' => '移动页面',
index 569c8482c7ade30ce6885af52599cb429e434840..b1028277cfdb48654c1c93a88ae655219f637722 100644 (file)
@@ -57,7 +57,7 @@ return [
     'page_custom_home_deletion' => '无法删除一个被设置为主页的页面',
 
     // Entities
-    'entity_not_found' => '未找到实体',
+    'entity_not_found' => '未找到项目',
     'bookshelf_not_found' => '未找到书架',
     'book_not_found' => '未找到图书',
     'page_not_found' => '未找到页面',
index bef4d20f5082ffc4b886a791e7889494a2d26e86..ddf6fa1c094f81fb8bc1cac4c9e27c86faaa5ea1 100755 (executable)
@@ -237,14 +237,14 @@ return [
     // Webhooks
     'webhooks' => 'Webhooks',
     'webhooks_create' => '新建 Webhook',
-    'webhooks_none_created' => '尚未创建任何 webhook',
+    'webhooks_none_created' => '尚未创建任何 Webhook。',
     'webhooks_edit' => '编辑 Webhook',
     'webhooks_save' => '保存 Webhook',
     'webhooks_details' => 'Webhook 详情',
-    'webhooks_details_desc' => '提供一个用户友好的名称和一个 POST endpoint 作为 webhook 数据发送的位置。',
+    'webhooks_details_desc' => '提供一个用户友好的名称和一个 POST Endpoint 作为 Webhook 数据发送的位置。',
     'webhooks_events' => 'Webhook 事件',
-    'webhooks_events_desc' => '选择所有应触发此 webhook 的事件。',
-    'webhooks_events_warning' => '请记住,即使应用了自定义权限,所有选定的事件也仍然会被触发。 确保使用此 webhook 不会泄露机密内容。',
+    'webhooks_events_desc' => '选择所有应触发此 Webhook 的事件。',
+    'webhooks_events_warning' => '请记住,即使应用了自定义权限,所有选定的事件也仍然会被触发。 确保使用此 Webhook 不会泄露机密内容。',
     'webhooks_events_all' => '所有系统事件',
     'webhooks_name' => 'Webhook 名称',
     'webhooks_timeout' => 'Webhook 请求超时(秒)',
@@ -255,7 +255,7 @@ return [
     'webhooks_delete_warning' => '这将会从系统中完全删除名为 “:webhookName” 的 webhook。',
     'webhooks_delete_confirm' => '您确定要删除此 Webhook 吗?',
     'webhooks_format_example' => 'Webhook 格式示例',
-    'webhooks_format_example_desc' => 'Webhook 数据会用 POST 请求按照以下 JSON 格式发送到设置的 endpoint。 “related_item” 和 “url” 属性是可选的,取决于触发的事件类型。',
+    'webhooks_format_example_desc' => 'Webhook 数据会以 POST 请求按照以下 JSON 格式发送到设置的 Endpoint。 “related_item” 和 “url” 属性是可选的,取决于触发的事件类型。',
     'webhooks_status' => 'Webhook 状态',
     'webhooks_last_called' => '最后一次调用:',
     'webhooks_last_errored' => '最后一个错误:',
@@ -279,6 +279,7 @@ return [
         'es_AR' => 'Español Argentina',
         'et' => 'Eesti keel',
         'eu' => 'Euskara',
+        'fa' => 'فارسی',
         'fr' => 'Français',
         'he' => 'עברית',
         'hr' => 'Hrvatski',
index 2dd9d55774c1e9368b7a532331058f67789909bb..d03d5dc66274cfad9becfa7503c1245a5067506f 100644 (file)
@@ -279,6 +279,7 @@ return [
         'es_AR' => 'Español Argentina',
         'et' => 'Eesti keel',
         'eu' => 'Euskara',
+        'fa' => 'فارسی',
         'fr' => 'Français',
         'he' => '希伯來語',
         'hr' => 'Hrvatski',
index 7d408cd1b5f24fdcb41f42e18e8b680bf9baa899..0398224ca52a406ee29d7ae160fa63a00caae4b4 100644 (file)
@@ -66,7 +66,6 @@
   @include lightDark(background-color, #FFF, #222);
   box-shadow: $bs-card;
   border-radius: 3px;
-  border: 1px solid transparent;
   .body, p.empty-text {
     padding: $-m;
   }
index bce456cf2ce1e1b82147ae45e8f368ac83a91e83..4509c2b9a4b8a610e424eddfc54493d073b509a6 100644 (file)
@@ -1,3 +1,4 @@
+
 // System wide notifications
 [notification] {
   position: fixed;
@@ -61,7 +62,7 @@
   }
 }
 
-[chapter-toggle] {
+.chapter-contents-toggle {
   cursor: pointer;
   margin: 0;
   transition: all ease-in-out 180ms;
@@ -77,7 +78,7 @@
     transform: rotate(90deg);
   }
   svg[data-icon="caret-right"] + * {
-    margin-inline-start: $-xs;
+    margin-inline-start: $-xxs;
   }
 }
 
   }
 }
 
-.popup-footer button, .popup-header-close {
-  position: absolute;
-  top: 0;
-  right: 0;
+.popup-header button, .popup-footer button {
   margin: 0;
-  height: 40px;
   border-radius: 0;
   box-shadow: none;
-  &:active {
-    outline: 0;
+  color: #FFF;
+  padding: $-xs $-m;
+}
+
+.popup-header button:not(.popup-header-close) {
+  font-size: 0.8rem;
+}
+
+.popup-header button:hover {
+    background-color: rgba(255, 255, 255, 0.1);
+}
+
+.popup-footer {
+  justify-content: end;
+  background-color: var(--color-primary-light);
+  min-height: 41px;
+  button {
+    padding: 10px $-m;
   }
 }
+
 .popup-header-close {
-  background-color: transparent;
   border: 0;
   color: #FFF;
   font-size: 16px;
-  padding: 0 $-m;
+  cursor: pointer;
+  svg {
+    margin-right: 0;
+  }
 }
 
 .popup-header, .popup-footer {
-  display: block !important;
+  display: flex;
   position: relative;
   height: 40px;
-  flex: none !important;
+  flex: 0;
   .popup-title {
     color: #FFF;
+    margin-right: auto;
     padding: 8px $-m;
   }
+  &.flex-container-row {
+    display: flex !important;
+  }
 }
 body.flexbox-support #entity-selector-wrap .popup-body .form-group {
   height: 444px;
@@ -298,12 +318,6 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
   }
 }
 
-.image-manager .corner-button {
-  margin: 0;
-  border-radius: 0;
-  padding: $-m;
-}
-
 // Dropzone
 /*
  * The MIT License
@@ -630,24 +644,76 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
 }
 
 .code-editor .CodeMirror {
-  height: 400px;
+  height: auto;
+  min-height: 50vh;
+  border-bottom: 0;
 }
 
 .code-editor .lang-options {
-  max-width: 540px;
-  margin-bottom: $-s;
-  a {
-    margin-inline-end: $-xs;
-    text-decoration: underline;
+  overflow-y: scroll;
+  flex-basis: 200px;
+  flex-grow: 1;
+}
+
+.code-editor .lang-options button {
+  display: block;
+  padding: $-xs $-m;
+  border-bottom: 1px solid;
+  @include lightDark(color, #333, #AAA);
+  @include lightDark(border-bottom-color, #EEE, #000);
+  cursor: pointer;
+  width: 100%;
+  text-align: left;
+  font-family: $mono;
+  font-size: 0.7rem;
+  &:hover, &.active {
+    background-color: var(--color-primary-light);
+    color: var(--color-primary);
   }
 }
 
-@include smaller-than($m) {
-  .code-editor .lang-options {
+.code-editor label {
+  background-color: var(--color-primary-light);
+  width: 100%;
+  color: var(--color-primary);
+  padding: $-xxs $-m;
+  margin-bottom: 0;
+}
+
+.code-editor-language-list {
+  position: relative;
+  width: 160px;
+  z-index: 2;
+  align-items: stretch;
+}
+
+.code-editor-language-list input {
+  border-radius: 0;
+  border: 0;
+  border-bottom: 1px solid #DDD;
+  padding: $-xs $-m;
+}
+
+.code-editor-main {
+  flex: 1;
+  min-width: 0;
+  .CodeMirror {
+    margin-bottom: 0;
+    z-index: 1;
     max-width: 100%;
+    width: 100%;
+  }
+}
+
+@include smaller-than($s) {
+  .code-editor .lang-options {
+    display: none;
+  }
+  .code-editor-body-wrap {
+    flex-direction: column;
   }
-  .code-editor .CodeMirror {
-    height: 200px;
+  .code-editor-language-list, .code-editor-language-list input {
+    width: 100%;
   }
 }
 
@@ -731,6 +797,55 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
   }
 }
 
+
+.dropdown-search {
+  position: relative;
+}
+.dropdown-search-toggle-breadcrumb {
+  border: 1px solid transparent;
+  border-radius: 4px;
+  line-height: normal;
+  padding: $-xs;
+  &:hover {
+    border-color: #DDD;
+  }
+  .svg-icon {
+    margin-inline-end: 0;
+  }
+}
+.dropdown-search-toggle-select {
+  display: flex;
+  gap: $-s;
+  line-height: normal;
+  .svg-icon {
+    height: 16px;
+    margin: 0;
+  }
+  .avatar {
+    height: 22px;
+    width: 22px;
+  }
+  .avatar + span {
+    max-width: 100%;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+  .dropdown-search-toggle-caret {
+    font-size: 1.15rem;
+  }
+}
+.dropdown-search-toggle-select-label {
+  min-width: 0;
+  white-space: nowrap;
+}
+.dropdown-search-toggle-select-caret {
+  font-size: 1.5rem;
+  line-height: 0;
+  margin-left: auto;
+  margin-top: -2px;
+}
+
 .dropdown-search-dropdown {
   box-shadow: $bs-med;
   overflow: hidden;
@@ -739,7 +854,9 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
   display: none;
   position: absolute;
   z-index: 80;
-  right: -$-m;
+  right: 0;
+  top: 0;
+  margin-top: $-m;
   @include rtl {
     right: auto;
     left: -$-m;
@@ -767,12 +884,15 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
       text-decoration: none;
     }
   }
-  input {
+  input, input:focus {
     padding-inline-start: $-xl;
     border-radius: 0;
     border: 0;
     border-bottom: 1px solid #DDD;
   }
+  input:focus {
+    outline: 0;
+  }
 }
 
 @include smaller-than($m) {
@@ -784,10 +904,4 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
   .dropdown-search-dropdown .dropdown-search-list {
     max-height: 240px;
   }
-}
-
-.custom-select-input {
-  max-width: 280px;
-  border: 1px solid #D4D4D4;
-  border-radius: 3px;
 }
\ No newline at end of file
index 665b1213be8ba5a4401b3449c801f0c627d5c207..73799f0a03eaba71a5ad062e80b4a4c7a295c69f 100644 (file)
@@ -7,7 +7,8 @@
   @include lightDark(color, #666, #AAA);
   display: inline-block;
   font-size: $fs-m;
-  padding: $-xs*1.5;
+  padding: $-xs*1.8;
+  height: 40px;
   width: 250px;
   max-width: 100%;
 
@@ -350,16 +351,13 @@ input[type=color] {
   }
 }
 
-.inline-input-style {
+.title-input input[type="text"] {
   display: block;
   width: 100%;
   padding: $-s;
-}
-
-.title-input input[type="text"] {
-  @extend .inline-input-style;
   margin-top: 0;
   font-size: 2em;
+  height: auto;
 }
 
 .title-input.page-title {
@@ -373,6 +371,7 @@ input[type=color] {
     max-width: 840px;
     margin: 0 auto;
     border: none;
+    height: auto;
   }
 }
 
@@ -383,10 +382,12 @@ input[type=color] {
 }
 
 .description-input textarea {
-  @extend .inline-input-style;
+  display: block;
+  width: 100%;
+  padding: $-s;
   font-size: $fs-m;
   color: #666;
-  width: 100%;
+  height: auto;
 }
 
 div[editor-type="markdown"] .title-input.page-title input[type="text"] {
@@ -413,9 +414,11 @@ div[editor-type="markdown"] .title-input.page-title input[type="text"] {
   }
   input {
     display: block;
+    padding: $-xs * 1.5;
     padding-inline-start: $-l + 4px;
     width: 300px;
     max-width: 100%;
+    height: auto;
   }
   &.flexible input {
     width: 100%;
index f070f5a187428eedc0a2af37688733efa1af4a28..923f026c2663f3f9f09d301b89cb947c3c03188e 100644 (file)
@@ -21,19 +21,28 @@ header {
   color: rgb(250, 250, 250);
   border-bottom: 1px solid #DDD;
   box-shadow: $bs-card;
-  padding: $-xxs 0;
   @include lightDark(border-bottom-color, #DDD, #000);
   @include whenDark {
     filter: saturate(0.8) brightness(0.8);
   }
+  .header-links {
+    display: flex;
+    align-items: center;
+    justify-content: end;
+  }
   .links {
     display: inline-block;
     vertical-align: top;
   }
   .links a {
     display: inline-block;
-    padding: $-m;
+    padding: 10px $-m;
     color: #FFF;
+    border-radius: 3px;
+  }
+  .links a:hover {
+    text-decoration: none;
+    background-color: rgba(255, 255, 255, .15);
   }
   .dropdown-container {
     padding-inline-start: $-m;
@@ -49,19 +58,25 @@ header {
   .user-name {
     vertical-align: top;
     position: relative;
-    display: inline-block;
+    display: inline-flex;
+    align-items: center;
     cursor: pointer;
-    > * {
-      vertical-align: top;
-    }
+    padding: $-s;
+    margin: 0 (-$-s);
+    border-radius: 3px;
+    gap: $-xs;
     > span {
       padding-inline-start: $-xs;
       display: inline-block;
-      padding-top: $-xxs;
+      line-height: 1;
     }
     > svg {
-      padding-top: 4px;
       font-size: 18px;
+      margin-top: -2px;
+      margin-inline-end: 0;
+    }
+    &:hover {
+      background-color: rgba(255, 255, 255, 0.15);
     }
     @include between($l, $xl) {
       padding-inline-start: $-xs;
@@ -79,22 +94,26 @@ header {
 
 header .search-box {
   display: inline-block;
-  margin-top: 10px;
   input {
     background-color: rgba(0, 0, 0, 0.2);
     border: 1px solid rgba(255, 255, 255, 0.2);
     border-radius: 40px;
     color: #EEE;
     z-index: 2;
+    height: auto;
+    padding: $-xs*1.5;
     padding-inline-start: 40px;
     &:focus {
       outline: none;
-      border: 1px solid rgba(255, 255, 255, 0.6);
+      border: 1px solid rgba(255, 255, 255, 0.4);
     }
   }
   button {
     z-index: 1;
     left: 16px;
+    top: 10px;
+    color: #FFF;
+    opacity: 0.6;
     @include lightDark(color, rgba(255, 255, 255, 0.8), #AAA);
     @include rtl {
       left: auto;
@@ -104,36 +123,39 @@ header .search-box {
       margin-block-end: 0;
     }
   }
-  ::-webkit-input-placeholder { /* Chrome/Opera/Safari */
-    color: #DDD;
-  }
-  ::-moz-placeholder { /* Firefox 19+ */
-    color: #DDD;
+  input::placeholder {
+    color: #FFF;
+    opacity: 0.6;
   }
   @include between($l, $xl) {
     max-width: 200px;
   }
+  &:focus-within button {
+    opacity: 1;
+  }
 }
 
 .logo {
-  display: inline-block;
+  display: inline-flex;
+  padding: ($-s - 6px) $-s;
+  margin: 6px (-$-s);
+  gap: $-s;
+  align-items: center;
+  border-radius: 4px;
   &:hover {
     color: #FFF;
     text-decoration: none;
+    background-color: rgba(255, 255, 255, .15);
   }
 }
+
 .logo-text {
-  display: inline-block;
   font-size: 1.8em;
   color: #fff;
   font-weight: 400;
-  @include padding(14px, $-l, 14px, 0);
-  vertical-align: top;
   line-height: 1;
 }
 .logo-image {
-  @include margin($-xs, $-s, $-xs, 0);
-  vertical-align: top;
   height: 43px;
 }
 
@@ -172,23 +194,29 @@ header .search-box {
     overflow: hidden;
     position: absolute;
     box-shadow: $bs-hover;
-    margin-top: -$-xs;
+    margin-top: $-m;
+    padding: $-xs 0;
     &.show {
       display: block;
     }
   }
   header .links a, header .dropdown-container ul li a, header .dropdown-container ul li button {
     text-align: start;
-    display: block;
-    padding: $-s $-m;
+    display: grid;
+    align-items: center;
+    padding: 8px $-m;
+    gap: $-m;
     color: $text-dark;
+    grid-template-columns: 16px auto;
+    line-height: 1.4;
     @include lightDark(color, $text-dark, #eee);
     svg {
       margin-inline-end: $-s;
+      width: 16px;
     }
     &:hover {
-      @include lightDark(background-color, #eee, #333);
-      @include lightDark(color, #000, #fff);
+      background-color: var(--color-primary-light);
+      color: var(--color-primary);
       text-decoration: none;
     }
     &:focus {
@@ -279,29 +307,6 @@ header .search-box {
   }
 }
 
-.dropdown-search {
-  position: relative;
-  .dropdown-search-toggle {
-    padding: $-xs;
-    border: 1px solid transparent;
-    border-radius: 4px;
-    &:hover {
-      border-color: #DDD;
-    }
-  }
-  .svg-icon {
-    margin-inline-end: 0;
-  }
-}
-
-.dropdown-search-toggle.compact {
-  padding: $-xxs $-xs;
-  .avatar {
-    height: 22px;
-    width: 22px;
-  }
-}
-
 .faded {
   a, button, span, span > div {
     color: #666;
index b1c80cb53eb7e95b3e3d3ceb023392ffad54a644..14a37dd4a2465777572e7787695d69ac690f1e26 100644 (file)
@@ -155,6 +155,13 @@ body.flexbox {
   }
 }
 
+.gap-m {
+  gap: $-m;
+}
+
+.justify-flex-start {
+  justify-content: flex-start;
+}
 .justify-flex-end {
   justify-content: flex-end;
 }
@@ -295,9 +302,9 @@ body.flexbox {
 }
 @include larger-than($xxl) {
   .tri-layout-left-contents, .tri-layout-right-contents {
-    padding: $-m;
+    padding: $-xl $-m;
     position: sticky;
-    top: $-m;
+    top: 0;
     max-height: 100vh;
     min-height: 50vh;
     overflow-y: scroll;
index 26d12a25da42949cbced533fd25ac66caac1bf9c..19060fbbf5d3b02e0129656b40f6d5ccd94d46fe 100644 (file)
@@ -6,7 +6,7 @@
     justify-self: stretch;
     align-self: stretch;
     height: auto;
-    margin-inline-end: $-l;
+    margin-inline-end: $-xs;
   }
   .icon:after {
     opacity: 0.5;
   > .content {
     flex: 1;
   }
-  .chapter-expansion-toggle {
+  .chapter-contents-toggle {
     border-radius: 0 4px 4px 0;
-    padding: $-xs $-m;
+    padding: $-xs ($-m + $-xxs);
     width: 100%;
     text-align: start;
   }
-  .chapter-expansion-toggle:hover {
+  .chapter-contents-toggle:hover {
     background-color: rgba(0, 0, 0, 0.06);
   }
 }
   @include margin($-xs, -$-s, 0, -$-s);
   padding-inline-start: 0;
   padding-inline-end: 0;
-  position: relative;
-
-  &:after, .sub-menu:after {
-    content: '';
-    display: block;
-    position: absolute;
-    left: $-m;
-    top: 1rem;
-    bottom: 1rem;
-    border-inline-start: 4px solid rgba(0, 0, 0, 0.1);
-    z-index: 0;
-    @include rtl {
-      left: auto;
-      right: $-m;
-    }
-  }
 
   ul {
     list-style: none;
   }
 
   .entity-list-item {
-    padding-top: $-xxs;
-    padding-bottom: $-xxs;
+    padding-top: 2px;
+    padding-bottom: 2px;
     background-clip: content-box;
     border-radius: 0 3px 3px 0;
     padding-inline-end: 0;
     .content {
+      width: 100%;
       padding-top: $-xs;
       padding-bottom: $-xs;
       max-width: calc(100% - 20px);
     }
   }
   .entity-list-item.selected {
-    @include lightDark(background-color, rgba(0, 0, 0, 0.08), rgba(255, 255, 255, 0.08));
+    @include lightDark(background-color, rgba(0, 0, 0, 0.06), rgba(255, 255, 255, 0.06));
   }
   .entity-list-item.no-hover {
     margin-top: -$-xs;
     margin-top: -.2rem;
     margin-inline-start: -1rem;
   }
-  [chapter-toggle] {
-    padding-inline-start: .7rem;
-    padding-bottom: .2rem;
+  .chapter-contents-toggle {
+    display: block;
+    width: 100%;
+    text-align: left;
+    padding: $-xxs $-s ($-xxs * 2) $-s;
+    border-radius: 0 3px 3px 0;
+    line-height: 1;
+    margin-top: -$-xxs;
+    margin-bottom: -$-xxs;
+    &:hover {
+      @include lightDark(background-color, rgba(0, 0, 0, 0.06), rgba(255, 255, 255, 0.06));
+    }
   }
   .entity-list-item .icon {
     z-index: 2;
     align-self: stretch;
     flex-shrink: 0;
     border-radius: 1px;
-    opacity: 0.6;
+    opacity: 0.8;
   }
   .entity-list-item .icon:after {
     opacity: 1;
   }
 }
 
-.chapter-child-menu {
-  ul.sub-menu {
-    display: none;
-    padding-inline-start: 0;
-    position: relative;
-  }
-  [chapter-toggle].open + .sub-menu {
-    display: block;
-  }
+.chapter-child-menu ul.sub-menu {
+  display: none;
+  padding-inline-start: 0;
+  position: relative;
+  margin-bottom: 0;
 }
 
 // Sortable Lists
@@ -415,6 +405,7 @@ ul.pagination {
   padding: $-s $-m;
   display: flex;
   align-items: center;
+  gap: $-m;
   background-color: transparent;
   border: 0;
   width: 100%;
@@ -424,7 +415,6 @@ ul.pagination {
     color: #666;
   }
   > span:first-child {
-    margin-inline-end: $-m;
     flex-basis: 1.88em;
     flex: none;
   }
@@ -439,8 +429,8 @@ ul.pagination {
     cursor: pointer;
   }
   &:not(.no-hover):hover {
+    @include lightDark(background-color, rgba(0, 0, 0, 0.06), rgba(255, 255, 255, 0.06));
     text-decoration: none;
-    background-color: rgba(0, 0, 0, 0.1);
     border-radius: 4px;
   }
   &.outline-hover:hover {
@@ -463,19 +453,74 @@ ul.pagination {
   }
 }
 
-.card .entity-list-item:not(.no-hover):hover {
-  @include lightDark(background-color, #F2F2F2, #2d2d2d)
+.split-icon-list-item {
+  display: flex;
+  align-items: center;
+  gap: $-m;
+  background-color: transparent;
+  border: 0;
+  width: 100%;
+  position: relative;
+  word-break: break-word;
+  border-radius: 4px;
+  > a {
+    padding: $-s $-m;
+    display: flex;
+    align-items: center;
+    gap: $-m;
+    flex: 1;
+  }
+  > a:hover {
+    text-decoration: none;
+  }
+  .icon {
+    flex-basis: 1.88em;
+    flex: none;
+  }
+  &:hover {
+    @include lightDark(background-color, rgba(0, 0, 0, 0.06), rgba(255, 255, 255, 0.06));
+  }
+}
+
+.icon-list-item-dropdown {
+  margin-inline-start: auto;
+  align-self: stretch;
+  display: flex;
+  align-items: stretch;
+  border-inline-start: 1px solid rgba(0, 0, 0, .1);
+  visibility: hidden;
+}
+.split-icon-list-item:hover .icon-list-item-dropdown,
+.split-icon-list-item:focus-within .icon-list-item-dropdown {
+  visibility: visible;
+}
+.icon-list-item-dropdown-toggle {
+  padding: $-xs;
+  display: flex;
+  align-items: center;
+  cursor: pointer;
+  @include lightDark(color, #888, #999);
+  svg {
+    margin: 0;
+  }
+  &:hover {
+    @include lightDark(background-color, rgba(0, 0, 0, 0.06), rgba(255, 255, 255, 0.06));
+  }
+}
+
+.card .entity-list-item:not(.no-hover, .book-contents .entity-list-item):hover {
+  @include lightDark(background-color, #F2F2F2, #2d2d2d);
+  border-radius: 0;
 }
 .card .entity-list-item .entity-list-item:hover {
   background-color: #EEEEEE;
 }
 
 .entity-list-item-children {
-  padding: $-m;
+  padding: $-m $-l;
   > div {
     overflow: hidden;
-    padding: $-xs 0;
-    margin-top: -$-xs;
+    padding: 0 0 $-xs 0;
   }
   .entity-chip {
     text-overflow: ellipsis;
@@ -485,6 +530,9 @@ ul.pagination {
     display: block;
     white-space: nowrap;
   }
+  > .entity-list > .entity-list-item:last-child {
+    margin-bottom: -$-xs;
+  }
 }
 
 .entity-list-item-image {
@@ -531,6 +579,9 @@ ul.pagination {
     font-size: $fs-m * 0.8;
     padding-top: $-xs;
   }
+  .entity-list-item p:empty {
+    padding-top: 0;
+  }
   p {
     margin: 0;
   }
@@ -574,8 +625,8 @@ ul.pagination {
   right: 0;
   margin: $-m 0;
   @include lightDark(background-color, #fff, #333);
-  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.18);
-  border-radius: 1px;
+  box-shadow: 0 1px 6px 0 rgba(0, 0, 0, 0.18);
+  border-radius: 3px;
   min-width: 180px;
   padding: $-xs 0;
   @include lightDark(color, #555, #eee);
@@ -652,6 +703,13 @@ ul.pagination {
   }
 }
 
+// Shift in sidebar dropdown menus to prevent shadows
+// being cut by scrollable container.
+.tri-layout-right .dropdown-menu,
+.tri-layout-left .dropdown-menu {
+  right: $-xs;
+}
+
 // Books grid view
 .featured-image-container {
   position: relative;
@@ -719,3 +777,19 @@ ul.pagination {
     }
   }
 }
+
+.entity-meta-item {
+  display: flex;
+  line-height: 1.2;
+  margin: 0.6em 0;
+  align-content: start;
+  gap: $-s;
+  a {
+    line-height: 1.2;
+  }
+  svg {
+    flex-shrink: 0;
+    width: 1em;
+    margin: 0;
+  }
+}
index 73819975fb6150fb9ffd66354f93a665416b533d..3ceec61d091245ada86bc0f81c6dbcd5f02fa8df 100755 (executable)
@@ -396,10 +396,14 @@ body.tox-fullscreen, body.markdown-fullscreen {
   }
 }
 
-.entity-list-item > span:first-child, .icon-list-item > span:first-child, .chapter-expansion > .icon {
+.entity-list-item > span:first-child,
+.icon-list-item > span:first-child,
+.split-icon-list-item > a > .icon,
+.chapter-expansion > .icon {
   font-size: 0.8rem;
   width: 1.88em;
   height: 1.88em;
+  flex-shrink: 0;
   display: flex;
   align-items: center;
   justify-content: center;
index 1a8b34c5b9af119865929d1eebde78c8180d2a99..b8160b4c2df575e594b347b0e811dbf8a425fe4b 100644 (file)
@@ -59,4 +59,47 @@ ul.contents ul li {
 }
 .chapter-hint + h1 {
   margin-top: 0;
+}
+
+// PDF specific overrides
+body.export-format-pdf {
+  font-size: 14px;
+  line-height: 1.2;
+
+  h1, h2, h3, h4, h5, h6 {
+    line-height: 1.2;
+  }
+
+  table {
+    max-width: 800px !important;
+    font-size: 0.8em;
+    width: 100% !important;
+  }
+
+  table td {
+    width: auto !important;
+  }
+
+  .page-content .float {
+    float: none !important;
+  }
+
+  .page-content img.align-left, .page-content img.align-right  {
+    float: none !important;
+    clear: both;
+    display: block;
+  }
+
+}
+
+// DOMPDF pdf export specific overrides
+body.export-format-pdf.export-engine-dompdf {
+  // Fix for full width linked image sizes on DOMPDF
+  .page-content a > img {
+    max-width: 700px;
+  }
+  // Undoes the above for table images to prevent visually worse scenario, Awaiting next DOMPDF release for patch
+  .page-content td a > img {
+    max-width: 100%;
+  }
 }
\ No newline at end of file
index 582bf7c7569a7faa4a55b7fc1d159e31cd490db6..ee99d76682c7edf1b8601bc037d0d5f033f2bfc4 100644 (file)
@@ -79,17 +79,17 @@ $loadingSize: 10px;
     animation-timing-function: cubic-bezier(.62, .28, .23, .99);
     margin-inline-end: 4px;
     background-color: var(--color-page);
-    animation-delay: 0.3s;
+    animation-delay: -300ms;
   }
   > div:first-child {
       left: -($loadingSize+$-xs);
       background-color: var(--color-book);
-      animation-delay: 0s;
+      animation-delay: -600ms;
   }
   > div:last-of-type {
     left: $loadingSize+$-xs;
     background-color: var(--color-chapter);
-    animation-delay: 0.6s;
+    animation-delay: 0ms;
   }
   > span {
     margin-inline-start: $-s;
@@ -138,7 +138,7 @@ $btt-size: 40px;
 
 .skip-to-content-link {
   position: fixed;
-  top: -$-xxl;
+  top: -52px;
   left: 0;
   background-color: #FFF;
   z-index: 15;
index 5bec265e82b9d4704839655f18a3f9db365c76fd..52ad9f8f4aa351d70acc8fabeaface4ca5ae83ef 100644 (file)
@@ -15,6 +15,7 @@
                         <div class="mb-xs"><a href="#request-format">Request Format</a></div>
                         <div class="mb-xs"><a href="#listing-endpoints">Listing Endpoints</a></div>
                         <div class="mb-xs"><a href="#error-handling">Error Handling</a></div>
+                        <div class="mb-xs"><a href="#rate-limits">Rate Limits</a></div>
                     </div>
 
                     @foreach($docs as $model => $endpoints)
index 3bcf29dd4e9e3552cb1368487df3ec9ce4de2d7f..edc526971e214e146321ddb188f185d0fa625518 100644 (file)
                "message": "No authorization token found on the request"
        }
 }
-</code></pre>
\ No newline at end of file
+</code></pre>
+
+<hr>
+
+<h5 id="rate-limits" class="text-mono mb-m">Rate Limits</h5>
+<p>
+    The API has built-in per-user rate-limiting to prevent potential abuse using the API.
+    By default, this is set to 180 requests per minute but this can be changed by an administrator
+    by setting an "API_REQUESTS_PER_MIN" .env option like so:
+</p>
+
+<pre><code class="language-bash"># The number of API requests that can be made per minute by a single user.
+API_REQUESTS_PER_MIN=180</code></pre>
+
+<p>
+    When the limit is reached you will receive a 429 "Too Many Attempts." error response.
+    It's generally good practice to limit requests made from your API client, where possible, to avoid
+    affecting normal use of the system caused by over-consuming system resources.
+    Keep in mind there may be other rate-limiting factors such as web-server & firewall controls.
+</p>
\ No newline at end of file
index f0a1354ea1e513adda8ad44b1c8e03ed63c0a2a2..a6ffb709b4dc097ecf4b14d0785aa7e585b77e94 100644 (file)
@@ -1,10 +1,27 @@
 <div component="attachments-list">
     @foreach($attachments as $attachment)
         <div class="attachment icon-list">
-            <a class="icon-list-item py-xs attachment-{{ $attachment->external ? 'link' : 'file' }}" href="{{ $attachment->getUrl() }}" @if($attachment->external) target="_blank" @endif>
-                <span class="icon">@icon($attachment->external ? 'export' : 'file')</span>
-                <span>{{ $attachment->name }}</span>
-            </a>
+            <div class="split-icon-list-item attachment-{{ $attachment->external ? 'link' : 'file' }}">
+                <a href="{{ $attachment->getUrl() }}" @if($attachment->external) target="_blank" @endif>
+                    <div class="icon">@icon($attachment->external ? 'export' : 'file')</div>
+                    <div class="label">{{ $attachment->name }}</div>
+                </a>
+                @if(!$attachment->external)
+                    <div component="dropdown" class="icon-list-item-dropdown">
+                        <button refs="dropdown@toggle" type="button" class="icon-list-item-dropdown-toggle">@icon('caret-down')</button>
+                        <ul refs="dropdown@menu" class="dropdown-menu" role="menu">
+                            <a href="{{ $attachment->getUrl(false) }}" class="icon-item">
+                                @icon('download')
+                                <div>{{ trans('common.download') }}</div>
+                            </a>
+                            <a href="{{ $attachment->getUrl(true) }}" target="_blank" class="icon-item">
+                                @icon('export')
+                                <div>{{ trans('common.open_in_tab') }}</div>
+                            </a>
+                        </ul>
+                    </div>
+                @endif
+            </div>
         </div>
     @endforeach
 </div>
\ No newline at end of file
index 4039771213d49e57d9d6e9924c00b67ebee25c8e..180500e0a04ae737b1f86668d7f8a41c070919e9 100644 (file)
             ]])
         </div>
 
-        <main class="content-wrap card">
+        <main class="content-wrap card auto-height">
             <h1 class="list-heading">{{ trans('entities.books_edit') }}</h1>
             <form action="{{ $book->getUrl() }}" method="POST" enctype="multipart/form-data">
                 <input type="hidden" name="_method" value="PUT">
                 @include('books.parts.form', ['model' => $book, 'returnLocation' => $book->getUrl()])
             </form>
         </main>
+
+
+        @if(userCan('book-delete', $book) && userCan('book-create-all') && userCan('bookshelf-create-all'))
+            @include('books.parts.convert-to-shelf', ['book' => $book])
+        @endif
     </div>
 @stop
\ No newline at end of file
diff --git a/resources/views/books/export.blade.php b/resources/views/books/export.blade.php
deleted file mode 100644 (file)
index 0b6b4a5..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-@extends('layouts.export')
-
-@section('title', $book->name)
-
-@section('content')
-    <h1 style="font-size: 4.8em">{{$book->name}}</h1>
-
-    <p>{{ $book->description }}</p>
-
-    @if(count($bookChildren) > 0)
-        <ul class="contents">
-            @foreach($bookChildren as $bookChild)
-                <li><a href="#{{$bookChild->getType()}}-{{$bookChild->id}}">{{ $bookChild->name }}</a></li>
-                @if($bookChild->isA('chapter') && count($bookChild->visible_pages) > 0)
-                    <ul>
-                        @foreach($bookChild->visible_pages as $page)
-                            <li><a href="#page-{{$page->id}}">{{ $page->name }}</a></li>
-                        @endforeach
-                    </ul>
-                @endif
-            @endforeach
-        </ul>
-    @endif
-
-    @foreach($bookChildren as $bookChild)
-        <div class="page-break"></div>
-        <h1 id="{{$bookChild->getType()}}-{{$bookChild->id}}">{{ $bookChild->name }}</h1>
-
-        @if($bookChild->isA('chapter'))
-            <p>{{ $bookChild->description }}</p>
-
-            @if(count($bookChild->visible_pages) > 0)
-                @foreach($bookChild->visible_pages as $page)
-                    <div class="page-break"></div>
-                    <div class="chapter-hint">{{$bookChild->name}}</div>
-                    <h1 id="page-{{$page->id}}">{{ $page->name }}</h1>
-                    {!! $page->html !!}
-                @endforeach
-            @endif
-
-        @else
-            {!! $bookChild->html !!}
-        @endif
-
-    @endforeach
-@endsection
\ No newline at end of file
diff --git a/resources/views/books/parts/convert-to-shelf.blade.php b/resources/views/books/parts/convert-to-shelf.blade.php
new file mode 100644 (file)
index 0000000..dde60aa
--- /dev/null
@@ -0,0 +1,26 @@
+<div class="content-wrap card auto-height">
+    <h2 class="list-heading">{{ trans('entities.convert_to_shelf') }}</h2>
+    <p>
+        {{ trans('entities.convert_to_shelf_contents_desc') }}
+        <br><br>
+        {{ trans('entities.convert_to_shelf_permissions_desc') }}
+    </p>
+    <div class="text-right">
+        <div component="dropdown" class="dropdown-container">
+            <button refs="dropdown@toggle" class="button outline" aria-haspopup="true" aria-expanded="false">{{ trans('entities.convert_book') }}</button>
+            <ul refs="dropdown@menu" class="dropdown-menu" role="menu">
+                <li class="px-m py-s text-small text-muted">
+                    {{ trans('entities.convert_book_confirm') }}
+                    <br>
+                    {{ trans('entities.convert_undo_warning') }}
+                </li>
+                <li>
+                    <form action="{{ $book->getUrl('/convert-to-shelf') }}" method="POST">
+                        {!! csrf_field() !!}
+                        <button type="submit" class="text-primary text-item">{{ trans('common.confirm') }}</button>
+                    </form>
+                </li>
+            </ul>
+        </div>
+    </div>
+</div>
\ No newline at end of file
index 5263bc8101007d6196a1f9bff34dc462d490e7e0..e0cb4b862dd8327179c4b1655f6d574d1886c36f 100644 (file)
 @section('right')
     <div class="mb-xl">
         <h5>{{ trans('common.details') }}</h5>
-        <div class="text-small text-muted blended-links">
+        <div class="blended-links">
             @include('entities.meta', ['entity' => $book])
             @if($book->restricted)
                 <div class="active-restriction">
                     @if(userCan('restrictions-manage', $book))
-                        <a href="{{ $book->getUrl('/permissions') }}">@icon('lock'){{ trans('entities.books_permissions_active') }}</a>
+                        <a href="{{ $book->getUrl('/permissions') }}" class="entity-meta-item">
+                            @icon('lock')
+                            <div>{{ trans('entities.books_permissions_active') }}</div>
+                        </a>
                     @else
-                        @icon('lock'){{ trans('entities.books_permissions_active') }}
+                        <div class="entity-meta-item">
+                            @icon('lock')
+                            <div>{{ trans('entities.books_permissions_active') }}</div>
+                        </div>
                     @endif
                 </div>
             @endif
index 65c48c18d77aba368a7af0872ad273d672e764a2..36058eff83bb090428133cb1a13744f5e6fa7646 100644 (file)
@@ -15,7 +15,7 @@
             ]])
         </div>
 
-        <main class="content-wrap card">
+        <main class="content-wrap card auto-height">
             <h1 class="list-heading">{{ trans('entities.chapters_edit') }}</h1>
             <form action="{{  $chapter->getUrl() }}" method="POST">
                 <input type="hidden" name="_method" value="PUT">
             </form>
         </main>
 
+        @if(userCan('chapter-delete', $chapter) && userCan('book-create-all'))
+            @include('chapters.parts.convert-to-book')
+        @endif
+
     </div>
 
 @stop
\ No newline at end of file
diff --git a/resources/views/chapters/export.blade.php b/resources/views/chapters/export.blade.php
deleted file mode 100644 (file)
index 61286ab..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-@extends('layouts.export')
-
-@section('title', $chapter->name)
-
-@section('content')
-    <h1 style="font-size: 4.8em">{{$chapter->name}}</h1>
-
-    <p>{{ $chapter->description }}</p>
-
-    @if(count($pages) > 0)
-        <ul class="contents">
-            @foreach($pages as $page)
-                <li><a href="#page-{{$page->id}}">{{ $page->name }}</a></li>
-            @endforeach
-        </ul>
-    @endif
-
-    @foreach($pages as $page)
-        <div class="page-break"></div>
-        <h1 id="page-{{$page->id}}">{{ $page->name }}</h1>
-        {!! $page->html !!}
-    @endforeach
-@endsection
\ No newline at end of file
index a00f0f7e1ae341c3a6638fe091c153118e7c9baf..8fdd091437beacdaca563b2330601b008f3bbc1d 100644 (file)
@@ -1,9 +1,14 @@
-<div class="chapter-child-menu">
-    <button chapter-toggle type="button" aria-expanded="{{ $isOpen ? 'true' : 'false' }}"
-            class="text-muted @if($isOpen) open @endif">
+<div component="chapter-contents" class="chapter-child-menu">
+    <button type="button"
+            refs="chapter-contents@toggle"
+            aria-expanded="{{ $isOpen ? 'true' : 'false' }}"
+            class="text-muted chapter-contents-toggle @if($isOpen) open @endif">
         @icon('caret-right') @icon('page') <span>{{ trans_choice('entities.x_pages', $bookChild->visible_pages->count()) }}</span>
     </button>
-    <ul class="sub-menu inset-list @if($isOpen) open @endif" @if($isOpen) style="display: block;" @endif role="menu">
+    <ul refs="chapter-contents@list"
+        class="chapter-contents-list sub-menu inset-list @if($isOpen) open @endif" @if($isOpen)
+        style="display: block;" @endif
+        role="menu">
         @foreach($bookChild->visible_pages as $childPage)
             <li class="list-item-page {{ $childPage->isA('page') && $childPage->draft ? 'draft' : '' }}" role="presentation">
                 @include('entities.list-item-basic', ['entity' => $childPage, 'classes' => $current->matches($childPage)? 'selected' : '' ])
diff --git a/resources/views/chapters/parts/convert-to-book.blade.php b/resources/views/chapters/parts/convert-to-book.blade.php
new file mode 100644 (file)
index 0000000..516d111
--- /dev/null
@@ -0,0 +1,28 @@
+<div class="content-wrap card auto-height">
+    <h2 class="list-heading">{{ trans('entities.convert_to_book') }}</h2>
+    <div class="grid half left-focus no-row-gap">
+        <p>
+            {{ trans('entities.convert_to_book_desc') }}
+        </p>
+        <div class="text-m-right">
+            <div component="dropdown" class="dropdown-container">
+                <button refs="dropdown@toggle" class="button outline" aria-haspopup="true" aria-expanded="false">
+                    {{ trans('entities.convert_chapter') }}
+                </button>
+                <ul refs="dropdown@menu" class="dropdown-menu" role="menu">
+                    <li class="px-m py-s text-small text-muted">
+                        {{ trans('entities.convert_chapter_confirm') }}
+                        <br>
+                        {{ trans('entities.convert_undo_warning') }}
+                    </li>
+                    <li>
+                        <form action="{{ $chapter->getUrl('/convert-to-book') }}" method="POST">
+                            {!! csrf_field() !!}
+                            <button type="submit" class="text-primary text-item">{{ trans('common.confirm') }}</button>
+                        </form>
+                    </li>
+                </ul>
+            </div>
+        </div>
+    </div>
+</div>
\ No newline at end of file
index 285e3489353cca255481e32241245f856527aae0..c3e735e2bfc43c703f8f80f885c92c238a3e24d1 100644 (file)
@@ -5,18 +5,19 @@
     <div class="content">
         <h4 class="entity-list-item-name break-text">{{ $chapter->name }}</h4>
         <div class="entity-item-snippet">
-            <p class="text-muted break-text mb-s">{{ $chapter->getExcerpt() }}</p>
+            <p class="text-muted break-text">{{ $chapter->getExcerpt() }}</p>
         </div>
     </div>
 </a>
 @if ($chapter->visible_pages->count() > 0)
     <div class="chapter chapter-expansion">
         <span class="icon text-chapter">@icon('page')</span>
-        <div class="content">
-            <button type="button" chapter-toggle
+        <div component="chapter-contents" class="content">
+            <button type="button"
+                    refs="chapter-contents@toggle"
                     aria-expanded="false"
-                    class="text-muted chapter-expansion-toggle">@icon('caret-right') <span>{{ trans_choice('entities.x_pages', $chapter->visible_pages->count()) }}</span></button>
-            <div class="inset-list">
+                    class="text-muted chapter-contents-toggle">@icon('caret-right') <span>{{ trans_choice('entities.x_pages', $chapter->visible_pages->count()) }}</span></button>
+            <div refs="chapter-contents@list" class="inset-list chapter-contents-list">
                 <div class="entity-list-item-children">
                     @include('entities.list', ['entities' => $chapter->visible_pages])
                 </div>
index edd39eddebed863b45c55b95396adfaae6a9880a..3e015616ad34384e1cf0f633543f4785e4d86844 100644 (file)
 
     <div class="mb-xl">
         <h5>{{ trans('common.details') }}</h5>
-        <div class="blended-links text-small text-muted">
+        <div class="blended-links">
             @include('entities.meta', ['entity' => $chapter])
 
             @if($book->restricted)
                 <div class="active-restriction">
                     @if(userCan('restrictions-manage', $book))
-                        <a href="{{ $book->getUrl('/permissions') }}">@icon('lock'){{ trans('entities.books_permissions_active') }}</a>
+                        <a href="{{ $book->getUrl('/permissions') }}" class="entity-meta-item">
+                            @icon('lock')
+                            <div>{{ trans('entities.books_permissions_active') }}</div>
+                        </a>
                     @else
-                        @icon('lock'){{ trans('entities.books_permissions_active') }}
+                        <div class="entity-meta-item">
+                            @icon('lock')
+                            <div>{{ trans('entities.books_permissions_active') }}</div>
+                        </div>
                     @endif
                 </div>
             @endif
             @if($chapter->restricted)
                 <div class="active-restriction">
                     @if(userCan('restrictions-manage', $chapter))
-                        <a href="{{ $chapter->getUrl('/permissions') }}">@icon('lock'){{ trans('entities.chapters_permissions_active') }}</a>
+                        <a href="{{ $chapter->getUrl('/permissions') }}" class="entity-meta-item">
+                            @icon('lock')
+                            <div>{{ trans('entities.chapters_permissions_active') }}</div>
+                        </a>
                     @else
-                        @icon('lock'){{ trans('entities.chapters_permissions_active') }}
+                        <div class="entity-meta-item">
+                            @icon('lock')
+                            <div>{{ trans('entities.chapters_permissions_active') }}</div>
+                        </div>
                     @endif
                 </div>
             @endif
index 28587d4e8c0f8e09a4d56b6b47152087006df0cf..8e4148b88716e9b6be23029b351447e749012161 100644 (file)
@@ -5,7 +5,7 @@
 
         <div class="popup-header primary-background">
             <div class="popup-title">{{ $title }}</div>
-            <button refs="popup@hide" type="button" class="popup-header-close">x</button>
+            <button refs="popup@hide" type="button" class="popup-header-close">@icon('close')</button>
         </div>
 
         <div class="px-m py-m">
diff --git a/resources/views/common/export-styles.blade.php b/resources/views/common/export-styles.blade.php
deleted file mode 100644 (file)
index 1dfa6bb..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-<style>
-    @if (!app()->runningUnitTests())
-        {!! file_get_contents(public_path('/dist/export-styles.css')) !!}
-    @endif
-</style>
-
-@if ($format === 'pdf')
-    <style>
-
-        /* PDF size adjustments */
-        body {
-            font-size: 14px;
-            line-height: 1.2;
-        }
-
-        h1, h2, h3, h4, h5, h6 {
-            line-height: 1.2;
-        }
-
-        table {
-            max-width: 800px !important;
-            font-size: 0.8em;
-            width: 100% !important;
-        }
-
-        table td {
-            width: auto !important;
-        }
-
-        /* Patches for CSS variable colors */
-        a {
-            color: {{ setting('app-color') }};
-        }
-
-        blockquote {
-            border-left-color: {{ setting('app-color') }};
-        }
-
-        /* Patches for content layout */
-        .page-content .float {
-            float: none !important;
-        }
-
-        .page-content img.align-left, .page-content img.align-right  {
-            float: none !important;
-            clear: both;
-            display: block;
-        }
-
-        @if($engine === \BookStack\Entities\Tools\PdfGenerator::ENGINE_DOMPDF)
-        {{-- Fix for full width linked image sizes on DOMPDF --}}
-        .page-content a > img {
-            max-width: 700px;
-        }
-        {{-- Undoes the above for table images to prevent visually worse scenario, Awaiting next DOMPDF release for patch --}}
-        .page-content td a > img {
-            max-width: 100%;
-        }
-        @endif
-    </style>
-@endif
\ No newline at end of file
index b5ac520c18f37755388da0be91cd2a43cf01bc11..197b80c27ec10c20da9399bd195d0e190d8e69e8 100644 (file)
@@ -17,7 +17,7 @@
                     class="mobile-menu-toggle hide-over-l">@icon('more')</button>
         </div>
 
-        <div class="flex-container-row justify-center hide-under-l">
+        <div class="flex-container-column items-center justify-center hide-under-l">
             @if (hasAppAccess())
             <form action="{{ url('/search') }}" method="GET" class="search-box" role="search">
                 <button id="header-search-box-button" type="submit" aria-label="{{ trans('common.search') }}" tabindex="-1">@icon('search') </button>
             @endif
         </div>
 
-        <div class="text-right">
-            <nav refs="header-mobile-toggle@menu" class="header-links">
-                <div class="links text-center">
-                    @if (hasAppAccess())
-                        <a class="hide-over-l" href="{{ url('/search') }}">@icon('search'){{ trans('common.search') }}</a>
-                        @if(userCanOnAny('view', \BookStack\Entities\Models\Bookshelf::class) || userCan('bookshelf-view-all') || userCan('bookshelf-view-own'))
-                            <a href="{{ url('/shelves') }}">@icon('bookshelf'){{ trans('entities.shelves') }}</a>
-                        @endif
-                        <a href="{{ url('/books') }}">@icon('books'){{ trans('entities.books') }}</a>
-                        @if(signedInUser() && userCan('settings-manage'))
-                            <a href="{{ url('/settings') }}">@icon('settings'){{ trans('settings.settings') }}</a>
-                        @endif
-                        @if(signedInUser() && userCan('users-manage') && !userCan('settings-manage'))
-                            <a href="{{ url('/settings/users') }}">@icon('users'){{ trans('settings.users') }}</a>
-                        @endif
+        <nav refs="header-mobile-toggle@menu" class="header-links">
+            <div class="links text-center">
+                @if (hasAppAccess())
+                    <a class="hide-over-l" href="{{ url('/search') }}">@icon('search'){{ trans('common.search') }}</a>
+                    @if(userCanOnAny('view', \BookStack\Entities\Models\Bookshelf::class) || userCan('bookshelf-view-all') || userCan('bookshelf-view-own'))
+                        <a href="{{ url('/shelves') }}">@icon('bookshelf'){{ trans('entities.shelves') }}</a>
                     @endif
+                    <a href="{{ url('/books') }}">@icon('books'){{ trans('entities.books') }}</a>
+                    @if(signedInUser() && userCan('settings-manage'))
+                        <a href="{{ url('/settings') }}">@icon('settings'){{ trans('settings.settings') }}</a>
+                    @endif
+                    @if(signedInUser() && userCan('users-manage') && !userCan('settings-manage'))
+                        <a href="{{ url('/settings/users') }}">@icon('users'){{ trans('settings.users') }}</a>
+                    @endif
+                @endif
 
-                    @if(!signedInUser())
-                        @if(setting('registration-enabled') && config('auth.method') === 'standard')
-                            <a href="{{ url('/register') }}">@icon('new-user'){{ trans('auth.sign_up') }}</a>
-                        @endif
-                        <a href="{{ url('/login')  }}">@icon('login'){{ trans('auth.log_in') }}</a>
+                @if(!signedInUser())
+                    @if(setting('registration-enabled') && config('auth.method') === 'standard')
+                        <a href="{{ url('/register') }}">@icon('new-user'){{ trans('auth.sign_up') }}</a>
                     @endif
-                </div>
-                @if(signedInUser())
-                    <?php $currentUser = user(); ?>
-                    <div class="dropdown-container" component="dropdown" option:dropdown:bubble-escapes="true">
+                    <a href="{{ url('/login')  }}">@icon('login'){{ trans('auth.log_in') }}</a>
+                @endif
+            </div>
+            @if(signedInUser())
+                <?php $currentUser = user(); ?>
+                <div class="dropdown-container" component="dropdown" option:dropdown:bubble-escapes="true">
                         <span class="user-name py-s hide-under-l" refs="dropdown@toggle"
                               aria-haspopup="true" aria-expanded="false" aria-label="{{ trans('common.profile_menu') }}" tabindex="0">
                             <img class="avatar" src="{{$currentUser->getAvatar(30)}}" alt="{{ $currentUser->name }}">
                             <span class="name">{{ $currentUser->getShortName(9) }}</span> @icon('caret-down')
                         </span>
-                        <ul refs="dropdown@menu" class="dropdown-menu" role="menu">
-                            <li>
-                                <a href="{{ url('/favourites') }}" class="icon-item">
-                                    @icon('star')
-                                    <div>{{ trans('entities.my_favourites') }}</div>
-                                </a>
-                            </li>
-                            <li>
-                                <a href="{{ $currentUser->getProfileUrl() }}" class="icon-item">
-                                    @icon('user')
-                                    <div>{{ trans('common.view_profile') }}</div>
-                                </a>
-                            </li>
-                            <li>
-                                <a href="{{ $currentUser->getEditUrl() }}" class="icon-item">
-                                    @icon('edit')
-                                    <div>{{ trans('common.edit_profile') }}</div>
-                                </a>
-                            </li>
-                            <li>
-                                <form action="{{ url(config('auth.method') === 'saml2' ? '/saml2/logout' : '/logout') }}"
-                                      method="post">
-                                    {{ csrf_field() }}
-                                    <button class="icon-item">
-                                        @icon('logout')
-                                        <div>{{ trans('auth.logout') }}</div>
-                                    </button>
-                                </form>
-                            </li>
-                            <li><hr></li>
-                            <li>
-                                @include('common.dark-mode-toggle', ['classes' => 'icon-item'])
-                            </li>
-                        </ul>
-                    </div>
-                @endif
-            </nav>
-        </div>
+                    <ul refs="dropdown@menu" class="dropdown-menu" role="menu">
+                        <li>
+                            <a href="{{ url('/favourites') }}" class="icon-item">
+                                @icon('star')
+                                <div>{{ trans('entities.my_favourites') }}</div>
+                            </a>
+                        </li>
+                        <li>
+                            <a href="{{ $currentUser->getProfileUrl() }}" class="icon-item">
+                                @icon('user')
+                                <div>{{ trans('common.view_profile') }}</div>
+                            </a>
+                        </li>
+                        <li>
+                            <a href="{{ $currentUser->getEditUrl() }}" class="icon-item">
+                                @icon('edit')
+                                <div>{{ trans('common.edit_profile') }}</div>
+                            </a>
+                        </li>
+                        <li>
+                            <form action="{{ url(config('auth.method') === 'saml2' ? '/saml2/logout' : '/logout') }}"
+                                  method="post">
+                                {{ csrf_field() }}
+                                <button class="icon-item">
+                                    @icon('logout')
+                                    <div>{{ trans('auth.logout') }}</div>
+                                </button>
+                            </form>
+                        </li>
+                        <li><hr></li>
+                        <li>
+                            @include('common.dark-mode-toggle', ['classes' => 'icon-item'])
+                        </li>
+                    </ul>
+                </div>
+            @endif
+        </nav>
 
     </div>
 </header>
index 929f56ed3c62e53b29e6ffde41d84186be90ac0e..1efe3ba34ca97253220f2502facc222fb513bfc5 100644 (file)
@@ -2,7 +2,7 @@
      option:dropdown-search:url="/search/entity/siblings?entity_type={{$entity->getType()}}&entity_id={{ $entity->id }}"
      option:dropdown-search:local-search-selector=".entity-list-item"
 >
-    <div class="dropdown-search-toggle" refs="dropdown@toggle"
+    <div class="dropdown-search-toggle-breadcrumb" refs="dropdown@toggle"
          aria-haspopup="true" aria-expanded="false" tabindex="0">
         <div class="separator">@icon('chevron-right')</div>
     </div>
@@ -18,6 +18,6 @@
         <div refs="dropdown-search@loading">
             @include('common.loading-icon')
         </div>
-        <div refs="dropdown-search@listContainer" class="dropdown-search-list px-m"></div>
+        <div refs="dropdown-search@listContainer" class="dropdown-search-list px-m" tabindex="-1"></div>
     </div>
 </div>
\ No newline at end of file
index dd7231095b8f6ebf3e7e14ec9d9674adfbceb999..bac240b1eed0c2a493b3ba8b09727067c08bf4c6 100644 (file)
@@ -1,13 +1,18 @@
-<div component="dropdown" class="dropdown-container" id="export-menu">
+<div component="dropdown"
+     class="dropdown-container"
+     id="export-menu">
+
     <div refs="dropdown@toggle" class="icon-list-item"
          aria-haspopup="true" aria-expanded="false" aria-label="{{ trans('entities.export') }}" tabindex="0">
         <span>@icon('export')</span>
         <span>{{ trans('entities.export') }}</span>
     </div>
+
     <ul refs="dropdown@menu" class="wide dropdown-menu" role="menu">
         <li><a href="{{ $entity->getUrl('/export/html') }}" target="_blank" class="label-item"><span>{{ trans('entities.export_html') }}</span><span>.html</span></a></li>
         <li><a href="{{ $entity->getUrl('/export/pdf') }}" target="_blank" class="label-item"><span>{{ trans('entities.export_pdf') }}</span><span>.pdf</span></a></li>
         <li><a href="{{ $entity->getUrl('/export/plaintext') }}" target="_blank" class="label-item"><span>{{ trans('entities.export_text') }}</span><span>.txt</span></a></li>
         <li><a href="{{ $entity->getUrl('/export/markdown') }}" target="_blank" class="label-item"><span>{{ trans('entities.export_md') }}</span><span>.md</span></a></li>
     </ul>
+
 </div>
index 298cc7c3e421750c6ec9669b2fcd9fb0a229f319..83ff2376220afe9dce33ff85904bc1ba5a515320 100644 (file)
@@ -1,50 +1,62 @@
 <div class="entity-meta">
     @if($entity->isA('revision'))
-        <div>
-            @icon('history'){{ trans('entities.pages_revision') }}
-            {{ trans('entities.pages_revisions_number') }}{{ $entity->revision_number == 0 ? '' : $entity->revision_number }}
+        <div class="entity-meta-item">
+            @icon('history')
+            <div>
+                {{ trans('entities.pages_revision') }}
+                {{ trans('entities.pages_revisions_number') }}{{ $entity->revision_number == 0 ? '' : $entity->revision_number }}
+            </div>
         </div>
     @endif
 
     @if ($entity->isA('page'))
-        <div>
-            @if (userCan('page-update', $entity)) <a href="{{ $entity->getUrl('/revisions') }}"> @endif
-            @icon('history'){{ trans('entities.meta_revision', ['revisionCount' => $entity->revision_count]) }}
-            @if (userCan('page-update', $entity))</a>@endif
-        </div>
+        @if (userCan('page-update', $entity)) <a href="{{ $entity->getUrl('/revisions') }}" class="entity-meta-item"> @else <div class="entity-meta-item"> @endif
+        @icon('history'){{ trans('entities.meta_revision', ['revisionCount' => $entity->revision_count]) }}
+        @if (userCan('page-update', $entity))</a> @else </div> @endif
     @endif
 
     @if ($entity->ownedBy && $entity->owned_by !== $entity->created_by)
-        <div>
-            @icon('user'){!! trans('entities.meta_owned_name', [
-            'user' => "<a href='{$entity->ownedBy->getProfileUrl()}'>".e($entity->ownedBy->name). "</a>"
-        ]) !!}
+        <div class="entity-meta-item">
+            @icon('user')
+            <div>
+                {!! trans('entities.meta_owned_name', [
+                    'user' => "<a href='{$entity->ownedBy->getProfileUrl()}'>".e($entity->ownedBy->name). "</a>"
+                ]) !!}
+            </div>
         </div>
     @endif
 
     @if ($entity->createdBy)
-        <div>
-            @icon('star'){!! trans('entities.meta_created_name', [
-            'timeLength' => '<span title="'.$entity->created_at->toDayDateTimeString().'">'.$entity->created_at->diffForHumans() . '</span>',
-            'user' => "<a href='{$entity->createdBy->getProfileUrl()}'>".e($entity->createdBy->name). "</a>"
-            ]) !!}
+        <div class="entity-meta-item">
+            @icon('star')
+            <div>
+                {!! trans('entities.meta_created_name', [
+                    'timeLength' => '<span title="'.$entity->created_at->toDayDateTimeString().'">'.$entity->created_at->diffForHumans() . '</span>',
+                    'user' => "<a href='{$entity->createdBy->getProfileUrl()}'>".e($entity->createdBy->name). "</a>"
+                ]) !!}
+            </div>
         </div>
     @else
-        <div>
-            @icon('star')<span title="{{$entity->created_at->toDayDateTimeString()}}">{{ trans('entities.meta_created', ['timeLength' => $entity->created_at->diffForHumans()]) }}</span>
+        <div class="entity-meta-item">
+            @icon('star')
+            <span title="{{$entity->created_at->toDayDateTimeString()}}">{{ trans('entities.meta_created', ['timeLength' => $entity->created_at->diffForHumans()]) }}</span>
         </div>
     @endif
 
     @if ($entity->updatedBy)
-        <div>
-            @icon('edit'){!! trans('entities.meta_updated_name', [
-                'timeLength' => '<span title="' . $entity->updated_at->toDayDateTimeString() .'">' . $entity->updated_at->diffForHumans() .'</span>',
-                'user' => "<a href='{$entity->updatedBy->getProfileUrl()}'>".e($entity->updatedBy->name). "</a>"
-            ]) !!}
+        <div class="entity-meta-item">
+            @icon('edit')
+            <div>
+                {!! trans('entities.meta_updated_name', [
+                    'timeLength' => '<span title="' . $entity->updated_at->toDayDateTimeString() .'">' . $entity->updated_at->diffForHumans() .'</span>',
+                    'user' => "<a href='{$entity->updatedBy->getProfileUrl()}'>".e($entity->updatedBy->name). "</a>"
+                ]) !!}
+            </div>
         </div>
     @elseif (!$entity->isA('revision'))
-        <div>
-            @icon('edit')<span title="{{ $entity->updated_at->toDayDateTimeString() }}">{{ trans('entities.meta_updated', ['timeLength' => $entity->updated_at->diffForHumans()]) }}</span>
+        <div class="entity-meta-item">
+            @icon('edit')
+            <span title="{{ $entity->updated_at->toDayDateTimeString() }}">{{ trans('entities.meta_updated', ['timeLength' => $entity->updated_at->diffForHumans()]) }}</span>
         </div>
     @endif
 </div>
\ No newline at end of file
index ab73a014fa8d2a6c52b68c3c0ec97823414164e0..c896b50b5234ab25bddcca53ecc7408112ef8aa4 100644 (file)
@@ -3,11 +3,11 @@
         <div class="popup-body small" tabindex="-1">
             <div class="popup-header primary-background">
                 <div class="popup-title">{{ trans('entities.entity_select') }}</div>
-                <button refs="popup@hide" type="button" class="popup-header-close">x</button>
+                <button refs="popup@hide" type="button" class="popup-header-close">@icon('close')</button>
             </div>
             @include('entities.selector', ['name' => 'entity-selector'])
             <div class="popup-footer">
-                <button refs="entity-selector-popup@select" type="button" disabled="true" class="button corner-button">{{ trans('common.select') }}</button>
+                <button refs="entity-selector-popup@select" type="button" disabled="true" class="button">{{ trans('common.select') }}</button>
             </div>
         </div>
     </div>
diff --git a/resources/views/exports/book.blade.php b/resources/views/exports/book.blade.php
new file mode 100644 (file)
index 0000000..42e03ea
--- /dev/null
@@ -0,0 +1,20 @@
+@extends('layouts.export')
+
+@section('title', $book->name)
+
+@section('content')
+
+    <h1 style="font-size: 4.8em">{{$book->name}}</h1>
+    <p>{{ $book->description }}</p>
+
+    @include('exports.parts.book-contents-menu', ['children' => $bookChildren])
+
+    @foreach($bookChildren as $bookChild)
+        @if($bookChild->isA('chapter'))
+            @include('exports.parts.chapter-item', ['chapter' => $bookChild])
+        @else
+            @include('exports.parts.page-item', ['page' => $bookChild, 'chapter' => null])
+        @endif
+    @endforeach
+
+@endsection
\ No newline at end of file
diff --git a/resources/views/exports/chapter.blade.php b/resources/views/exports/chapter.blade.php
new file mode 100644 (file)
index 0000000..ae49fa9
--- /dev/null
@@ -0,0 +1,16 @@
+@extends('layouts.export')
+
+@section('title', $chapter->name)
+
+@section('content')
+
+    <h1 style="font-size: 4.8em">{{$chapter->name}}</h1>
+    <p>{{ $chapter->description }}</p>
+
+    @include('exports.parts.chapter-contents-menu', ['pages' => $pages])
+
+    @foreach($pages as $page)
+        @include('exports.parts.page-item', ['page' => $page, 'chapter' => null])
+    @endforeach
+
+@endsection
\ No newline at end of file
similarity index 71%
rename from resources/views/pages/export.blade.php
rename to resources/views/exports/page.blade.php
index d2f448d6e889bd430f3ce53852530d8411c2945c..e9324e96b9924c7d11f261e17e52387563abe630 100644 (file)
@@ -8,6 +8,6 @@
     <hr>
 
     <div class="text-muted text-small">
-        @include('entities.export-meta', ['entity' => $page])
+        @include('exports.parts.meta', ['entity' => $page])
     </div>
 @endsection
\ No newline at end of file
diff --git a/resources/views/exports/parts/book-contents-menu.blade.php b/resources/views/exports/parts/book-contents-menu.blade.php
new file mode 100644 (file)
index 0000000..6b75442
--- /dev/null
@@ -0,0 +1,10 @@
+@if(count($children) > 0)
+    <ul class="contents">
+        @foreach($children as $bookChild)
+            <li><a href="#{{$bookChild->getType()}}-{{$bookChild->id}}">{{ $bookChild->name }}</a></li>
+            @if($bookChild->isA('chapter') && count($bookChild->visible_pages) > 0)
+                @include('exports.parts.chapter-contents-menu', ['pages' => $bookChild->visible_pages])
+            @endif
+        @endforeach
+    </ul>
+@endif
\ No newline at end of file
diff --git a/resources/views/exports/parts/chapter-contents-menu.blade.php b/resources/views/exports/parts/chapter-contents-menu.blade.php
new file mode 100644 (file)
index 0000000..eeaa349
--- /dev/null
@@ -0,0 +1,7 @@
+@if (count($pages) > 0)
+        <ul class="contents">
+            @foreach($pages as $page)
+                <li><a href="#page-{{$page->id}}">{{ $page->name }}</a></li>
+            @endforeach
+        </ul>
+@endif
\ No newline at end of file
diff --git a/resources/views/exports/parts/chapter-item.blade.php b/resources/views/exports/parts/chapter-item.blade.php
new file mode 100644 (file)
index 0000000..f58068b
--- /dev/null
@@ -0,0 +1,10 @@
+<div class="page-break"></div>
+<h1 id="chapter-{{$chapter->id}}">{{ $chapter->name }}</h1>
+
+<p>{{ $chapter->description }}</p>
+
+@if(count($chapter->visible_pages) > 0)
+    @foreach($chapter->visible_pages as $page)
+        @include('exports.parts.page-item', ['page' => $page, 'chapter' => $chapter])
+    @endforeach
+@endif
\ No newline at end of file
diff --git a/resources/views/exports/parts/page-item.blade.php b/resources/views/exports/parts/page-item.blade.php
new file mode 100644 (file)
index 0000000..1a138ae
--- /dev/null
@@ -0,0 +1,8 @@
+<div class="page-break"></div>
+
+@if (isset($chapter))
+    <div class="chapter-hint">{{$chapter->name}}</div>
+@endif
+
+<h1 id="page-{{$page->id}}">{{ $page->name }}</h1>
+{!! $page->html !!}
\ No newline at end of file
diff --git a/resources/views/exports/parts/styles.blade.php b/resources/views/exports/parts/styles.blade.php
new file mode 100644 (file)
index 0000000..830b8e4
--- /dev/null
@@ -0,0 +1,20 @@
+{{-- Fetch in our standard export styles --}}
+<style>
+    @if (!app()->runningUnitTests())
+        {!! file_get_contents(public_path('/dist/export-styles.css')) !!}
+    @endif
+</style>
+
+{{-- Apply any additional styles that can't be applied via our standard SCSS export styles --}}
+@if ($format === 'pdf')
+    <style>
+        /* Patches for CSS variable colors within PDF exports */
+        a {
+            color: {{ setting('app-color') }};
+        }
+
+        blockquote {
+            border-left-color: {{ setting('app-color') }};
+        }
+    </style>
+@endif
\ No newline at end of file
index ed04bc04124c9c0302568b8b4135390ce1314289..206955fe94864021f23bac4b07082a6f0a09e362 100644 (file)
@@ -15,7 +15,7 @@
         <div>
             <div class="form-group">
                 <label for="owner">{{ trans('entities.permissions_owner') }}</label>
-                @include('form.user-select', ['user' => $model->ownedBy, 'name' => 'owned_by', 'compact' => false])
+                @include('form.user-select', ['user' => $model->ownedBy, 'name' => 'owned_by'])
             </div>
         </div>
     </div>
index 8823bb0750b5fdb014cae14bf0dcf1ca630ce46f..743795a6daa3b4451bd268d40abfe75d5ada5a12 100644 (file)
@@ -1,19 +1,19 @@
-<div class="dropdown-search custom-select-input" components="dropdown dropdown-search user-select"
+<div class="dropdown-search" components="dropdown dropdown-search user-select"
      option:dropdown-search:url="/search/users/select"
 >
     <input refs="user-select@input" type="hidden" name="{{ $name }}" value="{{ $user->id ?? '' }}">
     <div refs="dropdown@toggle"
-         class="dropdown-search-toggle {{ $compact ? 'compact' : '' }} flex-container-row items-center"
+         class="dropdown-search-toggle-select  input-base"
          aria-haspopup="true" aria-expanded="false" tabindex="0">
-        <div refs="user-select@user-info" class="flex-container-row items-center px-s">
+        <div refs="user-select@user-info" class="dropdown-search-toggle-select-label flex-container-row items-center">
             @if($user)
-                <img class="avatar small mr-m" src="{{ $user->getAvatar($compact ? 22 : 30) }}" alt="{{ $user->name }}">
+                <img class="avatar small mr-m" src="{{ $user->getAvatar(30) }}" width="30" height="30" alt="{{ $user->name }}">
                 <span>{{ $user->name }}</span>
             @else
                 <span>{{ trans('settings.users_none_selected') }}</span>
             @endif
         </div>
-        <span style="font-size: {{ $compact ? '1.15rem' : '1.5rem' }}; margin-left: auto;">
+        <span class="dropdown-search-toggle-select-caret">
             @icon('caret-down')
         </span>
     </div>
index 6435e4ebdc0f760801fa8c9dd6ded8bedf58f681..f6a337e5054d4bd7b978fc08c626253270d278ed 100644 (file)
         </div>
     </div>
 
-    <script nonce="{{ $cspNonce }}">
-        setTimeout(async () => {
-            const result = await window.components["confirm-dialog"][0].show();
-            console.log({result});
-        }, 1000);
-    </script>
-
     <div class="container" id="home-default">
         <div class="grid third gap-xxl no-row-gap" >
             <div>
index 36568fef4dad22efa7876f9825ebd90336b9d6bb..d631ad3c9bf81db2a70badcf0acf11260a6eddc3 100644 (file)
@@ -8,10 +8,10 @@
         <meta http-equiv="Content-Security-Policy" content="{{ $cspContent }}">
     @endif
 
-    @include('common.export-styles', ['format' => $format, 'engine' => $engine ?? ''])
-    @include('common.export-custom-head')
+    @include('exports.parts.styles', ['format' => $format, 'engine' => $engine ?? ''])
+    @include('exports.parts.custom-head')
 </head>
-<body>
+<body class="export export-format-{{ $format }} export-engine-{{ $engine ?? 'none' }}">
 <div class="page-content">
     @yield('content')
 </div>
index e95b21445295e851368ead241ef92d08353a6cdc..4571f4471ca94cd59428e9a73d4e20122c120129 100644 (file)
@@ -27,7 +27,7 @@
 
     <div refs="tri-layout@container" class="tri-layout-container" @yield('container-attrs') >
 
-        <div class="tri-layout-left print-hidden pt-m" id="sidebar">
+        <div class="tri-layout-left print-hidden" id="sidebar">
             <aside class="tri-layout-left-contents">
                 @yield('left')
             </aside>
@@ -39,7 +39,7 @@
             </div>
         </div>
 
-        <div class="tri-layout-right print-hidden pt-m">
+        <div class="tri-layout-right print-hidden">
             <aside class="tri-layout-right-contents">
                 @yield('right')
             </aside>
index c593d0e2389adde14d7091a3f3d7b73c19a1ae10..4ac688692ef81619dc6c6b6962fc24b217d2bdf4 100644 (file)
@@ -2,68 +2,69 @@
     <div components="popup code-editor" class="popup-background code-editor">
         <div refs="code-editor@container" class="popup-body" tabindex="-1">
 
-            <div class="popup-header primary-background">
+            <div class="popup-header flex-container-row primary-background">
                 <div class="popup-title">{{ trans('components.code_editor') }}</div>
-                <button class="popup-header-close" refs="popup@hide">x</button>
+                <div component="dropdown" refs="code-editor@historyDropDown" class="flex-container-row">
+                    <button refs="dropdown@toggle">
+                        <span>@icon('history')</span>
+                        <span>{{ trans('components.code_session_history') }}</span>
+                    </button>
+                    <ul refs="dropdown@menu code-editor@historyList" class="dropdown-menu"></ul>
+                </div>
+                <button class="popup-header-close" refs="popup@hide">@icon('close')</button>
             </div>
 
-            <div class="p-l popup-content">
-                <div class="form-group">
+            <div class="code-editor-body-wrap flex-container-row flex-fill">
+                <div class="code-editor-language-list flex-container-column flex-fill">
                     <label for="code-editor-language">{{ trans('components.code_language') }}</label>
+                    <input refs="code-editor@languageInput" id="code-editor-language" type="text">
                     <div class="lang-options">
-                        <small>
-                            <a refs="code-editor@languageLink" data-lang="CSS">CSS</a>
-                            <a refs="code-editor@languageLink" data-lang="C">C</a>
-                            <a refs="code-editor@languageLink" data-lang="C++">C++</a>
-                            <a refs="code-editor@languageLink" data-lang="C#">C#</a>
-                            <a refs="code-editor@languageLink" data-lang="Fortran">Fortran</a>
-                            <a refs="code-editor@languageLink" data-lang="Go">Go</a>
-                            <a refs="code-editor@languageLink" data-lang="HTML">HTML</a>
-                            <a refs="code-editor@languageLink" data-lang="INI">INI</a>
-                            <a refs="code-editor@languageLink" data-lang="Java">Java</a>
-                            <a refs="code-editor@languageLink" data-lang="JavaScript">JavaScript</a>
-                            <a refs="code-editor@languageLink" data-lang="JSON">JSON</a>
-                            <a refs="code-editor@languageLink" data-lang="Lua">Lua</a>
-                            <a refs="code-editor@languageLink" data-lang="MarkDown">MarkDown</a>
-                            <a refs="code-editor@languageLink" data-lang="Nginx">Nginx</a>
-                            <a refs="code-editor@languageLink" data-lang="PASCAL">Pascal</a>
-                            <a refs="code-editor@languageLink" data-lang="Perl">Perl</a>
-                            <a refs="code-editor@languageLink" data-lang="PHP">PHP</a>
-                            <a refs="code-editor@languageLink" data-lang="Powershell">Powershell</a>
-                            <a refs="code-editor@languageLink" data-lang="Python">Python</a>
-                            <a refs="code-editor@languageLink" data-lang="Ruby">Ruby</a>
-                            <a refs="code-editor@languageLink" data-lang="shell">Shell/Bash</a>
-                            <a refs="code-editor@languageLink" data-lang="SQL">SQL</a>
-                            <a refs="code-editor@languageLink" data-lang="VBScript">VBScript</a>
-                            <a refs="code-editor@languageLink" data-lang="VB.NET">VB.NET</a>
-                            <a refs="code-editor@languageLink" data-lang="XML">XML</a>
-                            <a refs="code-editor@languageLink" data-lang="YAML">YAML</a>
-                        </small>
+                        <button type="button" refs="code-editor@languageLink" data-lang="CSS">CSS</button>
+                        <button type="button" refs="code-editor@languageLink" data-lang="C">C</button>
+                        <button type="button" refs="code-editor@languageLink" data-lang="C++">C++</button>
+                        <button type="button" refs="code-editor@languageLink" data-lang="C#">C#</button>
+                        <button type="button" refs="code-editor@languageLink" data-lang="diff">Diff</button>
+                        <button type="button" refs="code-editor@languageLink" data-lang="Fortran">Fortran</button>
+                        <button type="button" refs="code-editor@languageLink" data-lang="F#">F#</button>
+                        <button type="button" refs="code-editor@languageLink" data-lang="Go">Go</button>
+                        <button type="button" refs="code-editor@languageLink" data-lang="Haskell">Haskell</button>
+                        <button type="button" refs="code-editor@languageLink" data-lang="HTML">HTML</button>
+                        <button type="button" refs="code-editor@languageLink" data-lang="INI">INI</button>
+                        <button type="button" refs="code-editor@languageLink" data-lang="Java">Java</button>
+                        <button type="button" refs="code-editor@languageLink" data-lang="JavaScript">JavaScript</button>
+                        <button type="button" refs="code-editor@languageLink" data-lang="JSON">JSON</button>
+                        <button type="button" refs="code-editor@languageLink" data-lang="Julia">Julia</button>
+                        <button type="button" refs="code-editor@languageLink" data-lang="kotlin">Kotlin</button>
+                        <button type="button" refs="code-editor@languageLink" data-lang="LaTeX">LaTeX</button>
+                        <button type="button" refs="code-editor@languageLink" data-lang="Lua">Lua</button>
+                        <button type="button" refs="code-editor@languageLink" data-lang="MarkDown">MarkDown</button>
+                        <button type="button" refs="code-editor@languageLink" data-lang="Nginx">Nginx</button>
+                        <button type="button" refs="code-editor@languageLink" data-lang="ocaml">OCaml</button>
+                        <button type="button" refs="code-editor@languageLink" data-lang="PASCAL">Pascal</button>
+                        <button type="button" refs="code-editor@languageLink" data-lang="Perl">Perl</button>
+                        <button type="button" refs="code-editor@languageLink" data-lang="PHP">PHP</button>
+                        <button type="button" refs="code-editor@languageLink" data-lang="Powershell">Powershell</button>
+                        <button type="button" refs="code-editor@languageLink" data-lang="Python">Python</button>
+                        <button type="button" refs="code-editor@languageLink" data-lang="Ruby">Ruby</button>
+                        <button type="button" refs="code-editor@languageLink" data-lang="rust">Rust</button>
+                        <button type="button" refs="code-editor@languageLink" data-lang="shell">Shell/Bash</button>
+                        <button type="button" refs="code-editor@languageLink" data-lang="SQL">SQL</button>
+                        <button type="button" refs="code-editor@languageLink" data-lang="typescript">TypeScript</button>
+                        <button type="button" refs="code-editor@languageLink" data-lang="VBScript">VBScript</button>
+                        <button type="button" refs="code-editor@languageLink" data-lang="VB.NET">VB.NET</button>
+                        <button type="button" refs="code-editor@languageLink" data-lang="XML">XML</button>
+                        <button type="button" refs="code-editor@languageLink" data-lang="YAML">YAML</button>
                     </div>
-                    <input refs="code-editor@languageInput" id="code-editor-language" type="text">
                 </div>
 
-                <div class="form-group">
-                    <div class="grid half no-break v-end mb-xs">
-                        <div>
-                            <label for="code-editor-content">{{ trans('components.code_content') }}</label>
-                        </div>
-                        <div class="text-right">
-                            <div component="dropdown" refs="code-editor@historyDropDown" class="inline block">
-                                <button refs="dropdown@toggle" class="text-button text-small">@icon('history') {{ trans('components.code_session_history') }}</button>
-                                <ul refs="dropdown@menu code-editor@historyList" class="dropdown-menu"></ul>
-                            </div>
-                        </div>
-                    </div>
-
-                    <div class="clearfix"></div>
+                <div class="code-editor-main flex-fill">
                     <textarea refs="code-editor@editor"></textarea>
                 </div>
 
-                <div class="form-group">
-                    <button refs="code-editor@saveButton" type="button" class="button">{{ trans('components.code_save') }}</button>
-                </div>
+            </div>
 
+            <div class="popup-footer">
+                <button refs="code-editor@saveButton" type="button" class="button">{{ trans('components.code_save') }}</button>
             </div>
 
         </div>
index 4846f4b761299adf6822ae5d8097ff7d4ca469d9..fa5cb7374eb31586db5d89a2e115f35f1400166b 100644 (file)
@@ -65,7 +65,9 @@
         </div>
 
         <div class="action-buttons px-m py-xs">
-            <div component="dropdown" dropdown-move-menu class="dropdown-container">
+            <div component="dropdown"
+                 option:dropdown:move-menu="true"
+                 class="dropdown-container">
                 <button refs="dropdown@toggle" type="button" aria-haspopup="true" aria-expanded="false" class="text-primary text-button">@icon('edit') <span refs="page-editor@changelogDisplay">{{ trans('entities.pages_edit_set_changelog') }}</span></button>
                 <ul refs="dropdown@menu" class="wide dropdown-menu">
                     <li class="px-l py-m">
index c15c31b86904bc1c7985cb20ac90663c6f053670..50a0cd8c370e5be57a51f90f051423d065e9e9b3 100644 (file)
@@ -9,7 +9,7 @@
 
             <div class="popup-header primary-background">
                 <div class="popup-title">{{ trans('components.image_select') }}</div>
-                <button refs="popup@hide" type="button" class="popup-header-close">x</button>
+                <button refs="popup@hide" type="button" class="popup-header-close">@icon('close')</button>
             </div>
 
             <div class="flex-fill image-manager-body">
                     </div>
 
                     <div refs="image-manager@formContainer" class="inner flex"></div>
-
-                    <button refs="image-manager@selectButton" type="button" class="hidden button corner-button">
-                        {{ trans('components.image_select_image') }}
-                    </button>
                 </div>
 
             </div>
 
+            <div class="popup-footer">
+                <button refs="image-manager@selectButton" type="button" class="hidden button">
+                    {{ trans('components.image_select_image') }}
+                </button>
+            </div>
+
         </div>
     </div>
 </div>
\ No newline at end of file
index 0111047c6cfec38fe0a2fea304226c7de3a6f114..2a71c60214183698826027c2ecc0797cc1444e34 100644 (file)
 @section('right')
     <div id="page-details" class="entity-details mb-xl">
         <h5>{{ trans('common.details') }}</h5>
-        <div class="body text-small blended-links">
+        <div class="blended-links">
             @include('entities.meta', ['entity' => $page])
 
             @if($book->restricted)
                 <div class="active-restriction">
                     @if(userCan('restrictions-manage', $book))
-                        <a href="{{ $book->getUrl('/permissions') }}">@icon('lock'){{ trans('entities.books_permissions_active') }}</a>
+                        <a href="{{ $book->getUrl('/permissions') }}" class="entity-meta-item">
+                            @icon('lock')
+                            <div>{{ trans('entities.books_permissions_active') }}</div>
+                        </a>
                     @else
-                        @icon('lock'){{ trans('entities.books_permissions_active') }}
+                        <div class="entity-meta-item">
+                            @icon('lock')
+                            <div>{{ trans('entities.books_permissions_active') }}</div>
+                        </div>
                     @endif
                 </div>
             @endif
             @if($page->chapter && $page->chapter->restricted)
                 <div class="active-restriction">
                     @if(userCan('restrictions-manage', $page->chapter))
-                        <a href="{{ $page->chapter->getUrl('/permissions') }}">@icon('lock'){{ trans('entities.chapters_permissions_active') }}</a>
+                        <a href="{{ $page->chapter->getUrl('/permissions') }}" class="entity-meta-item">
+                            @icon('lock')
+                            <div>{{ trans('entities.chapters_permissions_active') }}</div>
+                        </a>
                     @else
-                        @icon('lock'){{ trans('entities.chapters_permissions_active') }}
+                        <div class="entity-meta-item">
+                            @icon('lock')
+                            <div>{{ trans('entities.chapters_permissions_active') }}</div>
+                        </div>
                     @endif
                 </div>
             @endif
             @if($page->restricted)
                 <div class="active-restriction">
                     @if(userCan('restrictions-manage', $page))
-                        <a href="{{ $page->getUrl('/permissions') }}">@icon('lock'){{ trans('entities.pages_permissions_active') }}</a>
+                        <a href="{{ $page->getUrl('/permissions') }}" class="entity-meta-item">
+                            @icon('lock')
+                            <div>{{ trans('entities.pages_permissions_active') }}</div>
+                        </a>
                     @else
-                        @icon('lock'){{ trans('entities.pages_permissions_active') }}
+                        <div class="entity-meta-item">
+                            @icon('lock')
+                            <div>{{ trans('entities.pages_permissions_active') }}</div>
+                        </div>
                     @endif
                 </div>
             @endif
 
             @if($page->template)
-                <div>
-                    @icon('template'){{ trans('entities.pages_is_template') }}
+                <div class="entity-meta-item">
+                    @icon('template')
+                    <div>{{ trans('entities.pages_is_template') }}</div>
                 </div>
             @endif
         </div>
index 506a735a2c59552825334a2d4729bbc73831fb4d..b856d11502a5dd05fe7bf1879012afc04528c53a 100644 (file)
@@ -9,8 +9,9 @@
         <h1 class="list-heading">{{ trans('settings.audit') }}</h1>
         <p class="text-muted">{{ trans('settings.audit_desc') }}</p>
 
-        <div class="flex-container-row">
-            <div component="dropdown" class="list-sort-type dropdown-container mr-m">
+        <form action="{{ url('/settings/audit') }}" method="get" class="flex-container-row wrap justify-flex-start gap-m">
+
+            <div component="dropdown" class="list-sort-type dropdown-container">
                 <label for="">{{ trans('settings.audit_event_filter') }}</label>
                 <button refs="dropdown@toggle" aria-haspopup="true" aria-expanded="false" aria-label="{{ trans('common.sort_options') }}" class="input-base text-left">{{ $listDetails['event'] ?: trans('settings.audit_event_filter_no_filter') }}</button>
                 <ul refs="dropdown@menu" class="dropdown-menu">
                 </ul>
             </div>
 
-            <form action="{{ url('/settings/audit') }}" method="get" class="flex-container-row mr-m">
-                @if(!empty($listDetails['event']))
-                    <input type="hidden" name="event" value="{{ $listDetails['event'] }}">
-                @endif
-
-                @foreach(['date_from', 'date_to'] as $filterKey)
-                    <div class="mr-m">
-                        <label for="audit_filter_{{ $filterKey }}">{{ trans('settings.audit_' . $filterKey) }}</label>
-                        <input id="audit_filter_{{ $filterKey }}"
-                               component="submit-on-change"
-                               type="date"
-                               name="{{ $filterKey }}"
-                               value="{{ $listDetails[$filterKey] ?? '' }}">
-                    </div>
-                @endforeach
+            @if(!empty($listDetails['event']))
+                <input type="hidden" name="event" value="{{ $listDetails['event'] }}">
+            @endif
 
-                <div class="form-group ml-auto mr-m"
-                     component="submit-on-change"
-                     option:submit-on-change:filter='[name="user"]'>
-                    <label for="owner">{{ trans('settings.audit_table_user') }}</label>
-                    @include('form.user-select', ['user' => $listDetails['user'] ? \BookStack\Auth\User::query()->find($listDetails['user']) : null, 'name' => 'user', 'compact' =>  true])
+            @foreach(['date_from', 'date_to'] as $filterKey)
+                <div class=>
+                    <label for="audit_filter_{{ $filterKey }}">{{ trans('settings.audit_' . $filterKey) }}</label>
+                    <input id="audit_filter_{{ $filterKey }}"
+                           component="submit-on-change"
+                           type="date"
+                           name="{{ $filterKey }}"
+                           value="{{ $listDetails[$filterKey] ?? '' }}">
                 </div>
+            @endforeach
 
+            <div class="form-group"
+                 component="submit-on-change"
+                 option:submit-on-change:filter='[name="user"]'>
+                <label for="owner">{{ trans('settings.audit_table_user') }}</label>
+                @include('form.user-select', ['user' => $listDetails['user'] ? \BookStack\Auth\User::query()->find($listDetails['user']) : null, 'name' => 'user'])
+            </div>
 
-                <div class="form-group ml-auto">
-                    <label for="ip">{{ trans('settings.audit_table_ip') }}</label>
-                    @include('form.text', ['name' => 'ip', 'model' => (object) $listDetails])
-                    <input type="submit" style="display: none">
-                </div>
-            </form>
-        </div>
+
+            <div class="form-group">
+                <label for="ip">{{ trans('settings.audit_table_ip') }}</label>
+                @include('form.text', ['name' => 'ip', 'model' => (object) $listDetails])
+                <input type="submit" style="display: none">
+            </div>
+        </form>
 
         <hr class="mt-l mb-s">
 
index b7be95b4a1519ac53da4b746c942300627b308a3..a7392196b68be6ad0329a57dffbb9c0191ea4052 100644 (file)
             <div>
                 <label for="setting-app-custom-head" class="setting-list-label">{{ trans('settings.app_custom_html') }}</label>
                 <p class="small">{{ trans('settings.app_custom_html_desc') }}</p>
-                <textarea name="setting-app-custom-head" id="setting-app-custom-head" class="simple-code-input mt-m">{{ setting('app-custom-head', '') }}</textarea>
+                <div class="mt-m">
+                    <textarea component="code-textarea"
+                              option:code-textarea:mode="html"
+                              name="setting-app-custom-head"
+                              id="setting-app-custom-head"
+                              class="simple-code-input">{{ setting('app-custom-head', '') }}</textarea>
+                </div>
                 <p class="small text-right">{{ trans('settings.app_custom_html_disabled_notice') }}</p>
             </div>
 
index 0d592468d1b8a1adace0bc1776695a779a4bd13a..4d440b635f4f83e386f47fdb4cfb07d5ad0a9372 100644 (file)
 
     <div id="details" class="mb-xl">
         <h5>{{ trans('common.details') }}</h5>
-        <div class="text-small text-muted blended-links">
+        <div class="blended-links">
             @include('entities.meta', ['entity' => $shelf])
             @if($shelf->restricted)
                 <div class="active-restriction">
                     @if(userCan('restrictions-manage', $shelf))
-                        <a href="{{ $shelf->getUrl('/permissions') }}">@icon('lock'){{ trans('entities.shelves_permissions_active') }}</a>
+                        <a href="{{ $shelf->getUrl('/permissions') }}" class="entity-meta-item">
+                            @icon('lock')
+                            <div>{{ trans('entities.shelves_permissions_active') }}</div>
+                        </a>
                     @else
-                        @icon('lock'){{ trans('entities.shelves_permissions_active') }}
+                        <div class="entity-meta-item">
+                            @icon('lock')
+                            <div>{{ trans('entities.shelves_permissions_active') }}</div>
+                        </div>
                     @endif
                 </div>
             @endif
index 9ee5d4c056280c2aa78813247551be6672cf75cd..b18c182ebde971b6016d47836d333ad0a2b8fc6f 100644 (file)
@@ -19,7 +19,7 @@
                         <p class="small">{{ trans('settings.users_migrate_ownership_desc') }}</p>
                     </div>
                     <div>
-                        @include('form.user-select', ['name' => 'new_owner_id', 'user' => null, 'compact' => false])
+                        @include('form.user-select', ['name' => 'new_owner_id', 'user' => null])
                     </div>
                 </div>
             @endif
index 37f59b9706e5caf1ce9917846fb136555e42ccf1..5e16e5333e184f2f37a8f99b9e241a5deb0fd151 100644 (file)
@@ -82,6 +82,7 @@ Route::middleware('auth')->group(function () {
     Route::get('/books/{slug}/delete', [BookController::class, 'showDelete']);
     Route::get('/books/{bookSlug}/copy', [BookController::class, 'showCopy']);
     Route::post('/books/{bookSlug}/copy', [BookController::class, 'copy']);
+    Route::post('/books/{bookSlug}/convert-to-shelf', [BookController::class, 'convertToShelf']);
     Route::get('/books/{bookSlug}/sort', [BookSortController::class, 'show']);
     Route::put('/books/{bookSlug}/sort', [BookSortController::class, 'update']);
     Route::get('/books/{bookSlug}/export/html', [BookExportController::class, 'html']);
@@ -132,6 +133,7 @@ Route::middleware('auth')->group(function () {
     Route::get('/books/{bookSlug}/chapter/{chapterSlug}/copy', [ChapterController::class, 'showCopy']);
     Route::post('/books/{bookSlug}/chapter/{chapterSlug}/copy', [ChapterController::class, 'copy']);
     Route::get('/books/{bookSlug}/chapter/{chapterSlug}/edit', [ChapterController::class, 'edit']);
+    Route::post('/books/{bookSlug}/chapter/{chapterSlug}/convert-to-book', [ChapterController::class, 'convertToBook']);
     Route::get('/books/{bookSlug}/chapter/{chapterSlug}/permissions', [ChapterController::class, 'showPermissions']);
     Route::get('/books/{bookSlug}/chapter/{chapterSlug}/export/pdf', [ChapterExportController::class, 'pdf']);
     Route::get('/books/{bookSlug}/chapter/{chapterSlug}/export/html', [ChapterExportController::class, 'html']);
index 9fe8f8215bc6f201d26d669a360e28135228953a..f426cff7358fba2e54a4bbfa63dd82b18f20f7d8 100644 (file)
@@ -6,10 +6,12 @@ use BookStack\Entities\Models\Book;
 use Carbon\Carbon;
 use Illuminate\Support\Facades\DB;
 use Tests\TestCase;
+use Tests\Uploads\UsesImages;
 
 class BooksApiTest extends TestCase
 {
     use TestsApi;
+    use UsesImages;
 
     protected string $baseEndpoint = '/api/books';
 
@@ -118,6 +120,42 @@ class BooksApiTest extends TestCase
         $this->assertGreaterThan(Carbon::now()->subDay()->unix(), $book->updated_at->unix());
     }
 
+    public function test_update_cover_image_control()
+    {
+        $this->actingAsApiEditor();
+        /** @var Book $book */
+        $book = Book::visible()->first();
+        $this->assertNull($book->cover);
+        $file = $this->getTestImage('image.png');
+
+        // Ensure cover image can be set via API
+        $resp = $this->call('PUT', $this->baseEndpoint . "/{$book->id}", [
+            'name'  => 'My updated API book with image',
+        ], [], ['image' => $file]);
+        $book->refresh();
+
+        $resp->assertStatus(200);
+        $this->assertNotNull($book->cover);
+
+        // Ensure further updates without image do not clear cover image
+        $resp = $this->put($this->baseEndpoint . "/{$book->id}", [
+            'name' => 'My updated book again',
+        ]);
+        $book->refresh();
+
+        $resp->assertStatus(200);
+        $this->assertNotNull($book->cover);
+
+        // Ensure update with null image property clears image
+        $resp = $this->put($this->baseEndpoint . "/{$book->id}", [
+            'image' => null,
+        ]);
+        $book->refresh();
+
+        $resp->assertStatus(200);
+        $this->assertNull($book->cover);
+    }
+
     public function test_delete_endpoint()
     {
         $this->actingAsApiEditor();
index 034d4bc289eddf62194c75618c4e2f46323af201..bc7b6f16493a91f9091b04cf0b002599477b9865 100644 (file)
@@ -7,10 +7,12 @@ use BookStack\Entities\Models\Bookshelf;
 use Carbon\Carbon;
 use Illuminate\Support\Facades\DB;
 use Tests\TestCase;
+use Tests\Uploads\UsesImages;
 
 class ShelvesApiTest extends TestCase
 {
     use TestsApi;
+    use UsesImages;
 
     protected string $baseEndpoint = '/api/shelves';
 
@@ -146,6 +148,42 @@ class ShelvesApiTest extends TestCase
         $this->assertTrue($shelf->books()->count() === 0);
     }
 
+    public function test_update_cover_image_control()
+    {
+        $this->actingAsApiEditor();
+        /** @var Book $shelf */
+        $shelf = Bookshelf::visible()->first();
+        $this->assertNull($shelf->cover);
+        $file = $this->getTestImage('image.png');
+
+        // Ensure cover image can be set via API
+        $resp = $this->call('PUT', $this->baseEndpoint . "/{$shelf->id}", [
+            'name'  => 'My updated API shelf with image',
+        ], [], ['image' => $file]);
+        $shelf->refresh();
+
+        $resp->assertStatus(200);
+        $this->assertNotNull($shelf->cover);
+
+        // Ensure further updates without image do not clear cover image
+        $resp = $this->put($this->baseEndpoint . "/{$shelf->id}", [
+            'name' => 'My updated shelf again',
+        ]);
+        $shelf->refresh();
+
+        $resp->assertStatus(200);
+        $this->assertNotNull($shelf->cover);
+
+        // Ensure update with null image property clears image
+        $resp = $this->put($this->baseEndpoint . "/{$shelf->id}", [
+            'image' => null,
+        ]);
+        $shelf->refresh();
+
+        $resp->assertStatus(200);
+        $this->assertNull($shelf->cover);
+    }
+
     public function test_delete_endpoint()
     {
         $this->actingAsApiEditor();
diff --git a/tests/Auth/GroupSyncServiceTest.php b/tests/Auth/GroupSyncServiceTest.php
new file mode 100644 (file)
index 0000000..74d2c7e
--- /dev/null
@@ -0,0 +1,57 @@
+<?php
+
+namespace Tests\Auth;
+
+use BookStack\Auth\Access\GroupSyncService;
+use BookStack\Auth\Role;
+use BookStack\Auth\User;
+use Tests\TestCase;
+
+class GroupSyncServiceTest extends TestCase
+{
+    public function test_user_is_assigned_to_matching_roles()
+    {
+        $user = $this->getViewer();
+
+        $roleA = Role::factory()->create(['display_name' => 'Wizards']);
+        $roleB = Role::factory()->create(['display_name' => 'Gremlins']);
+        $roleC = Role::factory()->create(['display_name' => 'ABC123', 'external_auth_id' => 'sales']);
+        $roleD = Role::factory()->create(['display_name' => 'DEF456', 'external_auth_id' => 'admin-team']);
+
+        foreach ([$roleA, $roleB, $roleC, $roleD] as $role) {
+            $this->assertFalse($user->hasRole($role->id));
+        }
+
+        (new GroupSyncService())->syncUserWithFoundGroups($user, ['Wizards', 'Gremlinz', 'Sales', 'Admin Team'], false);
+
+        $user = User::query()->find($user->id);
+        $this->assertTrue($user->hasRole($roleA->id));
+        $this->assertFalse($user->hasRole($roleB->id));
+        $this->assertTrue($user->hasRole($roleC->id));
+        $this->assertTrue($user->hasRole($roleD->id));
+    }
+
+    public function test_multiple_values_in_role_external_auth_id_handled()
+    {
+        $user = $this->getViewer();
+        $role = Role::factory()->create(['display_name' => 'ABC123', 'external_auth_id' => 'sales, engineering, developers, marketers']);
+        $this->assertFalse($user->hasRole($role->id));
+
+        (new GroupSyncService())->syncUserWithFoundGroups($user, ['Developers'], false);
+
+        $user = User::query()->find($user->id);
+        $this->assertTrue($user->hasRole($role->id));
+    }
+
+    public function test_commas_can_be_used_in_external_auth_id_if_escaped()
+    {
+        $user = $this->getViewer();
+        $role = Role::factory()->create(['display_name' => 'ABC123', 'external_auth_id' => 'sales\,-developers, marketers']);
+        $this->assertFalse($user->hasRole($role->id));
+
+        (new GroupSyncService())->syncUserWithFoundGroups($user, ['Sales, Developers'], false);
+
+        $user = User::query()->find($user->id);
+        $this->assertTrue($user->hasRole($role->id));
+    }
+}
index 03ef926cb07de08cd503b5cb6a0be7e4b8c94677..96e10e4dae818a7155306c55f050c875b9d7596d 100644 (file)
@@ -615,7 +615,7 @@ class LdapTest extends TestCase
 
     public function test_dump_user_details_option_works()
     {
-        config()->set(['services.ldap.dump_user_details' => true]);
+        config()->set(['services.ldap.dump_user_details' => true, 'services.ldap.thumbnail_attribute' => 'jpegphoto']);
 
         $this->commonLdapMocks(1, 1, 1, 1, 1);
         $this->mockLdap->shouldReceive('searchAndGetEntries')->times(1)
@@ -623,7 +623,9 @@ class LdapTest extends TestCase
             ->andReturn(['count' => 1, 0 => [
                 'uid' => [$this->mockUser->name],
                 'cn'  => [$this->mockUser->name],
-                'dn'  => ['dc=test' . config('services.ldap.base_dn')],
+                // Test dumping binary data for avatar responses
+                'jpegphoto' => base64_decode('/9j/4AAQSkZJRg=='),
+                'dn'        => ['dc=test' . config('services.ldap.base_dn')],
             ]]);
 
         $resp = $this->post('/login', [
index 9a6106243241d00e8cfd9c95b493ae1a62a49d9d..7286a1de8b997ee5dbc0295ac841a4e13da1fb3b 100644 (file)
@@ -241,6 +241,16 @@ class MfaVerificationTest extends TestCase
         }
     }
 
+    public function test_login_mfa_interception_does_not_log_error()
+    {
+        $logHandler = $this->withTestLogger();
+
+        [$user, $secret, $loginResp] = $this->startTotpLogin();
+
+        $loginResp->assertRedirect('/mfa/verify');
+        $this->assertFalse($logHandler->hasErrorRecords());
+    }
+
     /**
      * @return array<User, string, TestResponse>
      */
index 7f102a17eaede81e70792b8130f091444da0cdd9..8b2702b46e7c1bacfdcf72456cdd4a163a9f054f 100644 (file)
@@ -290,6 +290,7 @@ class BookTest extends TestCase
 
         /** @var Book $copy */
         $copy = Book::query()->where('name', '=', 'My copy book')->first();
+
         $this->assertNotNull($copy->cover);
         $this->assertNotEquals($book->cover->id, $copy->cover->id);
     }
diff --git a/tests/Entity/ConvertTest.php b/tests/Entity/ConvertTest.php
new file mode 100644 (file)
index 0000000..9791f77
--- /dev/null
@@ -0,0 +1,145 @@
+<?php
+
+namespace Tests\Entity;
+
+use BookStack\Actions\ActivityType;
+use BookStack\Actions\Tag;
+use BookStack\Entities\Models\Book;
+use BookStack\Entities\Models\Bookshelf;
+use BookStack\Entities\Models\Chapter;
+use BookStack\Entities\Models\Page;
+use Tests\TestCase;
+
+class ConvertTest extends TestCase
+{
+    public function test_chapter_edit_view_shows_convert_option()
+    {
+        /** @var Chapter $chapter */
+        $chapter = Chapter::query()->first();
+
+        $resp = $this->asEditor()->get($chapter->getUrl('/edit'));
+        $resp->assertSee('Convert to Book');
+        $resp->assertSee('Convert Chapter');
+        $resp->assertElementExists('form[action$="/convert-to-book"] button');
+    }
+
+    public function test_convert_chapter_to_book()
+    {
+        /** @var Chapter $chapter */
+        $chapter = Chapter::query()->whereHas('pages')->first();
+        $chapter->tags()->save(new Tag(['name' => 'Category', 'value' => 'Penguins']));
+        /** @var Page $childPage */
+        $childPage = $chapter->pages()->first();
+
+        $resp = $this->asEditor()->post($chapter->getUrl('/convert-to-book'));
+        $resp->assertRedirectContains('/books/');
+
+        /** @var Book $newBook */
+        $newBook = Book::query()->orderBy('id', 'desc')->first();
+
+        $this->assertDatabaseMissing('chapters', ['id' => $chapter->id]);
+        $this->assertDatabaseHas('pages', ['id' => $childPage->id, 'book_id' => $newBook->id, 'chapter_id' => 0]);
+        $this->assertCount(1, $newBook->tags);
+        $this->assertEquals('Category', $newBook->tags->first()->name);
+        $this->assertEquals('Penguins', $newBook->tags->first()->value);
+        $this->assertEquals($chapter->name, $newBook->name);
+        $this->assertEquals($chapter->description, $newBook->description);
+
+        $this->assertActivityExists(ActivityType::BOOK_CREATE_FROM_CHAPTER, $newBook);
+    }
+
+    public function test_convert_chapter_to_book_requires_permissions()
+    {
+        /** @var Chapter $chapter */
+        $chapter = Chapter::query()->first();
+        $user = $this->getViewer();
+
+        $permissions = ['chapter-delete-all', 'book-create-all', 'chapter-update-all'];
+        $this->giveUserPermissions($user, $permissions);
+
+        foreach ($permissions as $permission) {
+            $this->removePermissionFromUser($user, $permission);
+            $resp = $this->actingAs($user)->post($chapter->getUrl('/convert-to-book'));
+            $this->assertPermissionError($resp);
+            $this->giveUserPermissions($user, [$permission]);
+        }
+
+        $resp = $this->actingAs($user)->post($chapter->getUrl('/convert-to-book'));
+        $this->assertNotPermissionError($resp);
+        $resp->assertRedirect();
+    }
+
+    public function test_book_edit_view_shows_convert_option()
+    {
+        $book = Book::query()->first();
+
+        $resp = $this->asEditor()->get($book->getUrl('/edit'));
+        $resp->assertSee('Convert to Shelf');
+        $resp->assertSee('Convert Book');
+        $resp->assertSee('Note that permissions on shelves do not auto-cascade to content');
+        $resp->assertElementExists('form[action$="/convert-to-shelf"] button');
+    }
+
+    public function test_book_convert_to_shelf()
+    {
+        /** @var Book $book */
+        $book = Book::query()->whereHas('directPages')->whereHas('chapters')->firstOrFail();
+        $book->tags()->save(new Tag(['name' => 'Category', 'value' => 'Ducks']));
+        /** @var Page $childPage */
+        $childPage = $book->directPages()->first();
+        /** @var Chapter $childChapter */
+        $childChapter = $book->chapters()->whereHas('pages')->firstOrFail();
+        /** @var Page $chapterChildPage */
+        $chapterChildPage = $childChapter->pages()->firstOrFail();
+        $bookChapterCount = $book->chapters()->count();
+        $systemBookCount = Book::query()->count();
+
+        // Run conversion
+        $resp = $this->asEditor()->post($book->getUrl('/convert-to-shelf'));
+
+        /** @var Bookshelf $newShelf */
+        $newShelf = Bookshelf::query()->orderBy('id', 'desc')->first();
+
+        // Checks for new shelf
+        $resp->assertRedirectContains('/shelves/');
+        $this->assertDatabaseMissing('chapters', ['id' => $childChapter->id]);
+        $this->assertCount(1, $newShelf->tags);
+        $this->assertEquals('Category', $newShelf->tags->first()->name);
+        $this->assertEquals('Ducks', $newShelf->tags->first()->value);
+        $this->assertEquals($book->name, $newShelf->name);
+        $this->assertEquals($book->description, $newShelf->description);
+        $this->assertEquals($newShelf->books()->count(), $bookChapterCount + 1);
+        $this->assertEquals($systemBookCount + $bookChapterCount, Book::query()->count());
+        $this->assertActivityExists(ActivityType::BOOKSHELF_CREATE_FROM_BOOK, $newShelf);
+
+        // Checks for old book to contain child pages
+        $this->assertDatabaseHas('books', ['id' => $book->id, 'name' => $book->name . ' Pages']);
+        $this->assertDatabaseHas('pages', ['id' => $childPage->id, 'book_id' => $book->id, 'chapter_id' => 0]);
+
+        // Checks for nested page
+        $chapterChildPage->refresh();
+        $this->assertEquals(0, $chapterChildPage->chapter_id);
+        $this->assertEquals($childChapter->name, $chapterChildPage->book->name);
+    }
+
+    public function test_book_convert_to_shelf_requires_permissions()
+    {
+        /** @var Book $book */
+        $book = Book::query()->first();
+        $user = $this->getViewer();
+
+        $permissions = ['book-delete-all', 'bookshelf-create-all', 'book-update-all', 'book-create-all'];
+        $this->giveUserPermissions($user, $permissions);
+
+        foreach ($permissions as $permission) {
+            $this->removePermissionFromUser($user, $permission);
+            $resp = $this->actingAs($user)->post($book->getUrl('/convert-to-shelf'));
+            $this->assertPermissionError($resp);
+            $this->giveUserPermissions($user, [$permission]);
+        }
+
+        $resp = $this->actingAs($user)->post($book->getUrl('/convert-to-shelf'));
+        $this->assertNotPermissionError($resp);
+        $resp->assertRedirect();
+    }
+}
index ab5777e98981e9ec9c7e77db546b48b47ec07c47..b535f5aaa7e177d08efb4717c5c761410aa1732d 100644 (file)
@@ -423,6 +423,17 @@ class EntitySearchTest extends TestCase
         $search->assertSee('My supercool &lt;great&gt; <strong>TestPageContent</strong> page', false);
     }
 
+    public function test_words_adjacent_to_lines_breaks_can_be_matched_with_normal_terms()
+    {
+        $page = $this->newPage(['name' => 'TermA', 'html' => '
+            <p>TermA<br>TermB<br>TermC</p>
+        ']);
+
+        $search = $this->asEditor()->get('/search?term=' . urlencode('TermB TermC'));
+
+        $search->assertSee($page->getUrl(), false);
+    }
+
     public function test_searches_with_user_filters_adds_them_into_advanced_search_form()
     {
         $resp = $this->asEditor()->get('/search?term=' . urlencode('test {updated_by:me} {created_by:dan}'));
index 08d0921111fdb88a908867af8f228a9c1280e175..826b69be5d22b307882ba9e4385fd85d599c8a99 100644 (file)
@@ -457,4 +457,12 @@ class ExportTest extends TestCase
             $resp->assertElementExists('head meta[http-equiv="Content-Security-Policy"][content*="script-src "]');
         }
     }
+
+    public function test_html_exports_contain_body_classes_for_export_identification()
+    {
+        $page = Page::query()->first();
+
+        $resp = $this->asEditor()->get($page->getUrl('/export/html'));
+        $resp->assertElementExists('body.export.export-format-html.export-engine-none');
+    }
 }
index 17a5aa2c56faaf50cb4e9fbd6ad5ca828cba76a1..43389ad787ccc245a9b1d6d980f790c723a09038 100644 (file)
@@ -6,8 +6,8 @@ use BookStack\Entities\Models\Book;
 use BookStack\Entities\Models\Bookshelf;
 use BookStack\Entities\Models\Chapter;
 use BookStack\Entities\Models\Page;
+use BookStack\Entities\Repos\BaseRepo;
 use BookStack\Entities\Repos\BookRepo;
-use BookStack\Entities\Repos\BookshelfRepo;
 use Illuminate\Support\Str;
 use Tests\Uploads\UsesImages;
 
@@ -69,8 +69,8 @@ class OpenGraphTest extends TestCase
         $this->assertArrayNotHasKey('image', $tags);
 
         // Test image set if image has cover image
-        $shelfRepo = app(BookshelfRepo::class);
-        $shelfRepo->updateCoverImage($shelf, $this->getTestImage('image.png'));
+        $baseRepo = app(BaseRepo::class);
+        $baseRepo->updateCoverImage($shelf, $this->getTestImage('image.png'));
         $resp = $this->asEditor()->get($shelf->getUrl());
         $tags = $this->getOpenGraphTags($resp);
 
index cbf49bf71f424f747e97bc04b3e3ff55fa6bb50b..ce57d56f51d0f473e73e6f63642997e11a7424fe 100644 (file)
@@ -194,13 +194,23 @@ trait SharedTestHelpers
     /**
      * Completely remove the given permission name from the given user.
      */
-    protected function removePermissionFromUser(User $user, string $permission)
+    protected function removePermissionFromUser(User $user, string $permissionName)
     {
-        $permission = RolePermission::query()->where('name', '=', $permission)->first();
+        $permissionService = app()->make(PermissionService::class);
+
+        /** @var RolePermission $permission */
+        $permission = RolePermission::query()->where('name', '=', $permissionName)->firstOrFail();
+
+        $roles = $user->roles()->whereHas('permissions', function ($query) use ($permission) {
+            $query->where('id', '=', $permission->id);
+        })->get();
+
         /** @var Role $role */
-        foreach ($user->roles as $role) {
+        foreach ($roles as $role) {
             $role->detachPermission($permission);
+            $permissionService->buildJointPermissionForRole($role);
         }
+
         $user->clearPermissionCache();
     }
 
index 4e53aa020ba3ca5054e8d75e14a2ed57dc277276..888bacdfc6c0152f9be22d498c9fd348f90a552c 100644 (file)
@@ -111,7 +111,7 @@ class TestResponse extends BaseTestResponse
 
         foreach ($elements as $element) {
             $element = new Crawler($element);
-            if (preg_match("/$pattern/i", $element->html())) {
+            if (preg_match("/$pattern/i", $element->text())) {
                 $matched = true;
                 break;
             }
index 90215d558e02799bc75af878469cb79d7abe7b77..13f9d9e0c95fb97a3c690daf9115b89f36fa1374 100644 (file)
@@ -34,4 +34,18 @@ class UrlTest extends TestCase
         $this->assertEquals('/cool/docs', $bsRequest->getBaseUrl());
         $this->assertEquals('https://donkey.example.com:8091/cool/docs/login', $bsRequest->getUri());
     }
+
+    public function test_app_url_without_path_does_not_duplicate_path_slash()
+    {
+        config()->set('app.url', 'https://donkey.example.com');
+
+        // Have to manually get and wrap request in our custom type due to testing mechanics
+        $this->get('/settings');
+        $bsRequest = Request::createFrom(request());
+
+        $this->assertEquals('https://donkey.example.com', $bsRequest->getSchemeAndHttpHost());
+        $this->assertEquals('', $bsRequest->getBaseUrl());
+        $this->assertEquals('/settings', $bsRequest->getPathInfo());
+        $this->assertEquals('https://donkey.example.com/settings', $bsRequest->getUri());
+    }
 }
diff --git a/version b/version
index 668ff448dd221ea520b1b725d388dd0c62d680c7..eee08ef7617871204c7a8a771013f311ee470086 100644 (file)
--- a/version
+++ b/version
@@ -1 +1 @@
-v22.02-dev
+v22.06-dev