]> BookStack Code Mirror - bookstack/commitdiff
Merge branch 'master' into release
authorDan Brown <redacted>
Mon, 10 Jan 2022 18:23:19 +0000 (18:23 +0000)
committerDan Brown <redacted>
Mon, 10 Jan 2022 18:23:19 +0000 (18:23 +0000)
24 files changed:
.github/translators.txt
app/Entities/Models/Deletion.php
app/Entities/Models/Entity.php
app/Entities/Models/PageRevision.php
app/Entities/Tools/BookSortMap.php
app/Entities/Tools/TrashCan.php
app/Http/Controllers/Controller.php
public/loading_error.png [new file with mode: 0644]
resources/js/components/image-manager.js
resources/js/components/markdown-editor.js
resources/js/components/wysiwyg-editor.js
resources/lang/cs/common.php
resources/lang/es/auth.php
resources/lang/ja/auth.php
resources/lang/ja/common.php
resources/lang/ja/entities.php
resources/lang/ja/errors.php
resources/lang/ja/settings.php
resources/lang/pt_BR/activities.php
resources/lang/pt_BR/common.php
resources/lang/zh_CN/common.php
resources/lang/zh_CN/settings.php
resources/views/pages/parts/image-manager-form.blade.php
tests/HomepageTest.php

index b7639ce85ea5e0e39b2230de552a5a9880b61a0d..0a4fbdc1b5d31f5be0a5b5adcb6d2bff238f4fef 100644 (file)
@@ -210,3 +210,4 @@ Tomáš Batelka (Vofy) :: Czech
 Mundo Racional (ismael.mesquita) :: Portuguese, Brazilian
 Zarik (3apuk) :: Russian
 Ali Shaatani (a.shaatani) :: Arabic
+ChacMaster :: Portuguese, Brazilian
index 97abb87ffc5ea83c7b5773d35ede202cd396e5fa..181c9c5803d6441254bd89fc92e66cbddf27c4d0 100644 (file)
@@ -59,7 +59,7 @@ class Deletion extends Model implements Loggable
     /**
      * Get a URL for this specific deletion.
      */
-    public function getUrl($path): string
+    public function getUrl(string $path = 'restore'): string
     {
         return url("/settings/recycle-bin/{$this->id}/" . ltrim($path, '/'));
     }
index b5533429517e12f8263cd02f8dfae328b5403c36..7ad78f1d1475d03c6131146335949bb29b29be82 100644 (file)
@@ -36,6 +36,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
  * @property string     $slug
  * @property Carbon     $created_at
  * @property Carbon     $updated_at
+ * @property Carbon     $deleted_at
  * @property int        $created_by
  * @property int        $updated_by
  * @property bool       $restricted
index 2bfa169f476c3f67fd88a4d2539862aa7a955143..4daf50536441dda8360648eb6ce12c5eee881965 100644 (file)
@@ -46,19 +46,10 @@ class PageRevision extends Model
 
     /**
      * Get the url for this revision.
-     *
-     * @param null|string $path
-     *
-     * @return string
      */
-    public function getUrl($path = null)
+    public function getUrl(string $path = ''): string
     {
-        $url = $this->page->getUrl() . '/revisions/' . $this->id;
-        if ($path) {
-            return $url . '/' . trim($path, '/');
-        }
-
-        return $url;
+        return $this->page->getUrl('/revisions/' . $this->id . '/' . ltrim($path, '/'));
     }
 
     /**
index 43b25817ea3f191c24ee4c76d3d30149374b3f28..ff1ec767f784458e58c867f4a845acd8efdfdbf5 100644 (file)
@@ -24,7 +24,7 @@ class BookSortMap
 
     public static function fromJson(string $json): self
     {
-        $map = new static();
+        $map = new BookSortMap();
         $mapData = json_decode($json);
 
         foreach ($mapData as $mapDataItem) {
index ab62165af1583e146298652e60a984571e12f9ff..1e130c9e17956cc8ecaf8db05a4d3d9807d5c67b 100644 (file)
@@ -22,9 +22,12 @@ class TrashCan
 {
     /**
      * Send a shelf to the recycle bin.
+     *
+     * @throws NotifyException
      */
     public function softDestroyShelf(Bookshelf $shelf)
     {
+        $this->ensureDeletable($shelf);
         Deletion::createForEntity($shelf);
         $shelf->delete();
     }
@@ -36,6 +39,7 @@ class TrashCan
      */
     public function softDestroyBook(Book $book)
     {
+        $this->ensureDeletable($book);
         Deletion::createForEntity($book);
 
         foreach ($book->pages as $page) {
@@ -57,6 +61,7 @@ class TrashCan
     public function softDestroyChapter(Chapter $chapter, bool $recordDelete = true)
     {
         if ($recordDelete) {
+            $this->ensureDeletable($chapter);
             Deletion::createForEntity($chapter);
         }
 
@@ -77,19 +82,47 @@ class TrashCan
     public function softDestroyPage(Page $page, bool $recordDelete = true)
     {
         if ($recordDelete) {
+            $this->ensureDeletable($page);
             Deletion::createForEntity($page);
         }
 
-        // Check if set as custom homepage & remove setting if not used or throw error if active
-        $customHome = setting('app-homepage', '0:');
-        if (intval($page->id) === intval(explode(':', $customHome)[0])) {
-            if (setting('app-homepage-type') === 'page') {
-                throw new NotifyException(trans('errors.page_custom_home_deletion'), $page->getUrl());
+        $page->delete();
+    }
+
+    /**
+     * Ensure the given entity is deletable.
+     * Is not for permissions, but logical conditions within the application.
+     * Will throw if not deletable.
+     *
+     * @throws NotifyException
+     */
+    protected function ensureDeletable(Entity $entity): void
+    {
+        $customHomeId = intval(explode(':', setting('app-homepage', '0:'))[0]);
+        $customHomeActive = setting('app-homepage-type') === 'page';
+        $removeCustomHome = false;
+
+        // Check custom homepage usage for pages
+        if ($entity instanceof Page && $entity->id === $customHomeId) {
+            if ($customHomeActive) {
+                throw new NotifyException(trans('errors.page_custom_home_deletion'), $entity->getUrl());
+            }
+            $removeCustomHome = true;
+        }
+
+        // Check custom homepage usage within chapters or books
+        if ($entity instanceof Chapter || $entity instanceof Book) {
+            if ($entity->pages()->where('id', '=', $customHomeId)->exists()) {
+                if ($customHomeActive) {
+                    throw new NotifyException(trans('errors.page_custom_home_deletion'), $entity->getUrl());
+                }
+                $removeCustomHome = true;
             }
-            setting()->remove('app-homepage');
         }
 
-        $page->delete();
+        if ($removeCustomHome) {
+            setting()->remove('app-homepage');
+        }
     }
 
     /**
index f836f18edad2db02453717f3f2c4f4b43a099a77..2c4c2df1e384e2b2b433062d82d82aa959c3e1c8 100644 (file)
@@ -48,6 +48,8 @@ abstract class Controller extends BaseController
     /**
      * On a permission error redirect to home and display.
      * the error as a notification.
+     *
+     * @return never
      */
     protected function showPermissionError()
     {
diff --git a/public/loading_error.png b/public/loading_error.png
new file mode 100644 (file)
index 0000000..4f588fb
Binary files /dev/null and b/public/loading_error.png differ
index c974ab1b0abd818d7be81c81d08ae56cf134cd6c..6d05d3388af6f8a21809daad455b3d3d46dc4972 100644 (file)
@@ -74,6 +74,10 @@ class ImageManager {
 
         this.listContainer.addEventListener('event-emit-select-image', this.onImageSelectEvent.bind(this));
 
+        this.listContainer.addEventListener('error', event => {
+            event.target.src = baseUrl('loading_error.png');
+        }, true);
+
         onSelect(this.selectButton, () => {
             if (this.callback) {
                 this.callback(this.lastSelected);
index a90f74e2746401562f59c03cea0b8824298f9dc8..def3db5af864f30cd6f321d2f47c80240b9b1dd1 100644 (file)
@@ -395,8 +395,9 @@ class MarkdownEditor {
     actionInsertImage() {
         const cursorPos = this.cm.getCursor('from');
         window.ImageManager.show(image => {
+            const imageUrl = image.thumbs.display || image.url;
             let selectedText = this.cm.getSelection();
-            let newText = "[![" + (selectedText || image.name) + "](" + image.thumbs.display + ")](" + image.url + ")";
+            let newText = "[![" + (selectedText || image.name) + "](" + imageUrl + ")](" + image.url + ")";
             this.cm.focus();
             this.cm.replaceSelection(newText);
             this.cm.setCursor(cursorPos.line, cursorPos.ch + newText.length);
index bde73f4bfb12aa2e87e4f3d250e7b76d8831e314..7a2b6ceba45b4aa492a351c6056c9d05f86d1364 100644 (file)
@@ -563,8 +563,9 @@ class WysiwygEditor {
                         }
 
                         // Replace the actively selected content with the linked image
+                        const imageUrl = image.thumbs.display || image.url;
                         let html = `<a href="${image.url}" target="_blank">`;
-                        html += `<img src="${image.thumbs.display}" alt="${image.name}">`;
+                        html += `<img src="${imageUrl}" alt="${image.name}">`;
                         html += '</a>';
                         win.tinyMCE.activeEditor.execCommand('mceInsertContent', false, html);
                     }, 'gallery');
@@ -723,8 +724,9 @@ class WysiwygEditor {
                     tooltip: 'Insert an image',
                     onclick: function () {
                         window.ImageManager.show(function (image) {
+                            const imageUrl = image.thumbs.display || image.url;
                             let html = `<a href="${image.url}" target="_blank">`;
-                            html += `<img src="${image.thumbs.display}" alt="${image.name}">`;
+                            html += `<img src="${imageUrl}" alt="${image.name}">`;
                             html += '</a>';
                             editor.execCommand('mceInsertContent', false, html);
                         }, 'gallery');
index 9acd965b3b115598b7a878a7283ce31d6700dd7a..13865f40e14fc4b90c7c1c955055ec3d5a44afb8 100644 (file)
@@ -74,7 +74,7 @@ return [
     'status' => 'Stav',
     'status_active' => 'Aktivní',
     'status_inactive' => 'Neaktivní',
-    'never' => 'Never',
+    'never' => 'Nikdy',
 
     // Header
     'header_menu_expand' => 'Rozbalit menu v záhlaví',
index f354321383955a9d23b22c425a83d7057c869556..4980229ec746115645ea144c1a8ecd2b089c0316 100644 (file)
@@ -64,7 +64,7 @@ return [
     'email_not_confirmed_resend_button' => 'Reenviar Correo Electrónico de confirmación',
 
     // User Invite
-    'user_invite_email_subject' => 'As sido invitado a unirte a :appName!',
+    'user_invite_email_subject' => 'Has sido invitado a unirte a :appName!',
     'user_invite_email_greeting' => 'Se ha creado una cuenta para usted en :appName.',
     'user_invite_email_text' => 'Clica en el botón a continuación para ajustar una contraseña y poder acceder:',
     'user_invite_email_action' => 'Ajustar la Contraseña de la Cuenta',
index bbfb675c07ac82915329a3491458a4e2cd877f83..9c8d36669be4ff97611b5ed7727792996a892e02 100644 (file)
@@ -21,7 +21,7 @@ return [
     'email' => 'メールアドレス',
     'password' => 'パスワード',
     'password_confirm' => 'パスワード (確認)',
-    'password_hint' => 'Must be at least 8 characters',
+    'password_hint' => '8文字以上で設定する必要があります',
     'forgot_password' => 'パスワードをお忘れですか?',
     'remember_me' => 'ログイン情報を保存する',
     'ldap_email_hint' => 'このアカウントで使用するEメールアドレスを入力してください。',
@@ -54,7 +54,7 @@ return [
     'email_confirm_text' => '以下のボタンを押し、メールアドレスを確認してください:',
     'email_confirm_action' => 'メールアドレスを確認',
     'email_confirm_send_error' => 'Eメールの確認が必要でしたが、システム上でEメールの送信ができませんでした。管理者に連絡し、Eメールが正しく設定されていることを確認してください。',
-    'email_confirm_success' => 'Your email has been confirmed! You should now be able to login using this email address.',
+    'email_confirm_success' => 'メールアドレスが確認されました!このメールアドレスでログインできるようになりました。',
     'email_confirm_resent' => '確認メールを再送信しました。受信トレイを確認してください。',
 
     'email_not_confirmed' => 'Eメールアドレスが確認できていません',
@@ -71,7 +71,7 @@ return [
     'user_invite_page_welcome' => ':appNameへようこそ!',
     'user_invite_page_text' => 'アカウントの設定を完了してアクセスするには、今後の訪問時に:appNameにログインするためのパスワードを設定する必要があります。',
     'user_invite_page_confirm_button' => 'パスワードを確定',
-    'user_invite_success_login' => 'Password set, you should now be able to login using your set password to access :appName!',
+    'user_invite_success_login' => 'パスワードが設定されました。設定したパスワードで:appNameにログインできるようになりました!',
 
     // Multi-factor Authentication
     'mfa_setup' => '多要素認証を設定',
index 2c1d37c08bc79ebd9f87c9c32090c6af6c3a60a3..bf605525bfd9e29621d662eba4187ce1927163da 100644 (file)
@@ -85,10 +85,10 @@ return [
     'light_mode' => 'ライトモード',
 
     // Layout tabs
-    'tab_info' => 'Info',
-    'tab_info_label' => 'Tab: Show Secondary Information',
-    'tab_content' => 'Content',
-    'tab_content_label' => 'Tab: Show Primary Content',
+    'tab_info' => '情報',
+    'tab_info_label' => 'タブ: サブコンテンツを表示',
+    'tab_content' => '内容',
+    'tab_content_label' => 'タブ: メインコンテンツを表示',
 
     // Email Content
     'email_action_help' => '":actionText" をクリックできない場合、以下のURLをコピーしブラウザで開いてください:',
index e6d1f426b08f90617d7f927f21bdf4a4ef59cae0..e5ba8c673061bcbcd7e8c911e4a25261bb94ab4b 100644 (file)
@@ -22,7 +22,7 @@ return [
     'meta_created_name' => '作成: :timeLength (:user)',
     'meta_updated' => '更新: :timeLength',
     'meta_updated_name' => '更新: :timeLength (:user)',
-    'meta_owned_name' => 'Owned by :user',
+    'meta_owned_name' => '所有者: :user',
     'entity_select' => 'エンティティ選択',
     'images' => '画像',
     'my_recent_drafts' => '最近の下書き',
@@ -36,14 +36,14 @@ return [
     'export_html' => 'Webページ',
     'export_pdf' => 'PDF',
     'export_text' => 'テキストファイル',
-    'export_md' => 'Markdown File',
+    'export_md' => 'Markdown',
 
     // Permissions and restrictions
     'permissions' => '権限',
     'permissions_intro' => 'この設定は各ユーザの役割よりも優先して適用されます。',
     'permissions_enable' => 'カスタム権限設定を有効にする',
     'permissions_save' => '権限を保存',
-    'permissions_owner' => 'Owner',
+    'permissions_owner' => '所有者',
 
     // Search
     'search_results' => '検索結果',
@@ -143,8 +143,8 @@ return [
     'books_sort_chapters_last' => 'チャプターを後に',
     'books_sort_show_other' => '他のブックを表示',
     'books_sort_save' => '並び順を保存',
-    'books_copy' => 'Copy Book',
-    'books_copy_success' => 'Book successfully copied',
+    'books_copy' => 'ブックをコピー',
+    'books_copy_success' => 'ブックが正常にコピーされました',
 
     // Chapters
     'chapter' => 'チャプター',
@@ -163,8 +163,8 @@ return [
     'chapters_move' => 'チャプターを移動',
     'chapters_move_named' => 'チャプター「:chapterName」を移動',
     'chapter_move_success' => 'チャプターを「:bookName」に移動しました',
-    'chapters_copy' => 'Copy Chapter',
-    'chapters_copy_success' => 'Chapter successfully copied',
+    'chapters_copy' => 'チャプターをコピー',
+    'chapters_copy_success' => 'チャプターが正常にコピーされました',
     'chapters_permissions' => 'チャプター権限',
     'chapters_empty' => 'まだチャプター内にページはありません。',
     'chapters_permissions_active' => 'チャプターの権限は有効です',
@@ -215,16 +215,16 @@ return [
     'pages_copy_success' => 'ページが正常にコピーされました',
     'pages_permissions' => 'ページの権限設定',
     'pages_permissions_success' => 'ページの権限を更新しました',
-    'pages_revision' => 'Revision',
+    'pages_revision' => '編集履歴',
     'pages_revisions' => '編集履歴',
     'pages_revisions_named' => ':pageName のリビジョン',
     'pages_revision_named' => ':pageName のリビジョン',
-    'pages_revision_restored_from' => 'Restored from #:id; :summary',
+    'pages_revision_restored_from' => '#:id :summary から復元',
     'pages_revisions_created_by' => '作成者',
     'pages_revisions_date' => '日付',
     'pages_revisions_number' => '#',
-    'pages_revisions_numbered' => 'Revision #:id',
-    'pages_revisions_numbered_changes' => 'Revision #:id Changes',
+    'pages_revisions_numbered' => 'リビジョン #:id',
+    'pages_revisions_numbered_changes' => 'リビジョン #:id の変更',
     'pages_revisions_changelog' => '説明',
     'pages_revisions_changes' => '変更点',
     'pages_revisions_current' => '現在のバージョン',
@@ -333,15 +333,15 @@ return [
 
     // Revision
     'revision_delete_confirm' => 'このリビジョンを削除しますか?',
-    'revision_restore_confirm' => 'Are you sure you want to restore this revision? The current page contents will be replaced.',
+    'revision_restore_confirm' => 'このリビジョンを復元してよろしいですか?現在のページの内容が置換されます。',
     'revision_delete_success' => 'リビジョンを削除しました',
     'revision_cannot_delete_latest' => '最新のリビジョンを削除できません。',
 
     // Copy view
-    'copy_consider' => 'Please consider the below when copying content.',
-    'copy_consider_permissions' => 'Custom permission settings will not be copied.',
-    'copy_consider_owner' => 'You will become the owner of all copied content.',
-    '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.',
+    'copy_consider' => 'コンテンツをコピーする場合は以下の点にご注意ください。',
+    'copy_consider_permissions' => 'カスタム権限設定はコピーされません。',
+    'copy_consider_owner' => 'あなたはコピーされた全てのコンテンツの所有者になります。',
+    'copy_consider_images' => 'ページの画像ファイルは複製されず、元の画像は最初にアップロードされたページとの関係を保持します。',
+    'copy_consider_attachments' => 'ページの添付ファイルはコピーされません。',
+    'copy_consider_access' => '場所、所有者または権限を変更すると、以前アクセスできなかったユーザーがこのコンテンツにアクセスできるようになる可能性があります。',
 ];
index c30e3c50cedc008c5de501430934ec32e9381a0f..d63e9bf365b8bba17ddbbb674e571dc48b2f8b12 100644 (file)
@@ -99,7 +99,7 @@ return [
     'api_no_authorization_found' => 'リクエストに認証トークンが見つかりません',
     'api_bad_authorization_format' => 'リクエストに認証トークンが見つかりましたが、形式が正しくないようです',
     'api_user_token_not_found' => '提供された認証トークンに一致するAPIトークンが見つかりませんでした',
-    'api_incorrect_token_secret' => 'The secret provided for the given used API token is incorrect',
+    'api_incorrect_token_secret' => '利用されたAPIトークンに対して提供されたシークレットが正しくありません',
     'api_user_no_api_permission' => '使用されているAPIトークンの所有者には、API呼び出しを行う権限がありません',
     'api_user_token_expired' => '認証トークンが期限切れです。',
 
index bee756813e5c2d50b23c7969e59b8f803bb46c2f..0fb4b113edb815374ddba6db5476ff84e6cc5522 100644 (file)
@@ -234,27 +234,27 @@ return [
     'user_api_token_delete_success' => 'APIトークンが正常に削除されました',
 
     // Webhooks
-    'webhooks' => 'Webhooks',
-    'webhooks_create' => 'Create New Webhook',
-    'webhooks_none_created' => 'No webhooks have yet been created.',
-    'webhooks_edit' => 'Edit Webhook',
-    'webhooks_save' => 'Save Webhook',
-    'webhooks_details' => 'Webhook Details',
-    'webhooks_details_desc' => 'Provide a user friendly name and a POST endpoint as a location for the webhook data to be sent to.',
-    'webhooks_events' => 'Webhook Events',
-    'webhooks_events_desc' => 'Select all the events that should trigger this webhook to be called.',
-    'webhooks_events_warning' => 'Keep in mind that these events will be triggered for all selected events, even if custom permissions are applied. Ensure that use of this webhook won\'t expose confidential content.',
-    'webhooks_events_all' => 'All system events',
-    'webhooks_name' => 'Webhook Name',
-    'webhooks_timeout' => 'Webhook Request Timeout (Seconds)',
-    'webhooks_endpoint' => 'Webhook Endpoint',
+    'webhooks' => 'Webhook',
+    'webhooks_create' => 'Webhookを作成',
+    'webhooks_none_created' => 'Webhookはまだ作成されていません。',
+    'webhooks_edit' => 'Webhookを編集',
+    'webhooks_save' => 'Webhookを保存',
+    'webhooks_details' => 'Webhookの詳細',
+    'webhooks_details_desc' => 'ユーザーフレンドリーな名前とWebhookデータの送信先にするPOSTエンドポイントを指定します。',
+    'webhooks_events' => 'Webhookのイベント',
+    'webhooks_events_desc' => 'このWebhookの呼び出しをトリガーするすべてのイベントを選択します。',
+    'webhooks_events_warning' => 'これらのイベントはカスタム権限が適用されている場合でも、選択したすべてのイベントに対してトリガーされることに注意してください。このWebhookの利用により機密コンテンツが公開されないことを確認してください。',
+    'webhooks_events_all' => '全てのシステムイベント',
+    'webhooks_name' => 'Webhook',
+    'webhooks_timeout' => 'Webhookリクエストタイムアウト (秒)',
+    'webhooks_endpoint' => 'Webhookエンドポイント',
     'webhooks_active' => 'Webhook Active',
     'webhook_events_table_header' => 'Events',
     'webhooks_delete' => 'Delete Webhook',
     'webhooks_delete_warning' => 'This will fully delete this webhook, with the name \':webhookName\', from the system.',
     'webhooks_delete_confirm' => 'Are you sure you want to delete this webhook?',
-    'webhooks_format_example' => 'Webhook Format Example',
-    'webhooks_format_example_desc' => 'Webhook data is sent as a POST request to the configured endpoint as JSON following the format below. The "related_item" and "url" properties are optional and will depend on the type of event triggered.',
+    'webhooks_format_example' => 'Webhookのフォーマット例',
+    'webhooks_format_example_desc' => 'Webhookのデータは、設定されたエンドポイントにPOSTリクエストにより以下のフォーマットのJSONで送信されます。related_item と url プロパティはオプションであり、トリガーされるイベントの種類によって異なります。',
     'webhooks_status' => 'Webhook Status',
     'webhooks_last_called' => 'Last Called:',
     'webhooks_last_errored' => 'Last Errored:',
index b24446622cf06f88269d739bd2353a9601e15813..f6fa8e415c1d090318b2d913f01b4ced09e34b31 100644 (file)
@@ -7,57 +7,57 @@ return [
 
     // Pages
     'page_create'                 => 'criou a página',
-    'page_create_notification'    => 'Page successfully created',
+    'page_create_notification'    => 'Página criada com sucesso',
     'page_update'                 => 'atualizou a página',
-    'page_update_notification'    => 'Page successfully updated',
+    'page_update_notification'    => 'Página atualizada com sucesso',
     'page_delete'                 => 'excluiu a página',
-    'page_delete_notification'    => 'Page successfully deleted',
+    'page_delete_notification'    => 'Página excluída com sucesso',
     'page_restore'                => 'restaurou a página',
-    'page_restore_notification'   => 'Page successfully restored',
+    'page_restore_notification'   => 'Página restaurada com sucesso',
     'page_move'                   => 'moveu a página',
 
     // Chapters
     'chapter_create'              => 'criou o capítulo',
-    'chapter_create_notification' => 'Chapter successfully created',
+    'chapter_create_notification' => 'Capítulo criado com sucesso',
     'chapter_update'              => 'atualizou o capítulo',
-    'chapter_update_notification' => 'Chapter successfully updated',
+    'chapter_update_notification' => 'Capítulo atualizado com sucesso',
     'chapter_delete'              => 'excluiu o capítulo',
-    'chapter_delete_notification' => 'Chapter successfully deleted',
+    'chapter_delete_notification' => 'Capítulo excluída com sucesso',
     'chapter_move'                => 'moveu o capítulo',
 
     // Books
     'book_create'                 => 'criou o livro',
-    'book_create_notification'    => 'Book successfully created',
+    'book_create_notification'    => 'Livro criado com sucesso',
     'book_update'                 => 'atualizou o livro',
-    'book_update_notification'    => 'Book successfully updated',
+    'book_update_notification'    => 'Livro atualizado com sucesso',
     'book_delete'                 => 'excluiu o livro',
-    'book_delete_notification'    => 'Book successfully deleted',
+    'book_delete_notification'    => 'Livro excluído com sucesso',
     'book_sort'                   => 'ordenou o livro',
-    'book_sort_notification'      => 'Book successfully re-sorted',
+    'book_sort_notification'      => 'Livro reordenado com sucesso',
 
     // Bookshelves
-    'bookshelf_create'            => 'created bookshelf',
-    'bookshelf_create_notification'    => 'Bookshelf successfully created',
+    'bookshelf_create'            => 'prateleira criada',
+    'bookshelf_create_notification'    => 'Prateleira criada com sucesso',
     'bookshelf_update'                 => 'atualizou a prateleira',
-    'bookshelf_update_notification'    => 'Bookshelf successfully updated',
+    'bookshelf_update_notification'    => 'Prateleira atualizada com sucesso',
     'bookshelf_delete'                 => 'excluiu a prateleira',
-    'bookshelf_delete_notification'    => 'Bookshelf successfully deleted',
+    'bookshelf_delete_notification'    => 'Prateleira excluída com sucesso',
 
     // 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" foi adicionada aos seus favoritos',
+    'favourite_remove_notification' => '":name" foi removida dos seus favoritos',
 
     // MFA
-    'mfa_setup_method_notification' => 'Multi-factor method successfully configured',
-    'mfa_remove_method_notification' => 'Multi-factor method successfully removed',
+    'mfa_setup_method_notification' => 'Método de multi-fatores configurado com sucesso',
+    'mfa_remove_method_notification' => 'Método de multi-fatores removido com sucesso',
 
     // 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 criado',
+    'webhook_create_notification' => 'Webhook criado com sucesso',
+    'webhook_update' => 'webhook atualizado',
+    'webhook_update_notification' => 'Webhook atualizado com sucesso',
+    'webhook_delete' => 'webhook excluído',
+    'webhook_delete_notification' => 'Webhook excluido com sucesso',
 
     // Other
     'commented_on'                => 'comentou em',
index 00d0ca84469bfa110e8e6626f195859aa57f1c25..fd2e4235f036fa585fd00ec4ac7dbee7deee711b 100644 (file)
@@ -39,14 +39,14 @@ return [
     'reset' => 'Redefinir',
     'remove' => 'Remover',
     'add' => 'Adicionar',
-    'configure' => 'Configure',
+    'configure' => 'Configurar',
     'fullscreen' => 'Tela cheia',
     'favourite' => 'Favoritos',
     'unfavourite' => 'Remover dos Favoritos',
     'next' => 'Seguinte',
     'previous' => 'Anterior',
-    'filter_active' => 'Active Filter:',
-    'filter_clear' => 'Clear Filter',
+    'filter_active' => 'Filtro Ativo:',
+    'filter_clear' => 'Limpar Filtro',
 
     // Sort Options
     'sort_options' => 'Opções de Ordenação',
@@ -72,12 +72,12 @@ return [
     'default' => 'Padrão',
     'breadcrumb' => 'Caminho',
     'status' => 'Status',
-    'status_active' => 'Active',
-    'status_inactive' => 'Inactive',
-    'never' => 'Never',
+    'status_active' => 'Ativo',
+    'status_inactive' => 'Inativo',
+    'never' => 'Nunca',
 
     // Header
-    'header_menu_expand' => 'Expand Header Menu',
+    'header_menu_expand' => 'Expandir Cabeçalho do Menu',
     'profile_menu' => 'Menu de Perfil',
     'view_profile' => 'Visualizar Perfil',
     'edit_profile' => 'Editar Perfil',
@@ -86,9 +86,9 @@ return [
 
     // Layout tabs
     'tab_info' => 'Informações',
-    'tab_info_label' => 'Tab: Show Secondary Information',
+    'tab_info_label' => 'Aba: Mostrar Informação Secundária',
     'tab_content' => 'Conteúdo',
-    'tab_content_label' => 'Tab: Show Primary Content',
+    'tab_content_label' => 'Aba: Mostrar Conteúdo Primário',
 
     // Email Content
     'email_action_help' => 'Se você estiver tendo problemas ao clicar o botão ":actionText", copie e cole a URL abaixo no seu navegador:',
index 5edb74d250d0a33752051970ba8e27f05eeff7bd..412ecb062b023378de0b8daef9f16e3d74abefd7 100644 (file)
@@ -74,7 +74,7 @@ return [
     'status' => '状态',
     'status_active' => '已激活',
     'status_inactive' => '未激活',
-    'never' => '永不',
+    'never' => '从未',
 
     // Header
     'header_menu_expand' => '展开标头菜单',
index 85b7094b9eb03bebaaf98d8a3ecd3946b22d714d..064b166501131a887629fe4d424df9a40f9da489 100755 (executable)
@@ -236,7 +236,7 @@ return [
     // Webhooks
     'webhooks' => 'Webhooks',
     'webhooks_create' => '新建 Webhook',
-    'webhooks_none_created' => '不存在已创建的 webhooks',
+    'webhooks_none_created' => '尚未创建任何 webhook',
     'webhooks_edit' => '编辑 Webhook',
     'webhooks_save' => '保存 Webhook',
     'webhooks_details' => 'Webhook 详情',
index 6d62552266945fc7d27579cab7044a835289907f..81041fcac04e4fecd79861301f43ccaca92da2f5 100644 (file)
@@ -8,7 +8,7 @@
 
         <div class="image-manager-viewer">
             <a href="{{ $image->url }}" target="_blank" rel="noopener" class="block">
-                <img src="{{ $image->thumbs['display'] }}"
+                <img src="{{ $image->thumbs['display'] ?? $image->url }}"
                      alt="{{ $image->name }}"
                      class="anim fadeIn"
                      title="{{ $image->name }}">
index dc1b2277959fef2437218e9372c34c3c437b5706..900650a70537eaddd6da5b4d68a8d28dc91b1155 100644 (file)
@@ -79,6 +79,24 @@ class HomepageTest extends TestCase
         $pageDeleteReq->assertSessionMissing('error');
     }
 
+    public function test_custom_homepage_cannot_be_deleted_from_parent_deletion()
+    {
+        /** @var Page $page */
+        $page = Page::query()->first();
+        $this->setSettings([
+            'app-homepage'      => $page->id,
+            'app-homepage-type' => 'page',
+        ]);
+
+        $this->asEditor()->delete($page->book->getUrl());
+        $this->assertSessionError('Cannot delete a page while it is set as a homepage');
+        $this->assertDatabaseMissing('deletions', ['deletable_id' => $page->book->id]);
+
+        $page->refresh();
+        $this->assertNull($page->deleted_at);
+        $this->assertNull($page->book->deleted_at);
+    }
+
     public function test_custom_homepage_renders_includes()
     {
         $this->asEditor();